buninu-linux 0.8.55 → 0.9.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +46 -46
- package/README.md +7 -6
- package/initramfs/bin/tar +34 -46
- package/initramfs/buninu/apps/bunmsh/CHANGELOG.md +103 -0
- package/initramfs/buninu/apps/bunmsh/PORTING.md +133 -15
- package/initramfs/buninu/apps/bunmsh/help/awk.md +38 -0
- package/initramfs/buninu/apps/bunmsh/help/break.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/chmod.md +8 -2
- package/initramfs/buninu/apps/bunmsh/help/cmp.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/continue.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/cp.md +4 -0
- package/initramfs/buninu/apps/bunmsh/help/diff.md +30 -0
- package/initramfs/buninu/apps/bunmsh/help/egrep.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/export.md +2 -2
- package/initramfs/buninu/apps/bunmsh/help/expr.md +30 -0
- package/initramfs/buninu/apps/bunmsh/help/fgrep.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/grep.md +4 -0
- package/initramfs/buninu/apps/bunmsh/help/ls.md +4 -2
- package/initramfs/buninu/apps/bunmsh/help/mkdir.md +1 -1
- package/initramfs/buninu/apps/bunmsh/help/print.md +3 -0
- package/initramfs/buninu/apps/bunmsh/help/return.md +23 -0
- package/initramfs/buninu/apps/bunmsh/help/sed.md +8 -3
- package/initramfs/buninu/apps/bunmsh/help/set.md +12 -3
- package/initramfs/buninu/apps/bunmsh/help/tr.md +4 -0
- package/initramfs/buninu/apps/bunmsh/help/trap.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/uniq.md +27 -0
- package/initramfs/buninu/apps/bunmsh/package.json +1 -1
- package/initramfs/buninu/apps/bunmsh/src/awk.js +203 -0
- package/initramfs/buninu/apps/bunmsh/src/diff.js +222 -0
- package/initramfs/buninu/apps/bunmsh/src/fancy-ls.js +15 -4
- package/initramfs/buninu/apps/bunmsh/src/main.js +21 -5
- package/initramfs/buninu/apps/bunmsh/src/sed.js +159 -0
- package/initramfs/buninu/apps/bunmsh/src/shell.js +1283 -540
- package/initramfs/buninu/apps/bunmsh/test/awk-reference.test.js +35 -0
- package/initramfs/buninu/apps/bunmsh/test/awk.test.js +62 -0
- package/initramfs/buninu/apps/bunmsh/test/posix-bootstrap.test.js +595 -0
- package/initramfs/buninu/apps/bunmsh/test/utility-reference-cases.mjs +98 -0
- package/initramfs/buninu/apps/bunmsh/test/utility-reference.test.js +23 -0
- package/initramfs/buninu/apps/jsgotty/client-kitty.mjs +61 -0
- package/initramfs/buninu/apps/jsgotty/client.mjs +16 -0
- package/initramfs/buninu/apps/jsgotty/package-lock.json +2 -2
- package/initramfs/buninu/apps/jsgotty/package.json +1 -1
- package/initramfs/buninu/apps/jsgotty/tests/client-kitty.test.mjs +43 -0
- package/initramfs/etc/resolv.conf +2 -1
- package/initramfs/usr/share/doc/buninu-linux/tar.md +12 -1
- package/package.json +1 -1
- package/scripts/arch.js +5 -5
- package/scripts/arch.sh +5 -5
- package/scripts/fetch.js +2 -0
- package/scripts/npm-packages.md +4 -3
- package/test/fetch-npm.test.js +1 -1
- package/test/tar-strip.test.js +36 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// A tree-walking AWK interpreter. Shell input is data, never JavaScript source.
|
|
2
|
+
class InputString {
|
|
3
|
+
constructor(text) { this.text=String(text); this.numeric=/^[ \t]*[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[ \t]*$/.test(this.text); }
|
|
4
|
+
}
|
|
5
|
+
const str=v=>v instanceof InputString?v.text:v==null?'':String(v);
|
|
6
|
+
const num=v=>parseFloat(str(v))||0;
|
|
7
|
+
const numeric=v=>typeof v==='number'||v instanceof InputString&&v.numeric;
|
|
8
|
+
const truth=v=>numeric(v)?num(v)!==0:str(v)!=='';
|
|
9
|
+
const unset=new InputString(''); unset.numeric=true;
|
|
10
|
+
const failure=m=>{throw new Error(`awk: ${m}`);};
|
|
11
|
+
function regex(s,flags='') {
|
|
12
|
+
s=str(s).replaceAll('[[:space:]]','[\\s]').replaceAll('[[:digit:]]','[0-9]').replaceAll('[[:alnum:]]','[A-Za-z0-9]').replaceAll('[[:alpha:]]','[A-Za-z]').replaceAll('[[:blank:]]','[ \t]');
|
|
13
|
+
try {return new RegExp(s,flags);}catch(error){failure(`invalid regular expression ${JSON.stringify(s)}: ${error.message}`);}
|
|
14
|
+
}
|
|
15
|
+
function lex(source) {
|
|
16
|
+
const out=[];let i=0,operand=true;
|
|
17
|
+
const push=(kind,value)=>{out.push({kind,value});operand=!(kind==='number'||kind==='string'||kind==='regex'||kind==='name'&&!['print','printf','return','exit','getline','in','delete'].includes(value)||[')',']','++','--'].includes(value));};
|
|
18
|
+
while(i<source.length) {
|
|
19
|
+
const c=source[i];
|
|
20
|
+
if(c==='\\'&&source[i+1]==='\n'){i+=2;continue;}
|
|
21
|
+
if(/[ \t\r]/.test(c)){i++;continue;}
|
|
22
|
+
if(c==='#'){while(i<source.length&&source[i]!=='\n')i++;continue;}
|
|
23
|
+
if(c==='\n'){if(!(out.at(-1)?.kind==='op'&&[',','&&','||'].includes(out.at(-1).value)))push('op',';');i++;continue;}
|
|
24
|
+
if(c==='"'||c==='/'&&operand){
|
|
25
|
+
const delim=c;let value='';i++;let closed=false;
|
|
26
|
+
while(i<source.length){const ch=source[i++];if(ch===delim){closed=true;break;}
|
|
27
|
+
if(ch==='\\') {if(i>=source.length)break;const n=source[i++];
|
|
28
|
+
if(n==='\n')continue;
|
|
29
|
+
if(delim==='/'){value+=n==='/'?'/':'\\'+n;continue;}
|
|
30
|
+
if(/[0-7]/.test(n)){let oct=n;while(oct.length<3&&/[0-7]/.test(source[i]??''))oct+=source[i++];value+=String.fromCharCode(parseInt(oct,8));}
|
|
31
|
+
else value+=({n:'\n',t:'\t',r:'\r',b:'\b',f:'\f',v:'\v',a:'\x07','\\':'\\','"':'"','/':'/'})[n]??n;
|
|
32
|
+
}else value+=ch;
|
|
33
|
+
}
|
|
34
|
+
if(!closed)failure('unterminated '+(delim==='/'?'regular expression':'string'));
|
|
35
|
+
push(delim==='/'?'regex':'string',value);continue;
|
|
36
|
+
}
|
|
37
|
+
const number=/^(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/.exec(source.slice(i));
|
|
38
|
+
if(number){push('number',Number(number[0]));i+=number[0].length;continue;}
|
|
39
|
+
const name=/^[A-Za-z_][A-Za-z0-9_]*/.exec(source.slice(i));
|
|
40
|
+
if(name){push('name',name[0]);i+=name[0].length;continue;}
|
|
41
|
+
const op=/^(?:\+\+|--|\+=|-=|\*=|\/=|%=|\^=|==|!=|<=|>=|!~|&&|\|\||>>|[{}()[\],;?:$+*\/%^<>=!~|\-])/.exec(source.slice(i));
|
|
42
|
+
if(!op)failure(`unexpected character ${JSON.stringify(c)}`);
|
|
43
|
+
push('op',op[0]);i+=op[0].length;
|
|
44
|
+
}
|
|
45
|
+
out.push({kind:'eof',value:'<eof>'});return out;
|
|
46
|
+
}
|
|
47
|
+
export function compileAwk(source) {
|
|
48
|
+
const tokens=lex(source);let i=0;
|
|
49
|
+
// Quoted strings and regex literals are operands even when their contents
|
|
50
|
+
// happen to spell an operator, separator, or keyword.
|
|
51
|
+
const peek=()=>['string','regex'].includes(tokens[i].kind)?null:tokens[i].value, take=()=>tokens[i++], eat=v=>peek()===v?(i++,true):false;
|
|
52
|
+
const need=v=>{if(!eat(v))failure(`expected ${v}, got ${peek()}`);};
|
|
53
|
+
const separators=()=>{while(eat(';'));};
|
|
54
|
+
const prec={'=':1,'+=':1,'-=':1,'*=':1,'/=':1,'%=':1,'^=':1,'?':2,'||':3,'&&':4,'in':5,'~':5,'!~':5,'==':6,'!=':6,'<':6,'>':6,'<=':6,'>=':6,'concat':7,'+':8,'-':8,'*':9,'/':9,'%':9,'^':11};
|
|
55
|
+
const begins=t=>['number','string','regex'].includes(t.kind)||t.kind==='name'&&!['in','else'].includes(t.value)||['(','$'].includes(t.value);
|
|
56
|
+
function expr(min=0,stopRedirect=false) {
|
|
57
|
+
let t=take(),node;
|
|
58
|
+
if(t.kind==='number'||t.kind==='string')node={type:'literal',value:t.value};
|
|
59
|
+
else if(t.kind==='regex')node={type:'regex',value:t.value};
|
|
60
|
+
else if(t.value==='('){const values=[expr()];while(eat(','))values.push(expr());need(')');node=values.length===1?values[0]:{type:'list',values};}
|
|
61
|
+
else if(['!','+','-','++','--','$'].includes(t.value))node={type:t.value==='$'?'field':'unary',op:t.value,value:expr(t.value==='$'?13:10)};
|
|
62
|
+
else if(t.value==='getline'){
|
|
63
|
+
let target=null;if(tokens[i].kind==='name'||peek()==='$')target=expr(12);
|
|
64
|
+
let file=null;if(eat('<'))file=expr(7);
|
|
65
|
+
node={type:'getline',target,file};
|
|
66
|
+
}
|
|
67
|
+
else if(t.kind==='name'){
|
|
68
|
+
node={type:'variable',name:t.value};
|
|
69
|
+
if(eat('(')){const args=[];if(peek()!==')')do{args.push(expr());}while(eat(','));need(')');node={type:'call',name:t.value,args};}
|
|
70
|
+
else if(eat('[')){const keys=[];do{keys.push(expr());}while(eat(','));need(']');node={type:'index',name:t.value,keys};}
|
|
71
|
+
}else failure(`expected expression, got ${t.value}`);
|
|
72
|
+
while(true){let op=peek();
|
|
73
|
+
if(['++','--'].includes(op)){if(12<min)break;i++;node={type:'postfix',op,value:node};continue;}
|
|
74
|
+
if(stopRedirect&&['>','>>','|'].includes(op))break;
|
|
75
|
+
let implicit=false;if(prec[op]===undefined&&begins(tokens[i])){op='concat';implicit=true;}
|
|
76
|
+
const p=prec[op];if(p===undefined||p<min)break;
|
|
77
|
+
if(!implicit)i++;
|
|
78
|
+
if(op==='?'){const yes=expr();need(':');node={type:'conditional',condition:node,yes,no:expr(p)};continue;}
|
|
79
|
+
const right=expr(p+(p===1||op==='^'?0:1),stopRedirect);node={type:p===1?'assign':'binary',op,left:node,right};
|
|
80
|
+
}
|
|
81
|
+
return node;
|
|
82
|
+
}
|
|
83
|
+
function statement(){separators();
|
|
84
|
+
if(eat('{')){const body=[];separators();while(peek()!=='}'){if(tokens[i].kind==='eof')failure('unterminated block');body.push(statement());separators();}need('}');return {type:'block',body};}
|
|
85
|
+
if(eat('if')){need('(');const condition=expr();need(')');const yes=statement();separators();return {type:'if',condition,yes,no:eat('else')?statement():null};}
|
|
86
|
+
if(eat('while')){need('(');const condition=expr();need(')');return {type:'while',condition,body:statement()};}
|
|
87
|
+
if(eat('do')){const body=statement();separators();need('while');need('(');const condition=expr();need(')');return {type:'do',body,condition};}
|
|
88
|
+
if(eat('for')){need('(');const initial=peek()===';'?null:expr();
|
|
89
|
+
if(initial?.type==='binary'&&initial.op==='in'&&eat(')'))return {type:'forin',initial,body:statement()};
|
|
90
|
+
need(';');const condition=peek()===';'?null:expr();need(';');const increment=peek()===')'?null:expr();need(')');return {type:'for',initial,condition,increment,body:statement()};}
|
|
91
|
+
if(['break','continue','next','nextfile','exit','return'].includes(peek())){const op=take().value;const value=['exit','return'].includes(op)&&![';','}','<eof>'].includes(peek())?expr():null;return {type:'flow',op,value};}
|
|
92
|
+
if(eat('delete'))return {type:'delete',value:expr(12)};
|
|
93
|
+
if(peek()==='print'||peek()==='printf'){
|
|
94
|
+
const op=take().value,args=[];
|
|
95
|
+
if(![';','}','<eof>','>','>>','|'].includes(peek()))do{const value=expr(0,true);args.push(...(value.type==='list'?value.values:[value]));}while(eat(','));
|
|
96
|
+
let redirect=null;if(['>','>>','|'].includes(peek()))redirect={op:take().value,file:expr()};
|
|
97
|
+
return {type:'print',op,args,redirect};
|
|
98
|
+
}
|
|
99
|
+
return {type:'expression',value:expr()};
|
|
100
|
+
}
|
|
101
|
+
const program={begin:[],end:[],rules:[],functions:new Map()};separators();
|
|
102
|
+
while(tokens[i].kind!=='eof'){
|
|
103
|
+
if(eat('function')){const name=take();if(name.kind!=='name')failure('expected function name');need('(');const params=[];if(peek()!==')')do{const p=take();if(p.kind!=='name')failure('expected parameter');params.push(p.value);}while(eat(','));need(')');program.functions.set(name.value,{params,body:statement()});}
|
|
104
|
+
else if(peek()==='BEGIN'||peek()==='END'){const kind=take().value.toLowerCase();separators();if(peek()!=='{')failure(`${kind} requires a block`);program[kind].push(statement());}
|
|
105
|
+
else {let pattern=null,until=null;if(peek()!=='{'){pattern=expr();if(eat(','))until=expr();}
|
|
106
|
+
const action=peek()==='{'?statement():{type:'print',op:'print',args:[]};program.rules.push({pattern,until,action});}
|
|
107
|
+
separators();
|
|
108
|
+
}
|
|
109
|
+
return program;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function executeAwk(program, inputs, options={}) {
|
|
113
|
+
const vars=new Map(Object.entries({FS:' ',OFS:' ',ORS:'\n',RS:'\n',SUBSEP:'\x1c',CONVFMT:'%.6g',OFMT:'%.6g',NR:0,FNR:0,NF:0,FILENAME:'',RSTART:0,RLENGTH:-1,...Object.fromEntries(Object.entries(options.variables??{}).map(([k,v])=>[k,new InputString(v)]))}));
|
|
114
|
+
const arrays=new Map(),locals=[];let record='',fields=[],output='',flow=null,status=0,seed=1,inputIndex=0,recordIndex=0,records=[];
|
|
115
|
+
const readers=new Map(),written=new Set(),ranges=new Map(),pipes=new Map();
|
|
116
|
+
let stderr='';
|
|
117
|
+
function closePipe(command) {
|
|
118
|
+
if(!pipes.has(command))return 0;
|
|
119
|
+
const text=pipes.get(command);pipes.delete(command);
|
|
120
|
+
if(!options.runPipe)failure('output pipes require a shell adapter');
|
|
121
|
+
const p=options.runPipe(command,text);output+=p.stdout??'';stderr+=p.stderr??'';
|
|
122
|
+
return p.status;
|
|
123
|
+
}
|
|
124
|
+
const array=name=>{if(locals.at(-1)?.get(name) instanceof Map)return locals.at(-1).get(name);if(!arrays.has(name))arrays.set(name,new Map());return arrays.get(name);};
|
|
125
|
+
array('ARGV').set('0','awk');(options.argv??[]).forEach((v,i)=>array('ARGV').set(String(i+1),v));vars.set('ARGC',(options.argv??[]).length+1);
|
|
126
|
+
for(const [k,v] of Object.entries(options.env??{}))array('ENVIRON').set(k,new InputString(v));
|
|
127
|
+
const get=name=>{if(locals.at(-1)?.has(name))return locals.at(-1).get(name);return vars.get(name)??unset;};
|
|
128
|
+
const set=(name,value)=>{if(name==='NF'){const n=Math.trunc(num(value));if(n<0)failure('negative NF');fields.length=n;for(let i=0;i<n;i++)fields[i]??=new InputString('');record=fields.map(str).join(str(get('OFS')));}
|
|
129
|
+
if(locals.at(-1)?.has(name)){locals.at(-1).set(name,value);return value;}vars.set(name,value);return value;};
|
|
130
|
+
function split(text,sep){text=str(text);sep=str(sep);if(sep===' ')return text.trim()?text.trim().split(/\s+/):[];if(sep==='')return [...text];return text===''?[]:text.split(sep.length===1?sep:regex(sep));}
|
|
131
|
+
function setRecord(text){record=str(text);fields=split(record,get('FS')).map(x=>new InputString(x));vars.set('NF',fields.length);}
|
|
132
|
+
function ref(node){if(node.type==='variable')return {get:()=>get(node.name),set:v=>set(node.name,v)};
|
|
133
|
+
if(node.type==='index'){const a=array(node.name),k=node.keys.map(x=>str(evaluate(x))).join(str(get('SUBSEP')));return {get:()=>a.get(k)??unset,set:v=>(a.set(k,v),v),remove:()=>a.delete(k)};}
|
|
134
|
+
if(node.type==='field'){const n=Math.trunc(num(evaluate(node.value)));if(n<0)failure('negative field');return {get:()=>n===0?new InputString(record):fields[n-1]??new InputString(''),set:v=>{if(n===0)setRecord(v);else{while(fields.length<n)fields.push(new InputString(''));fields[n-1]=v;vars.set('NF',fields.length);record=fields.map(str).join(str(get('OFS')));}return v;}};}
|
|
135
|
+
failure('expression is not assignable');}
|
|
136
|
+
const pattern=node=>node.type==='regex'?node.value:str(evaluate(node));
|
|
137
|
+
function sprintf(format,args){let at=0;return str(format).replace(/%([-+ #0]*)(\d*)(?:\.(\d+))?([%csdiouxXeEfFgG])/g,(all,flags,width,precision,kind)=>{
|
|
138
|
+
if(kind==='%')return '%';const value=args[at++];let s;
|
|
139
|
+
if(kind==='s')s=str(value).slice(0,precision===undefined?undefined:Number(precision));
|
|
140
|
+
else if(kind==='c')s=numeric(value)?String.fromCharCode(num(value)):str(value).slice(0,1);
|
|
141
|
+
else if(/[diouxX]/.test(kind)){let n=Math.trunc(num(value));if(kind==='u')n>>>=0;s=n.toString(kind==='o'?8:/[xX]/.test(kind)?16:10);if(kind==='X')s=s.toUpperCase();}
|
|
142
|
+
else {const n=num(value),p=precision===undefined?6:Number(precision);s=/f/i.test(kind)?n.toFixed(p):/e/i.test(kind)?n.toExponential(p):String(Number(n.toPrecision(p||1)));if(kind===kind.toUpperCase())s=s.toUpperCase();}
|
|
143
|
+
if(flags.includes('+')&&num(value)>=0&&kind!=='s')s='+'+s;
|
|
144
|
+
return flags.includes('-')?s.padEnd(Number(width),' '):s.padStart(Number(width),flags.includes('0')?'0':' ');
|
|
145
|
+
});}
|
|
146
|
+
function nextRecord(){while(recordIndex>=records.length){if(inputIndex>=inputs.length)return null;const entry=inputs[inputIndex++];if(entry.assignment){set(entry.assignment[0],new InputString(entry.assignment[1]));continue;}vars.set('FILENAME',entry.name);vars.set('FNR',0);const text=entry.text;const rs=str(get('RS'));records=rs===''?text.replace(/^\n+|\n+$/g,'').split(/\n\n+/):text.split(rs.length===1?rs:regex(rs));if(records.at(-1)==='')records.pop();recordIndex=0;}
|
|
147
|
+
vars.set('NR',num(get('NR'))+1);vars.set('FNR',num(get('FNR'))+1);return records[recordIndex++];}
|
|
148
|
+
function call(node){const {name,args}=node;
|
|
149
|
+
if(name==='split'){const values=split(evaluate(args[0]),args[2]?pattern(args[2]):get('FS'));if(args[1]?.type!=='variable')failure('split requires array name');const a=array(args[1].name);a.clear();values.forEach((v,i)=>a.set(String(i+1),new InputString(v)));return values.length;}
|
|
150
|
+
if(name==='sub'||name==='gsub'){const re=regex(pattern(args[0]),name==='gsub'?'g':'');const replacement=str(evaluate(args[1]));const target=args[2]?ref(args[2]):{get:()=>record,set:setRecord};let count=0;
|
|
151
|
+
const text=str(target.get()).replace(re,match=>{count++;let s='';for(let i=0;i<replacement.length;i++){if(replacement[i]==='\\'&&i+1<replacement.length&&['&','\\'].includes(replacement[i+1]))s+=replacement[++i];else s+=replacement[i]==='&'?match:replacement[i];}return s;});target.set(text);return count;}
|
|
152
|
+
if(name==='match'){const s=str(evaluate(args[0])),m=regex(pattern(args[1])).exec(s);set('RSTART',m?m.index+1:0);set('RLENGTH',m?m[0].length:-1);return get('RSTART');}
|
|
153
|
+
if(program.functions.has(name)){const fn=program.functions.get(name),scope=new Map();fn.params.forEach((p,i)=>scope.set(p,args[i]?.type==='variable'&&!locals.at(-1)?.has(args[i].name)&&arrays.has(args[i].name)?array(args[i].name):args[i]?evaluate(args[i]):unset));locals.push(scope);let value='';try{run(fn.body);if(flow?.op==='return'){value=flow.value;flow=null;}}finally{locals.pop();}return value;}
|
|
154
|
+
if(name==='length'&&args[0]?.type==='variable'&&arrays.has(args[0].name))return array(args[0].name).size;
|
|
155
|
+
const a=args.map(evaluate);
|
|
156
|
+
switch(name){
|
|
157
|
+
case 'length':return str(a.length?a[0]:record).length;
|
|
158
|
+
case 'substr':return str(a[0]).substr(Math.max(0,num(a[1])-1),a.length>2?Math.max(0,num(a[2])):undefined);
|
|
159
|
+
case 'index':return str(a[0]).indexOf(str(a[1]))+1;
|
|
160
|
+
case 'sprintf':return sprintf(a[0],a.slice(1));
|
|
161
|
+
case 'tolower':return str(a[0]).toLowerCase();case 'toupper':return str(a[0]).toUpperCase();
|
|
162
|
+
case 'int':return Math.trunc(num(a[0]));case 'sqrt':return Math.sqrt(num(a[0]));case 'log':return Math.log(num(a[0]));case 'exp':return Math.exp(num(a[0]));case 'sin':return Math.sin(num(a[0]));case 'cos':return Math.cos(num(a[0]));case 'atan2':return Math.atan2(num(a[0]),num(a[1]));
|
|
163
|
+
case 'srand':{const old=seed;seed=a.length?num(a[0]):Date.now();return old;}case 'rand':seed=(1664525*seed+1013904223)>>>0;return seed/4294967296;
|
|
164
|
+
case 'close':{const name=str(a[0]);readers.delete(name);written.delete(name);return closePipe(name);}
|
|
165
|
+
default:failure(`unsupported function ${name}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function evaluate(n){switch(n.type){
|
|
169
|
+
case 'literal':return n.value;case 'regex':return Number(regex(n.value).test(record));
|
|
170
|
+
case 'variable':if(n.name==='length')return record.length;return get(n.name);
|
|
171
|
+
case 'index':case 'field':return ref(n).get();case 'call':return call(n);
|
|
172
|
+
case 'conditional':return evaluate(truth(evaluate(n.condition))?n.yes:n.no);
|
|
173
|
+
case 'getline':{let text;if(n.file){const path=str(evaluate(n.file));try{if(!readers.has(path))readers.set(path,{lines:options.readFile(path).split('\n'),i:0});const r=readers.get(path);if(r.lines.at(-1)==='')r.lines.pop();text=r.i<r.lines.length?r.lines[r.i++]:null;}catch{return -1;}}else text=nextRecord();if(text===null)return 0;if(n.target)ref(n.target).set(new InputString(text));else setRecord(text);return 1;}
|
|
174
|
+
case 'unary':case 'postfix':{if(n.op==='++'||n.op==='--'){const r=ref(n.value),v=num(r.get()),next=v+(n.op==='++'?1:-1);r.set(next);return n.type==='postfix'?v:next;}const v=evaluate(n.value);return n.op==='!'?Number(!truth(v)):n.op==='-'?-num(v):num(v);}
|
|
175
|
+
case 'assign':{const r=ref(n.left),v=evaluate(n.right);return r.set(n.op==='='?v:arithmetic(n.op[0],r.get(),v));}
|
|
176
|
+
case 'binary':{const a=evaluate(n.left);if(n.op==='&&')return Number(truth(a)&&truth(evaluate(n.right)));if(n.op==='||')return Number(truth(a)||truth(evaluate(n.right)));if(n.op==='in'){if(n.right.type!=='variable')failure('in requires an array');return Number(array(n.right.name).has(str(a)));}
|
|
177
|
+
if(n.op==='~'||n.op==='!~'){const match=regex(pattern(n.right)).test(str(a));return Number(n.op==='~'?match:!match);}
|
|
178
|
+
const b=evaluate(n.right);if(n.op==='concat')return str(a)+str(b);
|
|
179
|
+
if(['==','!=','<','>','<=','>='].includes(n.op)){const x=numeric(a)&&numeric(b)?num(a):str(a),y=numeric(a)&&numeric(b)?num(b):str(b);return Number(n.op==='=='?x===y:n.op==='!='?x!==y:n.op==='<'?x<y:n.op==='>'?x>y:n.op==='<='?x<=y:x>=y);}
|
|
180
|
+
return arithmetic(n.op,a,b);}
|
|
181
|
+
}failure(`unsupported expression ${n.type}`);}
|
|
182
|
+
function arithmetic(op,a,b){a=num(a);b=num(b);if((op==='/'||op==='%')&&b===0)failure('division by zero');return op==='+'?a+b:op==='-'?a-b:op==='*'?a*b:op==='/'?a/b:op==='%'?a%b:op==='^'?a**b:failure(`unsupported operator ${op}`);}
|
|
183
|
+
function run(n){if(flow)return;switch(n.type){
|
|
184
|
+
case 'block':for(const child of n.body){run(child);if(flow)break;}break;
|
|
185
|
+
case 'expression':evaluate(n.value);break;
|
|
186
|
+
case 'if':if(truth(evaluate(n.condition)))run(n.yes);else if(n.no)run(n.no);break;
|
|
187
|
+
case 'print':{const values=n.args.map(evaluate);const text=n.op==='printf'?sprintf(values[0],values.slice(1)):(values.length?values.map(str).join(str(get('OFS'))):record)+str(get('ORS'));
|
|
188
|
+
if(n.redirect){const file=str(evaluate(n.redirect.file));if(n.redirect.op==='|')pipes.set(file,(pipes.get(file)??'')+text);else {options.writeFile(file,text,n.redirect.op==='>>'||written.has(file));written.add(file);}}else output+=text;break;}
|
|
189
|
+
case 'delete':if(n.value.type==='variable')array(n.value.name).clear();else {const r=ref(n.value);if(!r.remove)failure('delete requires an array');r.remove();}break;
|
|
190
|
+
case 'flow':flow={op:n.op,value:n.value?evaluate(n.value):0};if(n.op==='exit')status=num(flow.value);break;
|
|
191
|
+
case 'while':case 'do':case 'for':case 'forin':{
|
|
192
|
+
if(n.type==='for'&&n.initial)evaluate(n.initial);
|
|
193
|
+
let keys;if(n.type==='forin'){if(n.initial.right.type!=='variable')failure('for-in requires array');keys=[...array(n.initial.right.name).keys()];}
|
|
194
|
+
let first=true,j=0;while(n.type==='forin'?j<keys.length:n.type==='do'&&first||!n.condition||truth(evaluate(n.condition))){first=false;if(keys)ref(n.initial.left).set(new InputString(keys[j++]));run(n.body);if(flow){if(flow.op==='break'){flow=null;break;}if(flow.op==='continue')flow=null;else break;}if(n.type==='for'&&n.increment)evaluate(n.increment);}break;}
|
|
195
|
+
default:failure(`unsupported statement ${n.type}`);
|
|
196
|
+
}}
|
|
197
|
+
for(const b of program.begin){run(b);if(flow)break;}
|
|
198
|
+
if(flow&&flow.op!=='exit')failure(`${flow.op} outside a record`);
|
|
199
|
+
if(!flow&&program.rules.length){let text;while((text=nextRecord())!==null){setRecord(text);for(const rule of program.rules){let yes=!rule.pattern||truth(evaluate(rule.pattern));if(rule.until){yes=ranges.get(rule)||yes;if(yes)ranges.set(rule,!truth(evaluate(rule.until)));}if(yes)run(rule.action);if(flow)break;}if(flow?.op==='exit')break;if(flow&& !['next','nextfile'].includes(flow.op))failure(`${flow.op} outside its context`);if(flow?.op==='nextfile')recordIndex=records.length;flow=null;}}
|
|
200
|
+
flow=null;for(const b of program.end){run(b);if(flow)break;}
|
|
201
|
+
for(const command of pipes.keys())closePipe(command);
|
|
202
|
+
return stderr?{status,stdout:output,stderr}:{status,stdout:output};
|
|
203
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Adapted from jsmdcui/src/index.js diffgutter (Myers bisect and merge cleanup).
|
|
3
|
+
|
|
4
|
+
MIT License
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2016-2020: Zachary Yedidia, et al.
|
|
7
|
+
Copyright (c) 2026 Dr. John (醫者小智)
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const _DIFF_DELETE = -1, _DIFF_INSERT = 1, _DIFF_EQUAL = 0;
|
|
29
|
+
|
|
30
|
+
function _arrEq(a, b) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false; return true; }
|
|
31
|
+
function _commonPrefix(a, b) { const n = Math.min(a.length, b.length); let i = 0; while (i < n && a[i] === b[i]) i++; return i; }
|
|
32
|
+
function _commonSuffix(a, b) { const n = Math.min(a.length, b.length); let i = 0; while (i < n && a[a.length-1-i] === b[b.length-1-i]) i++; return i; }
|
|
33
|
+
function _indexOfSub(longArr, shortArr) { const L = longArr.length, S = shortArr.length; if (S === 0) return 0; outer: for (let i = 0; i + S <= L; i++) { for (let j = 0; j < S; j++) if (longArr[i+j] !== shortArr[j]) continue outer; return i; } return -1; }
|
|
34
|
+
function _hasSuffix(seq, suf) { if (suf.length > seq.length) return false; for (let i = 0; i < suf.length; i++) if (seq[seq.length-suf.length+i] !== suf[i]) return false; return true; }
|
|
35
|
+
function _hasPrefix(seq, pre) { if (pre.length > seq.length) return false; for (let i = 0; i < pre.length; i++) if (seq[i] !== pre[i]) return false; return true; }
|
|
36
|
+
|
|
37
|
+
function _diffMain(a, b) {
|
|
38
|
+
if (_arrEq(a, b)) return a.length ? [{ op: _DIFF_EQUAL, seq: a.slice() }] : [];
|
|
39
|
+
const pre = _commonPrefix(a, b);
|
|
40
|
+
const commonprefix = a.slice(0, pre);
|
|
41
|
+
a = a.slice(pre); b = b.slice(pre);
|
|
42
|
+
const suf = _commonSuffix(a, b);
|
|
43
|
+
const commonsuffix = a.slice(a.length - suf);
|
|
44
|
+
a = a.slice(0, a.length - suf); b = b.slice(0, b.length - suf);
|
|
45
|
+
const diffs = _diffCompute(a, b);
|
|
46
|
+
if (commonprefix.length) diffs.unshift({ op: _DIFF_EQUAL, seq: commonprefix });
|
|
47
|
+
if (commonsuffix.length) diffs.push({ op: _DIFF_EQUAL, seq: commonsuffix });
|
|
48
|
+
return _diffCleanupMerge(diffs);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function _diffCompute(a, b) {
|
|
52
|
+
if (a.length === 0) return [{ op: _DIFF_INSERT, seq: b.slice() }];
|
|
53
|
+
if (b.length === 0) return [{ op: _DIFF_DELETE, seq: a.slice() }];
|
|
54
|
+
const aLonger = a.length > b.length;
|
|
55
|
+
const long = aLonger ? a : b, short = aLonger ? b : a;
|
|
56
|
+
const idx = _indexOfSub(long, short);
|
|
57
|
+
if (idx !== -1) {
|
|
58
|
+
const op = aLonger ? _DIFF_DELETE : _DIFF_INSERT;
|
|
59
|
+
return [
|
|
60
|
+
{ op, seq: long.slice(0, idx) },
|
|
61
|
+
{ op: _DIFF_EQUAL, seq: short.slice() },
|
|
62
|
+
{ op, seq: long.slice(idx + short.length) },
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
if (short.length === 1) return [{ op: _DIFF_DELETE, seq: a.slice() }, { op: _DIFF_INSERT, seq: b.slice() }];
|
|
66
|
+
return _diffBisect(a, b);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function _diffBisect(a, b) {
|
|
70
|
+
const n = a.length, m = b.length;
|
|
71
|
+
const maxD = Math.ceil((n + m) / 2);
|
|
72
|
+
const vOffset = maxD, vLength = 2 * maxD;
|
|
73
|
+
const v1 = new Int32Array(vLength).fill(-1);
|
|
74
|
+
const v2 = new Int32Array(vLength).fill(-1);
|
|
75
|
+
v1[vOffset + 1] = 0; v2[vOffset + 1] = 0;
|
|
76
|
+
const delta = n - m;
|
|
77
|
+
const front = (delta % 2 !== 0);
|
|
78
|
+
let k1start = 0, k1end = 0, k2start = 0, k2end = 0;
|
|
79
|
+
for (let d = 0; d < maxD; d++) {
|
|
80
|
+
for (let k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
|
|
81
|
+
const k1Offset = vOffset + k1;
|
|
82
|
+
let x1;
|
|
83
|
+
if (k1 === -d || (k1 !== d && v1[k1Offset-1] < v1[k1Offset+1])) x1 = v1[k1Offset+1];
|
|
84
|
+
else x1 = v1[k1Offset-1] + 1;
|
|
85
|
+
let y1 = x1 - k1;
|
|
86
|
+
while (x1 < n && y1 < m && a[x1] === b[y1]) { x1++; y1++; }
|
|
87
|
+
v1[k1Offset] = x1;
|
|
88
|
+
if (x1 > n) k1end += 2;
|
|
89
|
+
else if (y1 > m) k1start += 2;
|
|
90
|
+
else if (front) {
|
|
91
|
+
const k2Offset = vOffset + delta - k1;
|
|
92
|
+
if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
|
|
93
|
+
const x2 = n - v2[k2Offset];
|
|
94
|
+
if (x1 >= x2) return _diffBisectSplit(a, b, x1, y1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (let k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
|
|
99
|
+
const k2Offset = vOffset + k2;
|
|
100
|
+
let x2;
|
|
101
|
+
if (k2 === -d || (k2 !== d && v2[k2Offset-1] < v2[k2Offset+1])) x2 = v2[k2Offset+1];
|
|
102
|
+
else x2 = v2[k2Offset-1] + 1;
|
|
103
|
+
let y2 = x2 - k2;
|
|
104
|
+
while (x2 < n && y2 < m && a[n-x2-1] === b[m-y2-1]) { x2++; y2++; }
|
|
105
|
+
v2[k2Offset] = x2;
|
|
106
|
+
if (x2 > n) k2end += 2;
|
|
107
|
+
else if (y2 > m) k2start += 2;
|
|
108
|
+
else if (!front) {
|
|
109
|
+
const k1Offset = vOffset + delta - k2;
|
|
110
|
+
if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
|
|
111
|
+
const x1 = v1[k1Offset];
|
|
112
|
+
const y1 = vOffset + x1 - k1Offset;
|
|
113
|
+
const x2real = n - x2;
|
|
114
|
+
if (x1 >= x2real) return _diffBisectSplit(a, b, x1, y1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return [{ op: _DIFF_DELETE, seq: a.slice() }, { op: _DIFF_INSERT, seq: b.slice() }];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function _diffBisectSplit(a, b, x, y) {
|
|
123
|
+
return _diffMain(a.slice(0, x), b.slice(0, y)).concat(_diffMain(a.slice(x), b.slice(y)));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function _diffCleanupMerge(diffs) {
|
|
127
|
+
diffs.push({ op: _DIFF_EQUAL, seq: [] });
|
|
128
|
+
let pointer = 0, countDelete = 0, countInsert = 0, textDelete = [], textInsert = [];
|
|
129
|
+
while (pointer < diffs.length) {
|
|
130
|
+
const d = diffs[pointer];
|
|
131
|
+
if (d.op === _DIFF_INSERT) { countInsert++; textInsert = textInsert.concat(d.seq); pointer++; }
|
|
132
|
+
else if (d.op === _DIFF_DELETE) { countDelete++; textDelete = textDelete.concat(d.seq); pointer++; }
|
|
133
|
+
else {
|
|
134
|
+
if (countDelete + countInsert > 1) {
|
|
135
|
+
if (countDelete !== 0 && countInsert !== 0) {
|
|
136
|
+
let cl = _commonPrefix(textInsert, textDelete);
|
|
137
|
+
if (cl !== 0) {
|
|
138
|
+
const x = pointer - countDelete - countInsert;
|
|
139
|
+
if (x > 0 && diffs[x-1].op === _DIFF_EQUAL) diffs[x-1].seq = diffs[x-1].seq.concat(textInsert.slice(0, cl));
|
|
140
|
+
else { diffs.unshift({ op: _DIFF_EQUAL, seq: textInsert.slice(0, cl) }); pointer++; }
|
|
141
|
+
textInsert = textInsert.slice(cl); textDelete = textDelete.slice(cl);
|
|
142
|
+
}
|
|
143
|
+
cl = _commonSuffix(textInsert, textDelete);
|
|
144
|
+
if (cl !== 0) {
|
|
145
|
+
const insIdx = textInsert.length - cl;
|
|
146
|
+
diffs[pointer].seq = textInsert.slice(insIdx).concat(diffs[pointer].seq);
|
|
147
|
+
textInsert = textInsert.slice(0, insIdx); textDelete = textDelete.slice(0, textDelete.length - cl);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const repl = [];
|
|
151
|
+
if (textDelete.length) repl.push({ op: _DIFF_DELETE, seq: textDelete });
|
|
152
|
+
if (textInsert.length) repl.push({ op: _DIFF_INSERT, seq: textInsert });
|
|
153
|
+
const start = pointer - countDelete - countInsert;
|
|
154
|
+
diffs.splice(start, countDelete + countInsert, ...repl);
|
|
155
|
+
pointer = start + repl.length + 1;
|
|
156
|
+
} else if (pointer !== 0 && diffs[pointer-1].op === _DIFF_EQUAL) {
|
|
157
|
+
diffs[pointer-1].seq = diffs[pointer-1].seq.concat(d.seq);
|
|
158
|
+
diffs.splice(pointer, 1);
|
|
159
|
+
} else pointer++;
|
|
160
|
+
countInsert = 0; countDelete = 0; textDelete = []; textInsert = [];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (diffs[diffs.length-1].seq.length === 0) diffs.pop();
|
|
164
|
+
|
|
165
|
+
// Second pass: shift a single edit sandwiched between two equalities sideways.
|
|
166
|
+
let changes = false;
|
|
167
|
+
pointer = 1;
|
|
168
|
+
while (pointer < diffs.length - 1) {
|
|
169
|
+
const prev = diffs[pointer-1], cur = diffs[pointer], next = diffs[pointer+1];
|
|
170
|
+
if (prev.op === _DIFF_EQUAL && next.op === _DIFF_EQUAL) {
|
|
171
|
+
if (_hasSuffix(cur.seq, prev.seq)) {
|
|
172
|
+
if (prev.seq.length) {
|
|
173
|
+
cur.seq = prev.seq.concat(cur.seq.slice(0, cur.seq.length - prev.seq.length));
|
|
174
|
+
next.seq = prev.seq.concat(next.seq);
|
|
175
|
+
}
|
|
176
|
+
diffs.splice(pointer-1, 1); changes = true;
|
|
177
|
+
} else if (_hasPrefix(cur.seq, next.seq)) {
|
|
178
|
+
prev.seq = prev.seq.concat(next.seq);
|
|
179
|
+
cur.seq = cur.seq.slice(next.seq.length).concat(next.seq);
|
|
180
|
+
diffs.splice(pointer+1, 1); changes = true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
pointer++;
|
|
184
|
+
}
|
|
185
|
+
if (changes) return _diffCleanupMerge(diffs);
|
|
186
|
+
return diffs;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function normalFileDiff(left, right) {
|
|
190
|
+
// Keep the terminator as part of line identity, including a missing final LF.
|
|
191
|
+
const lines = text => text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
192
|
+
const a = lines(left), b = lines(right);
|
|
193
|
+
const operations = _diffMain(a, b);
|
|
194
|
+
const range = (first, last) => first === last ? String(first) : `${first},${last}`;
|
|
195
|
+
const render = (values, prefix) => values.map(line =>
|
|
196
|
+
`${prefix} ${line}${line.endsWith("\n") ? "" : "\n\\n"}`
|
|
197
|
+
).join("");
|
|
198
|
+
let oldLine = 0, newLine = 0, output = "";
|
|
199
|
+
for (let i = 0; i < operations.length;) {
|
|
200
|
+
if (operations[i].op === _DIFF_EQUAL) {
|
|
201
|
+
oldLine += operations[i].seq.length;
|
|
202
|
+
newLine += operations[i++].seq.length;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const oldStart = oldLine, newStart = newLine;
|
|
206
|
+
let removed = [], added = [];
|
|
207
|
+
while (i < operations.length && operations[i].op !== _DIFF_EQUAL) {
|
|
208
|
+
const change = operations[i++];
|
|
209
|
+
if (change.op === _DIFF_DELETE) {
|
|
210
|
+
removed = removed.concat(change.seq);
|
|
211
|
+
oldLine += change.seq.length;
|
|
212
|
+
} else {
|
|
213
|
+
added = added.concat(change.seq);
|
|
214
|
+
newLine += change.seq.length;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (!removed.length) output += `${oldStart}a${range(newStart + 1, newLine)}\n${render(added, ">")}`;
|
|
218
|
+
else if (!added.length) output += `${range(oldStart + 1, oldLine)}d${newStart}\n${render(removed, "<")}`;
|
|
219
|
+
else output += `${range(oldStart + 1, oldLine)}c${range(newStart + 1, newLine)}\n${render(removed, "<")}---\n${render(added, ">")}`;
|
|
220
|
+
}
|
|
221
|
+
return output;
|
|
222
|
+
}
|
|
@@ -43,8 +43,8 @@ function classifySuffix(stats) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function displayEntry(entry, options) {
|
|
46
|
-
const suffix = options.classify ? classifySuffix(entry.stats) : entry.stats.isDirectory() ? "/" : "";
|
|
47
|
-
return `${iconFor(entry.name, entry.stats, entry.path)} ${entry.name}${suffix}`;
|
|
46
|
+
const suffix = options.classify ? classifySuffix(entry.stats) : !options.plain && entry.stats.isDirectory() ? "/" : "";
|
|
47
|
+
return `${options.inode ? `${entry.stats.ino} ` : ""}${options.plain ? "" : `${iconFor(entry.name, entry.stats, entry.path)} `}${entry.name}${suffix}`;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function columns(items, width) {
|
|
@@ -98,7 +98,7 @@ function listDirectory(path, options) {
|
|
|
98
98
|
.filter((entry) => options.all || options.almostAll || !entry.name.startsWith("."))
|
|
99
99
|
.map((entry) => {
|
|
100
100
|
const entryPath = resolve(path, entry.name);
|
|
101
|
-
return { name: entry.name, stats: lstatSync(entryPath), path: entryPath };
|
|
101
|
+
return { name: entry.name, stats: options.dereference ? statSync(entryPath) : lstatSync(entryPath), path: entryPath };
|
|
102
102
|
});
|
|
103
103
|
if (options.all) entries = [
|
|
104
104
|
{ name: ".", stats: lstatSync(path), path },
|
|
@@ -116,6 +116,7 @@ function listDirectory(path, options) {
|
|
|
116
116
|
|
|
117
117
|
export function fancyLs(argv, state, terminal = Boolean(process.stdout.isTTY)) {
|
|
118
118
|
const options = {
|
|
119
|
+
plain: argv[0] === "ls" && Boolean(state.shellOptions?.posix),
|
|
119
120
|
all: false, almostAll: false, directory: false, long: false, human: false,
|
|
120
121
|
recursive: false, time: false, reverse: false, size: false, one: false, classify: false,
|
|
121
122
|
};
|
|
@@ -128,6 +129,8 @@ export function fancyLs(argv, state, terminal = Boolean(process.stdout.isTTY)) {
|
|
|
128
129
|
for (const flag of argument.slice(1)) {
|
|
129
130
|
if (flag === "a") options.all = true;
|
|
130
131
|
else if (flag === "A") options.almostAll = true;
|
|
132
|
+
else if (flag === "L") options.dereference = true;
|
|
133
|
+
else if (flag === "i") options.inode = true;
|
|
131
134
|
else if (flag === "d") options.directory = true;
|
|
132
135
|
else if (flag === "l") options.long = true;
|
|
133
136
|
else if (flag === "h") options.human = true;
|
|
@@ -148,7 +151,7 @@ export function fancyLs(argv, state, terminal = Boolean(process.stdout.isTTY)) {
|
|
|
148
151
|
const show = (operand, heading = operands.length > 1, separate = true) => {
|
|
149
152
|
const path = isAbsolute(operand) ? operand : resolve(state.cwd, operand);
|
|
150
153
|
try {
|
|
151
|
-
const stats = lstatSync(path);
|
|
154
|
+
const stats = options.dereference ? statSync(path) : lstatSync(path);
|
|
152
155
|
let listingDirectory = stats.isDirectory() && !options.directory;
|
|
153
156
|
// GNU/POSIX-style ls follows a command-line symlink to a directory for
|
|
154
157
|
// an ordinary listing and for -R. -d, -l, and -F request information
|
|
@@ -196,6 +199,14 @@ export function fancyLs(argv, state, terminal = Boolean(process.stdout.isTTY)) {
|
|
|
196
199
|
stderr += `bunmsh: ${argv[0]}: ${operand}: ${error.message}\n`;
|
|
197
200
|
}
|
|
198
201
|
};
|
|
202
|
+
if (options.time || options.size) operands.sort((a,b) => {
|
|
203
|
+
try {
|
|
204
|
+
const stat = options.dereference ? statSync : lstatSync;
|
|
205
|
+
const left=stat(resolve(state.cwd,a)), right=stat(resolve(state.cwd,b));
|
|
206
|
+
const order=(options.size ? right.size-left.size : right.mtimeMs-left.mtimeMs) || a.localeCompare(b);
|
|
207
|
+
return options.reverse ? -order : order;
|
|
208
|
+
} catch { return 0; }
|
|
209
|
+
});
|
|
199
210
|
for (const operand of operands) show(operand);
|
|
200
211
|
return { status, stdout, stderr };
|
|
201
212
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
createState,
|
|
7
7
|
execute,
|
|
8
8
|
executeArgv,
|
|
9
|
+
finishShell,
|
|
9
10
|
executeBunShellFallback,
|
|
10
11
|
isJavaScriptMode,
|
|
11
12
|
needsMoreInput,
|
|
@@ -440,6 +441,10 @@ async function interactive(state) {
|
|
|
440
441
|
}
|
|
441
442
|
};
|
|
442
443
|
const interrupt = () => {
|
|
444
|
+
if (Object.hasOwn(state.traps,"SIGINT")) {
|
|
445
|
+
if (state.traps.SIGINT && !state.pendingTraps.includes("SIGINT")) state.pendingTraps.push("SIGINT");
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
443
448
|
state.lastStatus = 130;
|
|
444
449
|
if (foregroundCommand || promptAbort?.signal.aborted) return;
|
|
445
450
|
process.stdout.write("\n");
|
|
@@ -456,6 +461,7 @@ async function interactive(state) {
|
|
|
456
461
|
// periodic autosave.
|
|
457
462
|
const SIGNAL_EXIT_STATUS = { SIGHUP: 129, SIGTERM: 143 };
|
|
458
463
|
const terminate = (signal) => {
|
|
464
|
+
if (Object.hasOwn(state.traps,signal)) return;
|
|
459
465
|
state.exitRequested = true;
|
|
460
466
|
state.exitStatus = SIGNAL_EXIT_STATUS[signal] ?? state.exitStatus;
|
|
461
467
|
if (foregroundCommand) {
|
|
@@ -725,7 +731,7 @@ async function interactive(state) {
|
|
|
725
731
|
process.off("SIGHUP", terminate);
|
|
726
732
|
readline.close();
|
|
727
733
|
}
|
|
728
|
-
return state.exitRequested ? state.exitStatus : state.lastStatus;
|
|
734
|
+
return (await finishShell(state,state.exitRequested ? state.exitStatus : state.lastStatus)).status;
|
|
729
735
|
}
|
|
730
736
|
|
|
731
737
|
async function main(argv) {
|
|
@@ -736,6 +742,7 @@ async function main(argv) {
|
|
|
736
742
|
let forceInteractive = false;
|
|
737
743
|
let forceMouse = false;
|
|
738
744
|
let builtinOnly = false;
|
|
745
|
+
const shellOptions = {};
|
|
739
746
|
let i = 0;
|
|
740
747
|
for (; i < argv.length; i++) {
|
|
741
748
|
const arg = argv[i];
|
|
@@ -785,6 +792,10 @@ async function main(argv) {
|
|
|
785
792
|
await printChangelog();
|
|
786
793
|
return 0;
|
|
787
794
|
}
|
|
795
|
+
if (/^-[eufxv]+$/.test(arg)) {
|
|
796
|
+
for (const key of arg.slice(1)) shellOptions[key]=true;
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
788
799
|
if (arg.startsWith("-")) {
|
|
789
800
|
console.error(`bunmsh: ${arg}: unknown option`);
|
|
790
801
|
return 2;
|
|
@@ -801,7 +812,7 @@ async function main(argv) {
|
|
|
801
812
|
if (forceMouse) state.mouseTracking = true;
|
|
802
813
|
if (builtinOnly) state.pathSearch = false;
|
|
803
814
|
const result = await executeArgv(forwardedArgv, state);
|
|
804
|
-
return state.exitRequested ? state.exitStatus : result.status;
|
|
815
|
+
return (await finishShell(state,state.exitRequested ? state.exitStatus : result.status)).status;
|
|
805
816
|
}
|
|
806
817
|
|
|
807
818
|
if (command !== null) {
|
|
@@ -813,8 +824,10 @@ async function main(argv) {
|
|
|
813
824
|
mouseTracking: forceMouse || undefined,
|
|
814
825
|
pathSearch: builtinOnly ? false : undefined,
|
|
815
826
|
});
|
|
827
|
+
Object.assign(state.shellOptions,shellOptions);
|
|
828
|
+
if (state.shellOptions.v) console.error(command);
|
|
816
829
|
const result = await execute(command, state);
|
|
817
|
-
return state.exitRequested ? state.exitStatus : result.status;
|
|
830
|
+
return (await finishShell(state,state.exitRequested ? state.exitStatus : result.status)).status;
|
|
818
831
|
}
|
|
819
832
|
|
|
820
833
|
if (i < argv.length) {
|
|
@@ -826,9 +839,11 @@ async function main(argv) {
|
|
|
826
839
|
mouseTracking: forceMouse || undefined,
|
|
827
840
|
pathSearch: builtinOnly ? false : undefined,
|
|
828
841
|
});
|
|
842
|
+
Object.assign(state.shellOptions,shellOptions);
|
|
843
|
+
if (state.shellOptions.v) console.error(source);
|
|
829
844
|
const result = await execute(source, state);
|
|
830
845
|
if (forceInteractive && !state.exitRequested) return interactive(state);
|
|
831
|
-
return state.exitRequested ? state.exitStatus : result.status;
|
|
846
|
+
return (await finishShell(state,state.exitRequested ? state.exitStatus : result.status)).status;
|
|
832
847
|
} catch (error) {
|
|
833
848
|
console.error(`bunmsh: ${script}: ${error.message}`);
|
|
834
849
|
return 1;
|
|
@@ -839,10 +854,11 @@ async function main(argv) {
|
|
|
839
854
|
mouseTracking: forceMouse || undefined,
|
|
840
855
|
pathSearch: builtinOnly ? false : undefined,
|
|
841
856
|
});
|
|
857
|
+
Object.assign(state.shellOptions,shellOptions);
|
|
842
858
|
if (forceInteractive || process.stdin.isTTY) return interactive(state);
|
|
843
859
|
const source = await Bun.stdin.text();
|
|
844
860
|
const result = await execute(source, state);
|
|
845
|
-
return state.exitRequested ? state.exitStatus : result.status;
|
|
861
|
+
return (await finishShell(state,state.exitRequested ? state.exitStatus : result.status)).status;
|
|
846
862
|
}
|
|
847
863
|
|
|
848
864
|
// Lets --define process.env.SERVE_AUTO_OPEN=/index.html, =off, or =yes
|