Skip to content

Instantly share code, notes, and snippets.

@KSXGitHub
Last active November 6, 2018 10:23
Show Gist options
  • Save KSXGitHub/939327737d39aa3ed20ed07814165cfd to your computer and use it in GitHub Desktop.
Save KSXGitHub/939327737d39aa3ed20ed07814165cfd to your computer and use it in GitHub Desktop.
#! /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