]> gitweb.factorcode.org Git - factor.git/blob - extra/gemini/cli/cli.factor
core: subseq-index? -> subseq-of?
[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 split-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     { [ dup "://" subseq-of? ] [ "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     URL ?first 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
122         [ dup "/" tail? "./../" "./" ? url-append-path ] change-path
123         gemini-go
124     ] when* ;
125
126 : gemini-less ( -- )
127     "gemini.txt" temp-file dup file-exists? [
128         "less" swap 2array try-process
129     ] [ drop ] if ;
130
131  : gemini-ls ( args -- )
132     [ PAGE ] [ "-l" = ] bi* print-links ;
133
134 : gemini-quit ( -- )
135     "gemini.txt" temp-file ?delete-file 0 exit ;
136
137 : gemini-url ( -- )
138     URL ?first [ print ] when* ;
139
140 : gemini-root ( -- )
141     URL ?first [ >url "/" >>path gemini-go ] when* ;
142
143 : gemini-shell ( args -- )
144     "|" split "gemini.txt" temp-file dup file-exists? [
145         "cat" swap 2array prefix run-pipeline drop
146     ] [ 2drop ] if ;
147
148 CONSTANT: COMMANDS {
149     T{ command
150         { name "back" }
151         { quot [ drop gemini-back ] }
152         { help "Go back to the previous Gemini URL." }
153         { abbrevs { "b" } } }
154     T{ command
155         { name "forward" }
156         { quot [ drop gemini-forward ] }
157         { help "Go forward to the next Gemini URL." }
158         { abbrevs { "f" } } }
159     T{ command
160         { name "history" }
161         { quot [ drop gemini-history ] }
162         { help "Display recently viewed Gemini URLs." }
163         { abbrevs { "h" "hist" } } }
164     T{ command
165         { name "less" }
166         { quot [ drop gemini-less ] }
167         { help "View the most recent Gemini URL in a pager." }
168         { abbrevs { "l" } } }
169     T{ command
170         { name "ls" }
171         { quot [ gemini-ls ] }
172         { help "List the currently available links." }
173         { abbrevs f } }
174     T{ command
175         { name "go" }
176         { quot [ gemini-go ] }
177         { help "Go to a Gemini URL" }
178         { abbrevs { "g" } } }
179     T{ command
180         { name "gus" }
181         { quot [ drop "gemini://gus.guru/search" gemini-go ] }
182         { help "Submit a query to the GUS search engine." }
183         { abbrevs f } }
184     T{ command
185         { name "up" }
186         { quot [ drop gemini-up ] }
187         { help "Go up one directory from the recent Gemini URL." }
188         { abbrevs { "u" } } }
189     T{ command
190         { name "url" }
191         { quot [ drop gemini-url ] }
192         { help "Print the most recent Gemini URL." }
193         { abbrevs f } }
194     T{ command
195         { name "reload" }
196         { quot [ drop gemini-reload ] }
197         { help "Reload the most recent Gemini URL." }
198         { abbrevs { "r" } } }
199     T{ command
200         { name "root" }
201         { quot [ drop gemini-root ] }
202         { help "Navigate to the most recent Gemini URL's root." }
203         { abbrevs f } }
204     T{ command
205         { name "shell" }
206         { quot [ gemini-shell ] }
207         { help "'cat' the most recent Gemini URL through a shell." }
208         { abbrevs { "!" } } }
209     T{ command
210         { name "quit" }
211         { quot [ drop gemini-quit ] }
212         { help "Quit the program." }
213         { abbrevs { "q" "exit" } } }
214 }
215
216 TUPLE: gemini-command-loop < command-loop ;
217
218 M: gemini-command-loop missing-command
219     over string>number [ 1 - LINKS ?nth ] [ f ] if* [
220         [ add-history ]
221         [ add-stack ]
222         [ dup array? [ first ] when gemini-get 3drop ] tri
223     ] [
224         call-next-method
225     ] if* ;
226
227 : gemini-main ( -- )
228     "Welcome to Project Gemini!" "GEMINI>"
229     gemini-command-loop new-command-loop
230     COMMANDS [ over add-command ] each
231     run-command-loop ;
232
233 MAIN: gemini-main