NAME
Skilletor::FSUtil - File and path helpers that behave as the reference's Node calls do
VERSION
version 0.001
SYNOPSIS
use Skilletor::FSUtil qw( read_text write_text inner_segments path_join );
my $text = read_text('/home/me/.claude/skilletor.json'); # as readFileSync(…, "utf8")
write_text( $file, $json."\n" ); # temp file + rename
my $segs = inner_segments('skills/../x'); # 'dotdot'
DESCRIPTION
The port of the reference's fsutil.ts, plus the pieces of Node's node:path and node:fs whose behaviour reaches a file name, a message or a hash: path.join, path.normalize and path.resolve (POSIX), and readFileSync(…, "utf8")'s lossy decoding. Plain functions, exported on request.
read_text
The file's bytes decoded as UTF-8, each ill-formed sequence replaced by one U+FFFD as the WHATWG decoder does (Node's behaviour); a BOM stays.
decode_utf8_lossy
The decoding "read_text" uses, for bytes already in hand.
encode_text
A character string as UTF-8 bytes, a lone surrogate as U+FFFD – what Node's Buffer.from(string) and createHash().update(string) make of it.
write_text
write_text( $file, $text, $executable );
"atomic_write" of "encode_text".
atomic_write
atomic_write( $file, $bytes, $executable );
Creates the parent directories, writes a .skilletor-tmp-* file beside the target with mode 0666 (0777 when $executable) less the umask, and renames it over the target. Croaks on characters above 0xFF: encode first.
inner_segments
inner_segments('a/./b'); # ['a', 'b']
inner_segments('/a'); # 'absolute'
inner_segments('a/../b'); # 'dotdot'
inner_segments('.'); # 'root'
The segments of a path relative to some root, or why it names nothing beneath it. \ separates like /.
path_join
path.join of Node's POSIX path module.
path_normalize
path.normalize of Node's POSIX path module.
path_resolve
path.join, path.normalize, path.resolve of Node's POSIX path module.
is_absolute
path.isAbsolute; on Windows a drive or backslash path too.
real_or_resolved
The real path of an existing path, else its "path_resolve" form.
same_path
True when two paths name the same location once symlinks are resolved.
same_file
True when writing one path would write the other: the same target once links are followed (a missing file or dangling link by its real parent), or the same inode (a hard link). What tells two spellings of a name apart on a case-insensitive file system. Never throws.
to_posix
The path with / between its parts (a no-op off Windows).
node_fs_error
node_fs_error( 0 + $!, 'scandir', $path ); # "ENOENT: no such file or directory, scandir '/x'"
The message Node gives a failed node:fs call, for an errno.
hash_bytes
sha256: and the hex digest – the hash of a lock entry's file.
is_executable
True when a mode has the owner's executable bit.
SUPPORT
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.