]> gitweb.factorcode.org Git - factor.git/blobdiff - extra/gemini/cli/cli.factor
core: subseq-index? -> subseq-of?
[factor.git] / extra / gemini / cli / cli.factor
index b379c5102bc4d3491066d84c4e500ea42cf1f61a..0d815873c9c4f8f42941b1a28e099fb35ac361df 100644 (file)
 ! Copyright (C) 2021 John Benediktsson
 ! See http://factorcode.org/license.txt for BSD license
 
-USING: accessors ascii assocs combinators
-combinators.short-circuit formatting gemini gemini.private io
-io.encodings.string kernel math math.parser namespaces present
-sequences splitting urls ;
+USING: accessors arrays assocs combinators
+combinators.short-circuit command-loop formatting gemini
+gemini.private io io.directories io.encodings.string
+io.encodings.utf8 io.files io.files.temp io.launcher io.pipes
+kernel math math.parser namespaces present sequences splitting
+system urls webbrowser ;
 
 IN: gemini.cli
 
-CONSTANT: ABBREVS H{
-     { "b"    "back" }
-     { "f"    "forward" }
-     { "g"    "go" }
-     { "h"    "history" }
-     { "hist" "history" }
-     { "q"    "quit" }
-     { "r"    "reload" }
-     { "u"    "up" }
-}
+CONSTANT: DEFAULT-URL "gemini://gemini.circumlunar.space"
 
 CONSTANT: HISTORY V{ }
 CONSTANT: LINKS V{ }
+CONSTANT: STACK V{ }
+CONSTANT: PAGE V{ }
 CONSTANT: URL V{ }
 
+: find-url ( url items -- i item )
+    [ dup array? [ first ] when = ] with find ;
+
+: nth-url ( i items -- url )
+    ?nth dup array? [ first ] when ;
+
+: stack-url ( delta -- url )
+    URL ?first STACK find-url drop
+    [ + STACK nth-url ] [ drop f ] if* ;
+
+: add-stack ( args -- )
+    dup dup array? [ first ] when
+    dup STACK find-url drop [
+        2drop
+    ] [
+        URL ?first STACK find-url drop [
+            over PAGE find-url drop [
+                1 + dup STACK nth-url rot = [
+                    2drop
+                ] [
+                    STACK [ length ] [ delete-slice ] bi
+                    STACK push
+                    STACK length 10 > [
+                        0 STACK remove-nth! drop
+                    ] when
+                ] if
+            ] [
+                2drop
+                STACK push
+            ] if
+        ] [
+            drop
+            STACK delete-all
+            STACK push
+        ] if*
+    ] if ;
+
 : add-history ( args -- )
     HISTORY dup length 10 > [
         0 swap remove-nth!
     ] when dupd remove! push ;
 
-: gemini-history ( args -- )
-    drop HISTORY [ 1 + swap "[%s] %s\n" printf ] each-index
-    LINKS delete-all HISTORY LINKS push-all ;
+: print-links ( links verbose? -- )
+    LINKS delete-all over LINKS push-all
+    '[
+        1 + swap [ dup array? [ first ] when URL ?first = [ drop "*" ] when ] keep
+        _ [ dup array? [ dup second empty? not ] [ f ] if ] [ f ] if [
+            first2 swap "[%s] %s (%s)\n" printf
+        ] [
+            dup array? [ first2 ] [ f ] if
+            dup empty? -rot ? "[%s] %s\n" printf
+        ] if
+    ] each-index ;
+
+: gemini-history ( -- )
+    HISTORY t print-links ;
+
+: gemini-print ( url body meta -- )
+    f pre [
+        PAGE delete-all
+        gemini-charset decode split-lines [
+            { [ pre get not ] [ "=>" ?head ] } 0&& [
+                swap gemini-link present over 2array PAGE push
+                PAGE length swap "[%s] %s\n" printf
+            ] [
+                gemini-line.
+            ] if
+        ] with each
+        LINKS delete-all PAGE LINKS push-all
+    ] with-variable ;
 
 : gemini-get ( args -- )
-    dup 0 URL set-nth
+    dup URL set-first
     >url dup gemini [ drop ] 2dip swap "text/" ?head [
-        f pre [
-            LINKS delete-all
-            gemini-charset decode string-lines [
-                { [ pre get not ] [ "=>" ?head ] } 0&& [
-                    swap gemini-link present LINKS push
-                    LINKS length swap "[%s] %s\n" printf
-                ] [
-                    gemini-line.
-                ] if
-            ] with each
-        ] with-variable
+        "gemini.txt" temp-file
+        [ utf8 [ gemini-print ] with-file-writer ]
+        [ utf8 file-contents print ] bi
     ] [
         "ERROR: Cannot display '" "'" surround print 2drop
     ] if ;
 
 : gemini-go ( args -- )
-    [ "gemini://gemini.circumlunar.space" ] when-empty
-    dup "gemini://" head? [ "gemini://" prepend ] unless
-    dup add-history gemini-get ;
+    present [ DEFAULT-URL ] when-empty
+    { [ dup "://" subseq-of? ] [ "gemini://" head? ] } 1||
+    [ "gemini://" prepend ] unless
+    dup "gemini://" head? [
+        [ add-history ] [ add-stack ] [ gemini-get ] tri
+    ] [ open-url ] if ;
 
-: gemini-reload ( args -- )
-    drop HISTORY ?last gemini-go ;
+: gemini-reload ( -- )
+    URL ?first gemini-go ;
 
