]> gitweb.factorcode.org Git - factor.git/blob - extra/gemini/cli/cli.factor
gemini: use ../ url-append-path for "up".
[factor.git] / extra / gemini / cli / cli.factor
1 ! Copyright (C) 2021 John Benediktsson
2 ! See http://factorcode.org/license.txt for BSD license
3
4 USING: accessors arrays assocs combinators
5 combinators.short-circuit command-loop formatting gemini
6 gemini.private io io.directories io.encodings.string
7 io.encodings.utf8 io.files io.files.temp io.launcher io.pipes
8 kernel math math.parser namespaces present sequences splitting
9 system urls webbrowser ;
10
11 IN: gemini.cli
12
13 CONSTANT: DEFAULT-URL "gemini://gemini.circumlunar.space"
14
15 CONSTANT: HISTORY V{ }
16 CONSTANT: LINKS V{ }
17 CONSTANT: STACK V{ }
18 CONSTANT: PAGE V{ }
19 CONSTANT: URL V{ }
20
21 : find-url ( url items -- i item )
22     [ dup array? [ first ] when = ] with find ;
23
24 : nth-url ( i items -- url )
25     ?nth dup array? [ first ] when ;
26
27 : stack-url ( delta -- url )
28     URL ?first STACK find-url drop
29     [ + STACK nth-url ] [ drop f ] if* ;
30
31 : add-stack ( args -- )
32     dup dup array? [ first ] when
33     dup STACK find-url drop [
34         2drop
35     ] [
36         URL ?first STACK find-url drop [
37             over PAGE find-url drop [
38                 1 + dup STACK nth-url rot = [
39                     2drop
40                 ] [
41                     STACK [ length ] [ delete-slice ] bi
42                     STACK push
43                     STACK length 10 > [
44                         0 STACK remove-nth! drop
45                     ] when
46                 ] if
47             ] [
48                 2drop
49                 STACK push
50             ] if
51         ] [
52             drop
53             STACK delete-all
54             STACK push
55         ] if*
56     ] if ;
57
58 : add-history ( args -- )
59     HISTORY dup length 10 > [
60         0 swap remove-nth!
61     ] when dupd remove! push ;
62
63 : print-links ( links verbose? -- )
64     LINKS delete-all over LINKS push-all
65     '[
66         1 + swap [ dup array? [ first ] when URL ?first = [ drop "*" ] when ] keep
67         _ [ dup array? [ dup second empty? not ] [ f ] if ] [ f ] if [
68             first2 swap "[%s] %s (%s)\n" printf
69         ] [
70             dup array? [ first2 ] [ f ] if
71             dup empty? -rot ? "[%s] %s\n" printf
72         ] if
73     ] each-index ;
74
75 : gemini-history ( -- )
76     HISTORY t print-links ;
77
78 : gemini-print ( url body meta -- )
79     f pre [
80         PAGE delete-all
81         gemini-charset decode string-lines [
82             { [ pre get not ] [ "=>" ?head ] } 0&& [
83                 swap gemini-link present over 2array PAGE push
84                 PAGE length swap "[%s] %s\n" printf
85             ] [
86                 gemini-line.
87             ] if
88         ] with each
89         LINKS delete-all PAGE LINKS push-all
90     ] with-variable ;
91
92 : gemini-get ( args -- )
93     dup URL set-first
94     >url dup gemini [ drop ] 2dip swap "text/" ?head [
95         "gemini.txt" temp-file
96         [ utf8 [ gemini-print ] with-file-writer ]
97         [ utf8 file-contents print ] bi
98     ] [
99         "ERROR: Cannot display '" "'" surround print 2drop
100     ] if ;
101
102 : gemini-go ( args -- )
103     present [ DEFAULT-URL ] when-empty
104     { [ "://" over subseq? ] [ "gemini://" head? ] } 1||
105     [ "gemini://" prepend ] unless
106     dup "gemini://" head? [
107         [ add-history ] [ add-stack ] [ gemini-get ] tri
108     ] [ open-url ] if ;
109
110 : gemini-reload ( -- )
111     HISTORY ?last gemini-go ;
112
113 : gemini-back ( -- )
114     -1 stack-url [ gemini-get ] when* ;
115
116 : gemini-forward ( -- )
117     1 stack-url [ gemini-get ] when* ;
118
119 : gemini-up ( -- )
120     URL ?first [
121         >url f >>query f >>anchor [ "../" url-append-path ] change-path
122         gemini-go
123     ] when* ;
124
125 : gemini-less ( -- )
126     "gemini.txt" temp-file dup exists? [
127         "less" swap 2array try-process
128     ] [ drop ] if ;
129
130  : gemini-ls ( args -- )
131     [ PAGE ] [ "-l" = ] bi* print-links ;
132
133 : gemini-quit ( -- )
134     "gemini.txt" temp-file ?delete-file 0 exit ;
135
136 : gemini-url ( -- )
137     URL ?first [ print ] when* ;
138
139 : gemini-root ( -- )
140     URL ?first [ >url "/" >>path gemini-go ] when* ;
141
142 : gemini-shell ( args -- )
143     "|" split "gemini.txt" temp-file dup exists? [
144         "cat" swap 2array prefix run-pipeline drop
145     ] [ 2drop ] if ;
146
147 CONSTANT: COMMANDS {
148     T{ command
149         { name "back" }
150         { quot [ drop gemini-back ] }
151         { help "Go back to the previous Gemini URL." }
152         { abbrevs { "b" } } }
153     T{ command
154         { name "forward" }
155         { quot [ drop gemini-forward ] }
156         { help "Go forward to the next Gemini URL." }
157         { abbrevs { "f" } } }
158     T{ command
159         { name "history" }
160         { quot [ drop gemini-history ] }
161         { help "Display recently viewed Gemini URLs." }
162         { abbrevs { "h" "hist" } } }
163     T{ command
164         { name "less" }
165         { quot [ drop gemini-less ] }
166         { help "View the most recent Gemini URL in a pager." }
167         { abbrevs { "l" } } }
168     T{ command
169         { name "ls" }
170         { quot [ gemini-ls ] }
171         { help "List the currently available links." }
172         { abbrevs f } }
173     T{ command
174         { name "go" }
175         { quot [ gemini-go ] }
176         { help "Go to a Gemini URL" }
177         { abbrevs { "g" } } }
178     T{ command
179         { name "gus" }
180         { quot [ drop "gemini://gus.guru/search" gemini-go ] }
181         { help "Submit a query to the GUS search engine." }
182         { abbrevs f } }
183     T{ command
184         { name "up" }
185         { quot [ drop gemini-up ] }
186         { help "Go up one directory from the recent Gemini URL." }
187         { abbrevs { "u" } } }
188     T{ command
189         { name "url" }
190         { quot [ drop gemini-url ] }
191         { help "Print the most recent Gemini URL." }
192         { abbrevs f } }
193     T{ command
194         { name "reload" }
195         { quot [ drop gemini-reload ] }
196         { help "Reload the most recent Gemini URL." }
197         { abbrevs { "r" } } }
198     T{ command
199         { name "root" }
200         { quot [ drop gemini-root ] }
201         { help "Navigate to the most recent Gemini URL's root." }
202         { abbrevs f } }
203     T{ command
204         { name "shell" }
205         { quot [ gemini-shell ] }
206         { help "'cat' the most recent Gemini URL through a shell." }
207         { abbrevs { "!" } } }
208     T{ command
209         { name "quit" }
210         { quot [ drop gemini-quit ] }
211         { help "Quit the program." }
212         { abbrevs { "q" "exit" } } }
213 }
214
215 TUPLE: gemini-command-loop < command-loop ;
216
217 M: gemini-command-loop missing-command
218     over string>number [ 1 - LINKS ?nth ] [ f ] if* [
219         [ add-history ]
220         [ add-stack ]
221         [ dup array? [ first ] when gemini-get 3drop ] tri
222     ] [
223         call-next-method
224     ] if* ;
225
226 : gemini-main ( -- )
227     "Welcome to Project Gemini!" "GEMINI>"
228     gemini-command-loop new-command-loop
229     COMMANDS [ over add-command ] each
230     run-command-loop ;
231
232 MAIN: gemini-main