diff options
| author | Agando <roduccomm@gmail.com> | 2026-09-29 13:10:14 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-09-29 13:10:14 +0200 |
| commit | 534fc0f767b681e7025188092251a12fce77dc74 (patch) | |
| tree | fb58d591f8f2ee4047644b6da48aae0009e5be3d /modules/core/module-fs.js | |
| parent | a7399a9df6caab582c07f09ee544d31e2a729508 (diff) | |
newOS4: tail -n/-N flags + live follow mode (-f) with test harness
Diffstat (limited to 'modules/core/module-fs.js')
| -rw-r--r-- | modules/core/module-fs.js | 49 |
1 files changed, 45 insertions, 4 deletions
diff --git a/modules/core/module-fs.js b/modules/core/module-fs.js index 25442eb..2fb9e65 100644 --- a/modules/core/module-fs.js +++ b/modules/core/module-fs.js @@ -232,11 +232,52 @@ class FileSystemModule { this.terminal.print(c.split('\n').slice(0, n).join('\n')); }; this.commands['tail'] = (args) => { - if(!args[0]) return this.terminal.print('Usage: tail <filename> [lines]', 'warning'); - const c = this.terminal.fs.readFile(args[0]); - if(c === null || c === undefined) return this.terminal.print('tail: ' + args[0] + ': No such file', 'error'); - const n = args[1] ? parseInt(args[1]) : 10; + const self = this; + let follow = false, n = 10; + const positional = []; + for (let i = 0; i < (args || []).length; i++) { + const a = args[i]; + if (a === '-f' || a === '--follow') follow = true; + else if (a === '-n') { + const v = args[++i]; + if (v === undefined || !/^\d+$/.test(v)) return this.terminal.print('tail: -n requires a number', 'error'); + n = parseInt(v, 10); + } + else if (/^-\d+$/.test(a)) n = parseInt(a.slice(1), 10); + else if (a === '-c' || a === '-q' || a === '-v' || a === '--pid') return this.terminal.print('tail: option ' + a + ' not supported', 'error'); + else if (a.startsWith('-')) return this.terminal.print('tail: invalid option: ' + a, 'error'); + else positional.push(a); + } + const file = positional[0]; + if (!file) return this.terminal.print('Usage: tail [-f] [-n N] <file> — last N lines; -f follows changes (Ctrl+C or Esc to stop)', 'warning'); + if (positional[1] && /^\d+$/.test(positional[1])) n = parseInt(positional[1], 10); // legacy back-compat: tail <file> <count> + const c = this.terminal.fs.readFile(file); + if (c === null || c === undefined) return this.terminal.print('tail: ' + file + ': No such file', 'error'); this.terminal.print(c.split('\n').slice(-n).join('\n')); + if (!follow) return; + // stop any previous follow before starting a new one + if (this.terminal._activeFollow && this.terminal._activeFollow.interval) clearInterval(this.terminal._activeFollow.interval); + this.terminal._activeFollow = null; + let offset = c.length; + const interval = setInterval(() => { + const cur = self.terminal.fs.readFile(file); + if (cur === null || cur === undefined) { + self.terminal.stopFollow('tail: ' + file + ': file removed — follow stopped'); + return; + } + if (cur.length < offset) { + self.terminal.print('tail: ' + file + ': file truncated — following from start', 'warning'); + self.terminal.print(cur); + offset = cur.length; + return; + } + if (cur.length > offset) { + self.terminal.print(cur.slice(offset)); + offset = cur.length; + } + }, 1500); + this.terminal._activeFollow = { interval, file }; + this.terminal.print('tail: following ' + file + ' (Ctrl+C or Esc to stop)', 'text-muted'); }; this.commands['sort'] = (args) => { if(!args[0]) return this.terminal.print('Usage: sort <filename>', 'warning'); |