-: gemini-back ( args -- )
-    drop URL ?first HISTORY index [
-        1 - HISTORY ?nth [ gemini-get ] when*
-    ] when* ;
+: gemini-back ( -- )
+    -1 stack-url [ gemini-get ] when* ;
 
-: gemini-forward ( args -- )
-    drop URL ?first HISTORY index [
-        1 + HISTORY ?nth [ gemini-get ] when*
-    ] when* ;
+: gemini-forward ( -- )
+    1 stack-url [ gemini-get ] when* ;
 
-: gemini-up ( args -- )
-    drop URL ?first [
+: gemini-up ( -- )
+    URL ?first [
         >url f >>query f >>anchor
-        [ "/" ?tail drop "/" split1-last drop "/" append ] change-path
-        present gemini-go
+        [ dup "/" tail? "./../" "./" ? url-append-path ] change-path
+        gemini-go
     ] when* ;
 
-: gemini-cmd ( cmd -- )
-    " " split1 swap >lower ABBREVS ?at drop {
-        { "history" [ gemini-history ] }
-        { "go" [ gemini-go ] }
-        { "reload" [ gemini-reload ] }
-        { "back" [ gemini-back ] }
-        { "forward" [ gemini-forward ] }
-        { "up" [ gemini-up ] }
-        { "" [ drop ] }
-        [
-            dup string>number [ 1 - LINKS ?nth ] [ f ] if* [
-                2nip gemini-go
-            ] [
-                "ERROR: Unknown command '" "'" surround print drop
-            ] if*
-        ]
-    } case flush ;
+: gemini-less ( -- )
+    "gemini.txt" temp-file dup file-exists? [
+        "less" swap 2array try-process
+    ] [ drop ] if ;
+
+ : gemini-ls ( args -- )
+    [ PAGE ] [ "-l" = ] bi* print-links ;
+
+: gemini-quit ( -- )
+    "gemini.txt" temp-file ?delete-file 0 exit ;
+
+: gemini-url ( -- )
+    URL ?first [ print ] when* ;
+
+: gemini-root ( -- )
+    URL ?first [ >url "/" >>path gemini-go ] when* ;
+
+: gemini-shell ( args -- )
+    "|" split "gemini.txt" temp-file dup file-exists? [
+        "cat" swap 2array prefix run-pipeline drop
+    ] [ 2drop ] if ;
+
+CONSTANT: COMMANDS {
+    T{ command
+        { name "back" }
+        { quot [ drop gemini-back ] }
+        { help "Go back to the previous Gemini URL." }
+        { abbrevs { "b" } } }
+    T{ command
+        { name "forward" }
+        { quot [ drop gemini-forward ] }
+        { help "Go forward to the next Gemini URL." }
+        { abbrevs { "f" } } }
+    T{ command
+        { name "history" }
+        { quot [ drop gemini-history ] }
+        { help "Display recently viewed Gemini URLs." }
+        { abbrevs { "h" "hist" } } }
+    T{ command
+        { name "less" }
+        { quot [ drop gemini-less ] }
+        { help "View the most recent Gemini URL in a pager." }
+        { abbrevs { "l" } } }
+    T{ command
+        { name "ls" }
+        { quot [ gemini-ls ] }
+        { help "List the currently available links." }
+        { abbrevs f } }
+    T{ command
+        { name "go" }
+        { quot [ gemini-go ] }
+        { help "Go to a Gemini URL" }
+        { abbrevs { "g" } } }
+    T{ command
+        { name "gus" }
+        { quot [ drop "gemini://gus.guru/search" gemini-go ] }
+        { help "Submit a query to the GUS search engine." }
+        { abbrevs f } }
+    T{ command
+        { name "up" }
+        { quot [ drop gemini-up ] }
+        { help "Go up one directory from the recent Gemini URL." }
+        { abbrevs { "u" } } }
+    T{ command
+        { name "url" }
+        { quot [ drop gemini-url ] }
+        { help "Print the most recent Gemini URL." }
+        { abbrevs f } }
+    T{ command
+        { name "reload" }
+        { quot [ drop gemini-reload ] }
+        { help "Reload the most recent Gemini URL." }
+        { abbrevs { "r" } } }
+    T{ command
+        { name "root" }
+        { quot [ drop gemini-root ] }
+        { help "Navigate to the most recent Gemini URL's root." }
+        { abbrevs f } }
+    T{ command
+        { name "shell" }
+        { quot [ gemini-shell ] }
+        { help "'cat' the most recent Gemini URL through a shell." }
+        { abbrevs { "!" } } }
+    T{ command
+        { name "quit" }
+        { quot [ drop gemini-quit ] }
+        { help "Quit the program." }
+        { abbrevs { "q" "exit" } } }
+}
+
+TUPLE: gemini-command-loop < command-loop ;
+
+M: gemini-command-loop missing-command
+    over string>number [ 1 - LINKS ?nth ] [ f ] if* [
+        [ add-history ]
+        [ add-stack ]
+        [ dup array? [ first ] when gemini-get 3drop ] tri
+    ] [
+        call-next-method
+    ] if* ;
 
 : gemini-main ( -- )
-    "Welcome to Project Gemini!" print flush [
-        "GEMINI> " write flush readln
-        [ gemini-cmd t ] [ f ] if*
-    loop ;
+    "Welcome to Project Gemini!" "GEMINI>"
+    gemini-command-loop new-command-loop
+    COMMANDS [ over add-command ] each
+    run-command-loop ;
 
 MAIN: gemini-main