Last active
November 6, 2018 10:23
-
-
Save KSXGitHub/939327737d39aa3ed20ed07814165cfd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env lsc | |
require! 'path' | |
require! 'fs' | |
const {exists-sync, readlink-sync} = fs | |
const {stdout, stderr, exit, cwd} = require 'process' | |
const {spawn-sync} = require 'child_process' | |
require! 'js-yaml' | |
const {create-command-arguments} = require './lib/shell-command-utils.js' | |
const argv = let | |
const usage = ''' | |
Usage: | |
$ $0 [options] [arguments] | |
''' | |
const example = ''' | |
$ $0 directory-or-file | |
$ $0 --follow=inf symlink | |
$ $0 -S a directory | |
$ $0 -L all --ls=colorls directory | |
$ $0 file --cat=/usr/bin/highlight -L failsafe -O '{tab: 4, out-format: ansi}' | |
''' | |
const coerce = do | |
yaml: (js-yaml.load _) | |
strarr: (x) -> Array.from(x).map((String _)) | |
chararr: (Array.from _) | |
const options = do | |
'help': | |
alias: 'h' | |
'cmd-cat': | |
alias: ['cat', 'c'] | |
describe: 'Cat program' | |
type: 'string' | |
default: 'cat' | |
'cmd-ls': | |
alias: ['ls', 'l'] | |
describe: 'Ls program' | |
type: 'string' | |
default: 'ls' | |
'dont-fake-interactive': | |
alias: ['no-interactive', 'no-script', 'n'] | |
describe: 'Tell catls to not use script command' | |
type: 'boolean' | |
'handle-non-arguments': | |
alias: ['on-zero-args', 'z'] | |
describe: 'Tell catls how to handle when there is no arguments (error[:status]|warn|quiet)' | |
type: 'string' | |
default: 'error' | |
'follow-symlink': | |
alias: ['cmd-follow', 'follow'] | |
describe: 'Follow symlink, value can be a natural number or Infinite' | |
type: 'string' | |
default: '0' | |
'symlink-agnostic': | |
alias: ['no-long-stat', 'no-lstat', 'short-stat', 'stat', 's'] | |
describe: 'Use stat() instead of lstat()' | |
type: 'boolean' | |
'symlink-ultimate': | |
alias: ['realpath', 'u'] | |
describe: 'Get to the final link target immediately' | |
type: 'boolean' | |
'common-options': | |
alias: ['options', 'O'] | |
describe: 'Shell options for both ls and cat' | |
type: 'string' | |
coerce: coerce.yaml | |
default: '{}' | |
'common-long-flags': | |
alias: ['long-flags', 'L'] | |
describe: 'Shell long-named flags for both ls and cat' | |
type: 'array' | |
coerce: coerce.strarr | |
default: [] | |
'common-short-flags': | |
alias: ['short-flags', 'S'] | |
describe: 'Shell character flags for both ls and cat' | |
type: 'string' | |
coerce: coerce.chararr | |
default: '' | |
'cat-options': | |
describe: 'Shell options with values for cat' | |
type: 'string' | |
coerce: coerce.yaml | |
default: '{}' | |
'cat-long-flags': | |
describe: 'Shell long-named options without values for cat' | |
type: 'array' | |
coerce: coerce.strarr | |
default: [] | |
'cat-short-flags': | |
describe: 'Shell long-named options without values for cat' | |
type: 'array' | |
coerce: coerce.chararr | |
default: [] | |
'ls-options': | |
describe: 'Shell options with values for ls' | |
type: 'string' | |
coerce: coerce.yaml | |
default: '{}' | |
'ls-long-flags': | |
describe: 'Shell long-named options without values for ls' | |
type: 'array' | |
coerce: coerce.strarr | |
default: [] | |
'ls-short-flags': | |
describe: 'Shell long-named options without values for ls' | |
type: 'array' | |
coerce: coerce.chararr | |
default: [] | |
return 'yargs' | |
|> require | |
|> (.usage usage) | |
|> (.example example) | |
|> (.env '') | |
|> (.options options) | |
|> (.help!) | |
|> (.argv) | |
const wdir = cwd! | |
const {_: list, cat, ls, dont-fake-interactive} = argv | |
const {follow-symlink, symlink-agnostic, symlink-ultimate} = argv | |
const {cat-args, ls-args} = let | |
const {cat-sum-opts, ls-sum-opts} = let {common-options, cat-options, ls-options} = argv | |
cat-sum-opts: {...common-options, ...cat-options} | |
ls-sum-opts: {...common-options, ...ls-options} | |
const {cat-sum-flags, ls-sum-flags} = let shared = argv.common-long-flags ++ argv.common-short-flags | |
cat-sum-flags: shared ++ argv.cat-long-flags ++ argv.cat-short-flags | |
ls-sum-flags: shared ++ argv.ls-long-flags ++ argv.ls-short-flags | |
return | |
cat-args: create-command-arguments [], cat-sum-opts, cat-sum-flags | |
ls-args: create-command-arguments [], ls-sum-opts, ls-sum-flags | |
const execute = if dont-fake-interactive | |
then (cmd, ...args) -> spawn-sync cmd, args | |
else let | |
require! 'shell-escape' | |
return (cmd, ...args) -> | |
const shell-command = shell-escape [cmd, ...args] | |
return spawn-sync 'silent-script', [shell-command] | |
const {get-stat, get-link, get-loop} = let | |
if symlink-agnostic and symlink-ultimate then | |
stderr.write ' | |
[ERROR] Conflict options: \ | |
Options symlink-agnostic and symlink-ultimate \ | |
cannot be true at the same time\n | |
' | |
exit 1 | |
if symlink-agnostic then return | |
get-stat: fs.stat-sync | |
get-link: -> throw new Error "Something goes wrong: Shouldn't end up calling getLink()" | |
get-loop: -> -> 0 | |
if symlink-ultimate then return | |
get-stat: fs.lstat-sync | |
get-link: fs.realpath-sync | |
get-loop: (main) -> (name) -> main name, 0, [] | |
return | |
get-stat: fs.lstat-sync | |
get-link: require('./lib/read-exact-link.js').sync | |
get-loop: (main) -> (a, b, c) -> main a, b, c | |
const handle-single-unit = (name, follow-symlink) -> | |
const main = (name, follow-symlink, visited) -> | |
const fullpath = path.resolve wdir, name | |
if not exists-sync fullpath | |
stderr.write "[ERROR] No such file or directory: #{name}\n" | |
return 1 | |
let stats = get-stat fullpath | |
const show-stat-info = (type, body = []) -> | |
const head = [ | |
['Type', type] | |
] | |
const tail = | |
['Size', stats.size] | |
['Mode', stats.mode] | |
['Modified', stats.mtime.toISOString!] | |
['Accessed', stats.atime.toISOString!] | |
['Changed', stats.ctime.toISOString!] | |
head ++ body ++ tail | |
|> (.map ([title, value]) -> " #{title}: #{value}") | |
|> (.join '\n') | |
|> ('[INFO]\n' +) | |
|> (+ '\n\n') | |
|> stdout.write | |
const show-exec-data = let | |
const create-reader = './lib/split-shell-string.js' |> require |> (.create-from-buffer) | |
const write = (stream) -> | |
stream |> create-reader |> (.set-prefix ' ' * 2) |> (.writeln!) | |
return (...args) -> | |
const {status, stdout, stderr} = execute ...args | |
write stdout | |
write stderr | |
stdout.write '\n\n' | |
return status | |
let | |
const heading = "📁 Item: #{fullpath}" | |
const hr = '—'.repeat heading.length | |
stdout.write "\n#{hr}\n#{heading}\n\n" | |
if stats.is-symbolic-link! | |
const link-target = get-link fullpath | |
show-stat-info 'Symbolic Link', [ | |
['Target', link-target] | |
['Data', readlink-sync fullpath] | |
] | |
return if follow-symlink and not visited.includes link-target | |
then loop-main link-target, follow-symlink - 1, visited +++ [link-target] | |
else 0 | |
if stats.is-file! | |
show-stat-info 'File' | |
stdout.write '[DATA]\n' | |
return show-exec-data cat, ...cat-args, fullpath | |
if stats.is-directory! | |
show-stat-info 'Directory' | |
stdout.write '[CONTENT]\n' | |
return show-exec-data ls, ...ls-args, fullpath | |
return let | |
show-stat-info let | |
const typemap = do | |
BlockDevice: 'Block Device' | |
CharacterDevice: 'Character Device' | |
FIFO: 'FIFO' | |
Socket: 'Socket' | |
for fname of typemap | |
if stats["is#{fname}"]! | |
return typemap[fname] | |
return 'Unknown' | |
return 0 | |
const loop-main = get-loop main | |
return main name, follow-symlink, [] | |
if not list.length | |
const [action, status] = argv.handle-non-arguments.split ':' | |
if action is 'quiet' | |
return exit 0 | |
if action is 'warn' | |
stderr.write '[WARN] Should provide at least 1 argument\n' | |
return exit 0 | |
if action is 'error' | |
const code = Number status | |
stderr.write '[ERROR] Must provide at least 1 argument\n' | |
exit if is-finite code then code else 1 | |
let | |
stderr.write '[WARN] Invalid value of --handle-no-arguments\n' | |
exit 0 | |
let | |
const actual-follow-symlink = let infinities = ['inf', 'infinity', '∞', 'all', 'yes', 'true'] | |
return if infinities.includes follow-symlink then Infinity else ((follow-symlink .>>. 0) >? 0) | |
list | |
|> (.map handle-single-unit _, actual-follow-symlink) | |
|> (.reduce (.|.), 0) | |
|> exit |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment