diff --git a/extra/factor-shell/authors.txt b/extra/factor-shell/authors.txt new file mode 100644 index 0000000000..7c1b2f2279 --- /dev/null +++ b/extra/factor-shell/authors.txt @@ -0,0 +1 @@ +Doug Coleman diff --git a/extra/factor-shell/factor-shell-tests.factor b/extra/factor-shell/factor-shell-tests.factor new file mode 100644 index 0000000000..f23dbd5990 --- /dev/null +++ b/extra/factor-shell/factor-shell-tests.factor @@ -0,0 +1,46 @@ +! Copyright (C) 2018 Doug Coleman. +! See http://factorcode.org/license.txt for BSD license. +USING: tools.test factor-shell ; +IN: factor-shell.tests + +! Absolute paths +{ "/" } [ "/" canonicalize-path ] unit-test +{ "/" } [ "/." canonicalize-path ] unit-test +{ "/" } [ "/.." canonicalize-path ] unit-test +{ "/" } [ "/Users/.." canonicalize-path ] unit-test +{ "/" } [ "/Users/../" canonicalize-path ] unit-test +{ "/" } [ "/Users/../." canonicalize-path ] unit-test +{ "/" } [ "/Users/.././" canonicalize-path ] unit-test +{ "/" } [ "/Users/.././././././" canonicalize-path ] unit-test +{ "/" } [ "/Users/../././/////./././/././././//././././././." canonicalize-path ] unit-test +{ "/" } [ "/Users/../../../..////.././././././/../" canonicalize-path ] unit-test +{ "/Users" } [ "/Users/../../../Users" canonicalize-path ] unit-test + +{ "/Users" } [ "/Users" canonicalize-path ] unit-test +{ "/Users" } [ "/Users/." canonicalize-path ] unit-test +{ "/Users/foo/bar" } [ "/Users/foo/bar" canonicalize-path ] unit-test + + +! Relative paths +{ "." } [ f canonicalize-path ] unit-test +{ "." } [ "" canonicalize-path ] unit-test +{ "." } [ "." canonicalize-path ] unit-test +{ "." } [ "./" canonicalize-path ] unit-test +{ "." } [ "./." canonicalize-path ] unit-test +{ ".." } [ ".." canonicalize-path ] unit-test +{ ".." } [ "../" canonicalize-path ] unit-test +{ ".." } [ "../." canonicalize-path ] unit-test +{ ".." } [ ".././././././//." canonicalize-path ] unit-test + +{ "../.." } [ "../.." canonicalize-path ] unit-test +{ "../.." } [ "../../" canonicalize-path ] unit-test +{ "../.." } [ "../.././././/./././" canonicalize-path ] unit-test + + +! Root paths +{ "/" } [ "/" root-path ] unit-test +{ "/" } [ "/Users" root-path ] unit-test +{ "/" } [ "//" root-path ] unit-test +{ "/" } [ "//Users" root-path ] unit-test +{ "/" } [ "/Users/foo/bar////././." root-path ] unit-test +{ "/" } [ "/Users/////" root-path ] unit-test diff --git a/extra/factor-shell/factor-shell.factor b/extra/factor-shell/factor-shell.factor new file mode 100644 index 0000000000..b3285f4d91 --- /dev/null +++ b/extra/factor-shell/factor-shell.factor @@ -0,0 +1,285 @@ +! Copyright (C) 2018 Doug Coleman. +! See http://factorcode.org/license.txt for BSD license. +USING: accessors arrays assocs classes.parser classes.tuple +combinators combinators.extras combinators.short-circuit +combinators.smart continuations fry io io.backend +io.encodings.utf8 io.files io.files.info io.launcher +io.pathnames io.standard-paths kernel lexer libc math math.order +multiline multiline.private namespaces parser prettyprint +sequences sequences.deep splitting strings system +system-info.macosx tools.hexdump unicode unix.signals unix.users +words ; +IN: factor-shell + +! General utils. +: trim-blanks ( string -- string' ) [ blank? ] trim ; inline +: trim-head-blanks ( string -- string' ) [ blank? ] trim-head ; inline +: trim-tail-blanks ( string -- string' ) [ blank? ] trim-tail ; inline + +GENERIC: find-command-path ( obj -- obj' ) + +M: sequence find-command-path + 0 over [ find-in-standard-login-path ] change-nth ; + +M: process find-command-path + [ find-command-path ] change-command ; + +GENERIC#: run-process>string 1 ( command encoding -- string ) + +M: array run-process>string ( command encoding -- string ) + [ swap >>command +new-session+ >>group ] dip + + [ find-command-path ] dip + [ contents ] with-process-reader ; inline + +M: string run-process>string + [ swap >>command +new-session+ >>group ] dip + + [ contents ] with-process-reader ; inline + +: run-utf8-process>string ( command -- string ) + utf8 run-process>string ; inline + + +! Shell things. +SYMBOL: factsh-directory-stack +factsh-directory-stack [ V{ } clone ] initialize + +SYMBOL: previous-directory +SYMBOL: pending-directory + + +: pushd ( string -- ) + factsh-directory-stack get push ; + +: popd ( -- string ) + factsh-directory-stack get pop ; +<< +: matching-delimiter ( ch -- ch' ) + H{ + { CHAR: ( CHAR: ) } + { CHAR: [ CHAR: ] } + { CHAR: { CHAR: } } + { CHAR: < CHAR: > } + { CHAR: : CHAR: ; } + } ?at drop ; + +: matching-delimiter-string ( string -- string' ) + [ matching-delimiter ] map ; +>> + +<< + +SYNTAX: STRING-SYNTAX: + scan-token + [ create-class-in dup tuple { "payload" } define-tuple-class ] + [ ] bi + { "[[" "[=[" "[==[" "[===[" "[====[" } [ + [ append create-word-in dup reset-generic ] + [ nip matching-delimiter-string ] 2bi + rot + '[ _ parse-multiline-string _ boa suffix! ] define-syntax + ] with with each ; + +>> + +<< +! STRING-SYNTAX: FACTSH +STRING-SYNTAX: FACTOR +STRING-SYNTAX: PYTHON +STRING-SYNTAX: RUBY +>> + +GENERIC: execute-string ( obj -- string ) +M: FACTOR execute-string + payload>> "-e=" prepend vm-path swap 2array run-utf8-process>string ; +M: PYTHON execute-string + payload>> 1array { "python" "-c" } prepend run-utf8-process>string ; +M: RUBY execute-string + payload>> 1array { "ruby" "-e" } prepend run-utf8-process>string ; + +ERROR: expected-len1 obj ; +: len1 ( seq -- obj ) dup length 1 = [ first ] [ expected-len1 ] if ; +ERROR: expected-len2 obj ; +: len2 ( seq -- obj1 obj2 ) dup length 2 = [ first2 ] [ expected-len2 ] if ; + +: find-binary-name ( string -- string/path ? ) + dup find-in-standard-login-path [ nip t ] [ f ] if* ; + +: transfer ( var1 var2 -- ) + [ get ] dip set ; inline + +: swap-vars ( var1 var2 -- ) + [ [ get ] bi@ ] 2keep [ set ] dip set ; inline + +ERROR: builtin-failed command argument message ; +: check-directory-exists ( to -- to ) + dup { [ exists? ] [ file-info directory? ] } 1&& + [ "cd" swap "No such directory" builtin-failed ] unless ; + +: root-path ( path -- path' ) + dup absolute-path? [ + dup [ path-separator? ] find + drop 1 + head + ] when ; + +: relative-path ( path -- relative-path ) + dup absolute-path? [ + dup [ path-separator? ] find + drop 1 + tail + ] when ; + +: canonicalize-path ( path -- path' ) + [ + relative-path + [ path-separator? ] split-when + [ { "." "" } member? ] reject + V{ } clone [ + dup ".." = [ + over empty? + [ over push ] + [ over ?last ".." = [ over push ] [ drop dup pop* ] if ] if + ] [ + over push + ] if + ] reduce + ] keep dup absolute-path? [ + [ + [ ".." = ] trim-head + path-separator join + ] dip root-path prepend-path + ] [ + drop path-separator join [ "." ] when-empty + ] if ; + +: count-canonical-path-components ( path -- n ) + canonicalize-path [ CHAR: / = ] count 1 + ; + +: mismatch-tail ( seq1 seq2 -- seq1 seq2 ) + 2dup mismatch [ 2dup [ length ] bi@ min ] unless* + '[ _ tail ] bi@ ; + +! c:\ vs d:\ -- no relative path possible. also impossible on unix. +: derive-both-absolute ( absolute-path1 absolute-path2 -- path1-relative-to-path2 ) + [ "/" split ] bi@ mismatch-tail [ "/" join ] bi@ + count-canonical-path-components + [ ".." ] replicate "/" join prepend-path ; + +: derive-relative-path ( path1 path2 -- path1-relative-to-path2 ) + [ canonicalize-path ] bi@ + { + { [ 2dup [ absolute-path ] both? ] [ derive-both-absolute ] } + [ "unimplemented" throw ] + } cond ; inline + +: home-directory? ( path -- ? ) "~" head? ; +: current-directory? ( path -- ? ) "./" head? ; + +: fixup-home-directory ( path -- path' ) + [ path-separator? ] split-when + dup first length 1 > [ + unclip [ "/" join ] [ rest home parent-directory prepend ] bi* append + ] [ + "/" join + ] if ; + +: our-cd ( args -- string/f ) + '[ + _ [ trim-blanks ] map + current-directory pending-directory transfer + [ + current-directory '[ drop home ] change + ] [ + { + { [ dup { "-" } sequence= ] [ drop previous-directory get current-directory set ] } + { [ dup first home-directory? ] [ first fixup-home-directory current-directory set ] } + ! { { "-L" } [ ] } + ! { { "-P" } [ ] } + [ len1 normalize-path canonicalize-path check-directory-exists current-directory set ] + } cond + ] if-empty + pending-directory previous-directory transfer f + ] [ + unparse + ] recover ; + +SYMBOL: exit-shell? + +ERROR: unknown-command cmd args ; +: eval-factsh ( string -- string-output ) + [ blank? ] trim-head " " split harvest [ + f + ] [ + unclip { + { "cd" [ our-cd ] } + { "ls" [ { "ls" } prepend utf8 run-process>string ] } + { "pwd" [ drop current-directory get "\n" append ] } + { "exit" [ [ 0 exit-shell? set ] [ len1 exit-shell? set ] if-empty "logout\n" ] } + ! { "pushd" [ len1 current-directory get ] } + ! { "popd" [ current-directory get ] } + [ + dup current-directory? [ + dup current-directory get prepend-path + dup exists? [ + 2nip run-utf8-process>string + ] [ + swap unknown-command + ] if + ] [ + find-binary-name [ prefix run-utf8-process>string ] + [ swap unknown-command ] if + ] if + ] + } case + ] if-empty ; + +: computer-name ( -- string ) { 1 10 } sysctl-query-string "." split1 drop ; + +: osx-bash-prompt ( -- string ) + [ + computer-name ":" + current-directory get + dup home = [ drop "~" ] [ file-name ] if + " " real-user-name "$ >> " + ] "" append-outputs-as ; + +: echo-prompt ( -- ) + osx-bash-prompt write flush ; + +SYMBOL: shell-last-exit + +GENERIC: handle-repl-error ( obj -- ) +M: unknown-command handle-repl-error + cmd>> ": command not found" append print ; + +M: process-failed handle-repl-error + process>> status>> shell-last-exit set ; + +: factsh-repl ( -- ) + ! [ "hello SIGUSR1" print flush ] SIGUSR1 add-signal-handler + [ "hello SIGINT" print flush ] SIGINT add-signal-handler + [ "hello SIGSTOP" print flush ] SIGSTOP add-signal-handler + [ "hello SIGQUIT" print flush ] SIGQUIT add-signal-handler + [ "hello SIGHUP" print flush ] SIGHUP add-signal-handler + [ "hello SIGTERM" print flush ] SIGTERM add-signal-handler + [ "hello SIGHUP" print flush ] SIGHUP add-signal-handler + + f exit-shell? [ + current-directory previous-directory transfer + [ + [ echo-prompt readln [ eval-factsh [ write ] when* ] [ nl t exit-shell? set ] if* ] + [ handle-repl-error ] recover + exit-shell? get not + ] loop + ] with-variable ; + +MAIN: factsh-repl +! PYTHON[[ print("hi")]] execute-string +! RUBY[[ printf('hello')]] execute-string +! FACTOR[[ USE: io "hi" print ]] execute-string + + +! stty -a +! ps ax -O tpgid +! http://unix.stackexchange.com/questions/149741/why-is-sigint-not-propagated-to-child-process-when-sent-to-its-parent-process +