clic.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP
HTML git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/
DIR Log
DIR Files
DIR Refs
DIR Tags
DIR README
DIR LICENSE
---
clic.lisp (26994B)
---
1 (in-package :cl-user)
2
3 ;;;; C binding to get terminal informations
4 #+ecl
5 (progn
6 (ffi:clines "
7 #include <sys/ioctl.h>
8 #include <limits.h>
9 #include <unistd.h>
10
11 #ifdef __OpenBSD__
12 void gotoPledge() {
13 pledge(\"dns inet stdio rpath tty wpath cpath proc exec\",NULL);
14 }
15
16 void kioskPledge() {
17 pledge(\"dns inet stdio tty rpath\",NULL);
18 }
19 #endif
20
21 int ttyPredicate() {
22 return isatty(fileno(stdout)); }
23 unsigned int getTerminalHeight() {
24 struct winsize w;
25 return ioctl(1,TIOCGWINSZ,&w)<0?UINT_MAX:w.ws_row;}")
26 #+openbsd
27 (progn
28 (ffi:def-function
29 ("kioskPledge" c-kiosk-pledge)
30 () :returning :void)
31 (ffi:def-function
32 ("gotoPledge" c-pledge)
33 () :returning :void))
34 (ffi:def-function
35 ("getTerminalHeight" c-termsize)
36 () :returning :unsigned-int)
37 (ffi:def-function
38 ("ttyPredicate" c-ttyp)
39 () :returning :int))
40 ;;;; END C binding
41
42 ;; structure to store links
43 (defstruct location host port type uri tls text
44 :predicate)
45
46 ;;;; kiosk mode
47 (defparameter *kiosk-mode* nil)
48
49 ;;;; no split mode
50 (defparameter *no-split* nil)
51
52 (defmacro kiosk-mode(&body code)
53 "prevent code if kiosk mode is enabled"
54 `(progn
55 (when (not *kiosk-mode*)
56 ,@code)))
57
58 ;;;; BEGIN GLOBAL VARIABLES
59
60 ;;; array of lines in buffer
61 (defparameter *buffer* nil)
62 ;;; array of lines of last menu
63 (defparameter *previous-buffer* nil)
64
65 ;;; bandwidth usage counter
66 (defparameter *total-bandwidth-in* 0)
67 (defparameter *last-bandwidth-in* 0)
68
69 ;;; a list containing the last viewed pages
70 (defparameter *history* '())
71
72 ;;; contain duration of the last request
73 (defparameter *duration* 0)
74
75 ;;; when clic loads a type 1 page, we store location structures here
76 (defparameter *links* (make-hash-table))
77
78 ;;; Colors for use in the code
79 (defparameter *colors* (make-hash-table))
80
81 ;;; List of allowed item types
82 (defparameter *allowed-selectors*
83 (list "0" "1" "2" "3" "4" "5" "6" "i"
84 "h" "7" "8" "9" "+" "T" "g" "I"))
85
86 ;;;; END GLOBAL VARIABLES
87
88 ;;;; BEGIN ANSI colors
89 (defun add-color(name type hue)
90 "Storing a ANSI color string into *colors*"
91 (setf (gethash name *colors*)
92 (format nil "~a[~a;~am" #\Escape type hue)))
93
94 (defun get-color(name) (gethash name *colors*))
95 (add-color 'red 1 31)
96 (add-color 'reset 0 70)
97 (add-color 'bg-black 0 40)
98 (add-color 'folder 4 34)
99 (add-color 'green 1 32)
100 (add-color 'file 0 35)
101 (add-color 'cyan 0 46)
102 (add-color 'http 0 33)
103 ;;;; END ANSI colors
104
105 (defun clear()
106 "Clear the screen"
107 (format t "~A[H~@*~A[J" #\escape))
108
109 ;;;; is the output interactive or a pipe ?
110 (defun ttyp()
111 "return t if the output is a terminal"
112 ;; we use this variable in case we don't want to be interactive
113 ;; like when we use a cmd arg to get an image
114 #+ecl
115 (if (= 1 (c-ttyp))
116 t
117 nil))
118
119 (defun copy-array(from)
120 "return a new array containing the same elements as the parameter"
121 (let ((dest (make-array (length from)
122 :fill-pointer 0
123 :initial-element nil
124 :adjustable t)))
125 (loop for element across from
126 do
127 (vector-push element dest))
128 dest))
129
130 (defun print-with-color(text &optional (color 'reset) (line-number nil))
131 "Used to display a line with a color"
132 (format t "~3A| ~a~a~a~%" (if line-number line-number "") (get-color color) text (get-color 'reset)))
133
134 (defmacro foreach-buffer(&body code)
135 `(progn
136 (loop for line across *buffer* do ,@code)))
137
138 (defmacro easy-socket(&body code)
139 "avoid duplicated code used for sockets"
140 `(progn
141
142 ;; try tls connection
143 (usocket:with-client-socket (socket sock host port)
144 (handler-case
145 (let ((stream
146 (cl+ssl:make-ssl-client-stream
147 sock
148 :external-format '(:utf-8 :eol-style :lf)
149 :unwrap-stream-p t
150 ;;:verify nil
151 :hostname host)))
152 ;; store in metadata that we are using TLS
153 (setf (location-tls (car *history*)) t)
154 ,@code)
155
156 ;; fallback to regular plaintext connection if tls fails
157 (t (c)
158 (usocket:with-client-socket (socket stream host port)
159 ,@code))))))
160
161 (defmacro check(identifier &body code)
162 "Macro to define a new syntax to make 'when' easier for formatted-output function"
163 `(progn (when (string= ,identifier line-type) ,@code)))
164
165 (defun split(text separator)
166 "this function split a string with separator and return a list"
167 (let ((text (concatenate 'string text (string separator))))
168 (loop for char across text
169 counting char into count
170 when (char= char separator)
171 collect
172 ;; we look at the position of the left separator from right to left
173 (let ((left-separator-position (position separator text :from-end t :end (- count 1))))
174 (subseq text
175 ;; if we can't find a separator at the left of the current, then it's the start of
176 ;; the string
177 (if left-separator-position (+ 1 left-separator-position) 0)
178 (- count 1))))))
179
180 (defun formatted-output(line)
181 "Used to display gopher response with color one line at a time"
182
183 ;; we check that the line is longer than 1 char and that it has tabs
184 (when (and
185 (< 1 (length line))
186 (position #\Tab line))
187 (let ((line-type (subseq line 0 1))
188 (field (split (subseq line 1) #\Tab)))
189
190 ;; if split worked
191 (when (>= (length field) 4)
192 (let ((line-number (+ 1 (hash-table-count *links*)))
193 (text (car field))
194 (uri (cadr field))
195 (host (caddr field))
196 (port (parse-integer (cadddr field))))
197
198 ;; see RFC 1436
199 ;; section 3.8
200 (if (member line-type *allowed-selectors* :test #'equal)
201 (progn
202
203 ;; RFC, page 4
204 (check "i"
205 (print-with-color text))
206
207 ;; 0 text file
208 (check "0"
209 (setf (gethash line-number *links*)
210 (make-location :host host :port port :uri uri :type line-type :text text))
211 (print-with-color text 'file line-number))
212
213 ;; 1 directory
214 (check "1"
215 (setf (gethash line-number *links*)
216 (make-location :host host :port port :uri uri :type line-type :text text))
217 (print-with-color text 'folder line-number))
218
219 ;; 2 CSO phone-book
220 ;; WE SKIP
221 (check "2")
222
223 ;; 3 Error
224 (check "3"
225 (print-with-color "error" 'red line-number))
226
227 ;; 4 BinHexed Mac file
228 (check "4"
229 (print-with-color text))
230
231 ;; 5 DOS Binary archive
232 (check "5"
233 (print-with-color "selector 5 not implemented" 'red))
234
235 ;; 6 Unix uuencoded file
236 (check "6"
237 (print-with-color "selector 6 not implemented" 'red))
238
239 ;; 7 Index search server
240 (check "7"
241 (setf (gethash line-number *links*)
242 (make-location :host host :port port :uri uri :type line-type :text text))
243 (print-with-color text 'red line-number))
244
245 ;; 8 Telnet session
246 (check "8"
247 (print-with-color "selector 8 not implemented" 'red))
248
249 ;; 9 Binary
250 (check "9"
251 (setf (gethash line-number *links*)
252 (make-location :host host :port port :uri uri :type line-type :text text))
253 (print-with-color text 'red line-number))
254
255 ;; + redundant server
256 (check "+"
257 (print-with-color "selector + not implemented" 'red))
258
259 ;; T text based tn3270 session
260 (check "T"
261 (print-with-color "selector T not implemented" 'red))
262
263 ;; g GIF file
264 (check "g"
265 (setf (gethash line-number *links*)
266 (make-location :host host :port port :uri uri :type line-type :text text))
267 (print-with-color text 'red line-number))
268
269 ;; I image
270 (check "I"
271 (setf (gethash line-number *links*)
272 (make-location :host host :port port :uri uri :type line-type :text text))
273 (print-with-color text 'red line-number))
274
275 ;; h http link
276 (check "h"
277 (setf (gethash line-number *links*) uri)
278 (print-with-color text 'http line-number))) ;;;; end of known types
279
280 ;; unknown type
281 (print-with-color (format nil
282 "invalid type ~a : ~a" line-type text)
283 'red)))))))
284
285 (defun download-binary(host port uri)
286 (easy-socket
287 ;; sending the request to the server
288 (format stream "~a~a~a" uri #\Return #\Newline)
289 (force-output stream)
290
291 ;; save into a file in /tmp
292 (let* ((filename (subseq uri (1+ (position #\/ uri :from-end t))))
293 (path (concatenate 'string "/tmp/" filename)))
294 (with-open-file (output path
295 :element-type '(unsigned-byte 8)
296 :direction :output :if-exists :supersede)
297 (let ((buf (make-array 4096 :element-type '(unsigned-byte 8))))
298 (loop for pos = (read-sequence buf stream)
299 while (plusp pos)
300 do
301 (format t ".")
302 (force-output)
303 (write-sequence buf output :end pos)))
304 (format t "~%File downloaded into ~a (~a bytes)~%" path (file-length output))))))
305
306
307 (defun getpage(host port uri &optional (search nil))
308 "send a request and store the answer (in *buffer* if text or save a file if binary)"
309
310 ;; we reset the buffer
311 (setf *buffer*
312 (make-array 200
313 :fill-pointer 0
314 :initial-element nil
315 :adjustable t))
316 (setf *last-bandwidth-in* 0)
317
318 (let ((real-time (get-internal-real-time)))
319 ;; we prepare informations about the connection
320 (easy-socket
321 ;; sending the request to the server
322 (if search
323 (format stream "~a ~a~a~a" uri search #\Return #\Newline)
324 (format stream "~a~a~a" uri #\Return #\Newline))
325 (force-output stream)
326
327 ;; not binary
328 ;; for each line we receive we store it in *buffer*
329 (loop for line = (read-line stream nil nil)
330 count line into lines
331 while line
332 do
333 ;; count bandwidth usage
334 (incf *total-bandwidth-in* (length line))
335 (incf *last-bandwidth-in* (length line))
336 ;; increase array size if needed
337 (when (= lines (- (array-total-size *buffer*) 1))
338 (adjust-array *buffer* (+ 200 (array-total-size *buffer*))))
339 (vector-push line *buffer*)))
340
341
342 ;; we store the duration of the connection
343 (setf *duration* (float (/ (- (get-internal-real-time) real-time)
344 internal-time-units-per-second)))))
345
346 (defun g(key)
347 "browse to the N-th link"
348 (let ((destination (gethash key *links*)))
349 (when destination
350 (cond
351 ;; visit a gopher link
352 ((location-p destination)
353 (visit destination))
354 ;; visit http link
355 ((search "URL:" destination)
356 (kiosk-mode
357 (uiop:run-program (list "xdg-open"
358 (subseq destination 4)))))))))
359
360 (defun filter-line(text)
361 "display only lines containg text"
362 (setf *previous-buffer* (copy-array *buffer*))
363 (setf *buffer* (make-array 400
364 :fill-pointer 0
365 :initial-element nil
366 :adjustable t))
367 ;; we create a new buffer from the current
368 ;; with only lines matching the string (no regex)
369 (loop for line across *previous-buffer*
370 do
371 (when (search text (car (split (subseq line 1) #\Tab)) :test #'char-equal)
372 (vector-push line *buffer*)))
373 (display-interactive-menu))
374
375 (defun load-file-menu(path)
376 "load a local file with a gophermap syntax and display it as a menu"
377 ;; we set the buffer
378 (setf *buffer*
379 (make-array 200
380 :fill-pointer 0
381 :initial-element nil
382 :adjustable t))
383
384 (with-open-file (stream path
385 :direction :input)
386 (loop for line = (read-line stream nil nil)
387 while line
388 do
389 (vector-push line *buffer*))))
390
391 (defun p()
392 "browse back to previous menu"
393 (when (<= 2 (length *history*))
394 (pop *history*)
395 (visit (pop *history*))))
396
397 (defun r()
398 "reload the previous menu"
399 (when (<= 1 (length *history*))
400 (visit (pop *history*))))
401
402 (defun s(number)
403 "show url for the link $NUMBER"
404 (let ((destination (gethash number *links*)))
405 (if (not destination)
406 (format t "No link ~a~%" number)
407 (format t "gopher://~a~a/~a~a~%"
408 (location-host destination)
409 (let ((port (location-port destination)))
410 (if (= 70 port)
411 ""
412 (format nil ":~a" port)))
413 (location-type destination)
414 (location-uri destination)))))
415
416 (defun help-shell()
417 "show help for the shell"
418 (format t "number : go to link n~%")
419 (format t "p or / : go to previous page~%")
420 (format t "h : display history~%")
421 (format t "sNUMBER : show url for link $NUMBER~%")
422 (format t "r or * : reload the page~%")
423 (format t "help : show this help~%")
424 (format t "d : dump the raw reponse~%")
425 (format t "/ text : display online lines matching text~%")
426 (format t "^D or x or q or . : quit clic~%"))
427
428 (defun parse-url(url)
429 "parse a gopher url and return a location"
430 (cond ((or
431 (string= "--help" url)
432 (string= "-h" url))
433 (help-shell)
434 (quit))
435
436 ((string= "-k" url)
437 #+openbsd
438 (c-kiosk-pledge)
439 (setf *kiosk-mode* t))
440
441 ((string= "-t" url)
442 (setf *no-split* t))
443
444 ((= 0 (or (search "file://" url) 1))
445 (load-file-menu (subseq url 7))
446 (make-location :host 'local-file
447 :text url
448 :port nil
449 :type "1"
450 :uri url))
451
452 (t
453 (let ((url (if (search "gopher://" url)
454 (subseq url 9)
455 url)))
456
457 ;; splitting with / to get host:port and uri
458 ;; splitting host and port to get them
459 (let* ((infos (split url #\/))
460 (host-port (split (pop infos) #\:)))
461
462 ;; create the location to visit
463 (make-location :host (pop host-port)
464 ;; default to port 70 if not supplied
465 :port (if host-port ;; <- empty if no port given
466 (parse-integer (car host-port))
467 70)
468
469 :text url
470
471 ;; if type is empty we default to "1"
472 :type (let ((type (pop infos)))
473 (if (< 0 (length type)) type "1"))
474
475 ;; glue remaining args between them
476 :uri (format nil "~{/~a~}" infos)))))))
477
478 (defun get-argv()
479 "Parse argv and return it"
480 #+ecl
481 (cdr (si::command-args)))
482
483 (defun user-input(input)
484 (cond
485 ;; show help
486 ((string= "help" input)
487 (help-shell))
488
489 ((search "s" input)
490 (s (parse-integer (subseq input 1))))
491
492 ((or
493 (string= "*" input)
494 (string= "ls" input)
495 (string= "r" input))
496 (r))
497
498 ;; go to previous page
499 ((or
500 (string= "/" input)
501 (string= "cd .." input)
502 (string= "p" input))
503 (p))
504
505 ;; search a pattern in a menu
506 ;; syntax /pattern
507 ((and
508 (search "/" input)
509 (> (length input) 1))
510 (filter-line (subseq input 1)))
511
512 ;; same as previously
513 ;; but with syntax / pattern
514 ((= 0 (or (search "/ " input) 1))
515 (filter-line (subseq input 2)))
516
517 ;; dump raw informations
518 ((string= "d" input)
519 (foreach-buffer
520 (format t "~a~%" line)))
521
522 ;; exit
523 ((or
524 (eql nil input)
525 (string= "NIL" input)
526 (string= "." input)
527 (string= "exit" input)
528 (string= "x" input)
529 (string= "q" input))
530 'end)
531
532 ;; show history
533 ((string= "h" input)
534 (setf *links* (make-hash-table))
535 (loop for element in *history*
536 do
537 (formatted-output
538 (format nil "~a~a ~a ~a ~a~%"
539 (location-type element)
540 (location-text element)
541 (location-uri element)
542 (location-host element)
543 (location-port element)))))
544
545
546 ;; follow a link
547 (t
548 ;; we ignore error in case of bad input
549 ;; just do nothing
550 (ignore-errors
551 (g (parse-integer input))))))
552
553 (defun display-interactive-binary-file()
554 "call xdg-open on the binary file"
555 (kiosk-mode
556 (let* ((location (car *history*))
557 (filename (subseq ;; get the text after last /
558 (location-uri location)
559 (1+ (position #\/
560 (location-uri location)
561 :from-end t))))
562 (filepath (concatenate 'string "/tmp/" (or filename "index"))))
563 (uiop:run-program (list "xdg-open" filepath)))))
564
565 (defun display-text-stdout()
566 "display the buffer to stdout"
567 (foreach-buffer
568 (format t "~a~%" line)))
569
570 (defun display-with-pager()
571 "display the buffer using $PAGER"
572 (let* ((uri (location-uri (car *history*)))
573 (filename (subseq uri (1+ (position #\/ uri :from-end t))))
574 (path (concatenate 'string "/tmp/" (or filename "index"))))
575 (with-open-file (output path
576 :direction :output
577 :if-does-not-exist :create
578 :if-exists :supersede)
579 (foreach-buffer
580 (format output "~a~%" line)))
581 (uiop:run-program (nconc
582 (if (uiop:getenv "PAGER")
583 (split (uiop:getenv "PAGER") #\Space)
584 (list "less"))
585 (list path))
586 :input :interactive
587 :output :interactive)))
588
589 ;; display a text file using the pager by piping
590 ;; the data to out, no temp file
591 (defun display-with-pager-kiosk()
592 "display the buffer to stdout, we don't use system() in kiosk mode"
593 (loop for line across *buffer*
594 do
595 (format t "~a~%" line)))
596
597 (defun display-interactive-menu()
598 "display a menu"
599 ;; we store the user input outside of the loop
600 ;; so if the user doesn't want to scroll
601 ;; we break the loop and then execute the command
602 (let ((input nil))
603 (let ((rows (if *no-split*
604 -1
605 (* (- (c-termsize) 1))))) ; -1 for command bar
606
607 (loop for line across *buffer*
608 counting line into row
609 do
610 (formatted-output line)
611
612
613 ;; split and ask to scroll or to type a command
614 (when (= row rows)
615 (setf row 0)
616 (format t "~a press enter or a shell command: "
617 (if *kiosk-mode* "KIOSK" ""))
618 (force-output)
619 (let ((first-input (read-char *standard-input* nil nil t)))
620 (cond
621 ((not first-input)
622 (format t "~%") ;; display a newline
623 (setf input "x") ;; we exit
624 (loop-finish))
625 ((char= #\NewLine first-input)
626 ;; we hide previous line (prompt)
627 (format t "'~C[A~C[K~C" #\Escape #\Escape #\return))
628 (t
629 (unread-char first-input)
630 (let ((input-text (format nil "~a" (read-line nil nil))))
631 (setf input input-text)
632 (loop-finish)))))))
633
634 ;; in case of shell command, do it
635 (if input
636 (user-input input)
637 (when (< (length *buffer*) rows)
638 (dotimes (i (- rows (length *buffer*)))
639 (format t "~%")))))))
640
641 (defun pipe-text(host port uri)
642 "pipe text to stdout, with stdout not a TTY output"
643 (getpage host port uri)
644 (foreach-buffer
645 (format t "~a~%" line)))
646
647 (defun pipe-binary(host port uri)
648 "pipe data to stdout, with stdout not a TTY output"
649 (easy-socket
650 (format stream "~a~a~a" uri #\Return #\Newline)
651 (force-output stream)
652
653 ;; write to the standard output
654 (let ((buf (make-array 4096 :element-type '(unsigned-byte 8))))
655 (loop for pos = (read-sequence buf stream)
656 while (plusp pos)
657 do
658 (write-sequence buf *standard-output* :end pos)))))
659
660 (defun pipe-to-stdout(destination)
661 "fetch data and output to stdout without storing anything"
662
663 (if (or
664 (string= "0" (location-type destination))
665 (string= "1" (location-type destination))
666 (string= "7" (location-type destination)))
667
668 (pipe-text (location-host destination)
669 (location-port destination)
670 (location-uri destination))
671
672 (pipe-binary (location-host destination)
673 (location-port destination)
674 (location-uri destination))))
675
676 (defun visit(destination)
677 "fetch and display content interactively"
678
679 ;; add it to the history !
680 (push destination *history*)
681
682 (let ((type
683 (cond
684
685 ;; fetch a menu
686 ((string= "1" (location-type destination))
687 (if (eql 'local-file (location-host destination))
688 'menu
689 (getpage (location-host destination)
690 (location-port destination)
691 (location-uri destination)))
692 'menu)
693
694 ;; fetch a text file
695 ((string= "0" (location-type destination))
696 (getpage (location-host destination)
697 (location-port destination)
698 (location-uri destination))
699 'text)
700
701 ;; fetch a menu after search
702 ((string= "7" (location-type destination))
703 (format t "Input : ")
704 (let ((user-input (read-line nil nil)))
705 (getpage (location-host destination)
706 (location-port destination)
707 (location-uri destination)
708 user-input))
709 'menu)
710
711 ;; if not type 0 1 7 then it's binary
712 (t
713 (kiosk-mode
714 (download-binary (location-host destination)
715 (location-port destination)
716 (location-uri destination)))
717 'binary))))
718
719 ;; we reset the links table ONLY if we have a new menu
720 ;; we also keep the last menu buffer
721 (when (eql type 'menu)
722 (setf *previous-buffer* (copy-array *buffer*))
723 (setf *links* (make-hash-table)))
724
725
726 (if (eql type 'menu)
727 (display-interactive-menu)
728 (progn
729 (if (eql type 'text)
730 (if *kiosk-mode*
731 (display-with-pager-kiosk)
732 (display-with-pager))
733 (kiosk-mode (display-interactive-binary-file)))
734 ;; redraw last menu
735 ;; we need to get previous buffer and reset links numbering
736 (pop *history*)
737 (when (and
738 *previous-buffer*
739 (not *kiosk-mode*))
740 (setf *buffer* (copy-array *previous-buffer*))
741 (setf *links* (make-hash-table))
742 (display-interactive-menu))))))
743
744
745 (defun display-prompt()
746 "show the prompt and helper"
747 (let ((last-page (car *history*)))
748 (format t "~a~agopher://~a:~a/~a~a (~as, ~aKb) / (p)rev (r)edisplay (h)istory : "
749 (if *kiosk-mode* "KIOSK " "")
750 (if (location-tls last-page) "**TLS** " "UNSECURE ")
751 (location-host last-page)
752 (location-port last-page)
753 (location-type last-page)
754 (location-uri last-page)
755 *duration*
756 (floor (/ *last-bandwidth-in* 1024.0))))
757 (force-output))
758
759 (defun shell()
760 "Shell for user interaction"
761 (display-prompt)
762
763 ;; we loop until X or Q is typed
764 (loop for input = (format nil "~a" (read-line nil nil))
765 while (not (or
766 (string= "NIL" input) ;; ^D
767 (string= "exit" input)
768 (string= "x" input)
769 (string= "q" input)))
770 do
771 (when (eq 'end (user-input input))
772 (loop-finish))
773 (display-prompt)))
774
775 (defun main()
776 "entry function of clic, we need to determine if the usage is one of
777 the 3 following cases : interactive, not interactive or
778 piped. Interactive is the state where the user will browse clic for
779 multiple content. Not interactive is the case where clic is called
780 with a parameter not of type 1, so it will fetch the content,
781 display it and exit and finally, the redirected case where clic will
782 print to stdout and exit."
783
784 ;; pledge support on OpenBSD
785 #+openbsd
786 (c-pledge)
787
788 ;; re-enable SIGINT (Ctrl+C) disabled for loading clic
789 (ext:set-signal-handler ext:+sigint+ 'quit)
790
791 (handler-case
792 (let ((destination (car (last
793 (loop for element in (get-argv)
794 collect (parse-url element))))))
795
796 ;; if we didn't passed a url as parameter, use a default
797 (if (not (location-p destination))
798 (setf destination (make-location :host "gopherproject.org" :port 70 :uri "/" :type "1" :text "gopherproject")))
799
800 ;; is there an output redirection ?
801 (if (ttyp)
802 (progn
803 (clear)
804 ;; if we don't ask a menu, not going interactive
805 (if (not (string= "1" (location-type destination)))
806 ;; not interactive
807 (visit destination)
808 ;; if user want to drop from first page we need
809 ;; to look it here
810 (when (not (eq 'end (visit destination)))
811 ;; we continue to the shell if we are in a terminal
812 (shell)))
813 (format t "~a kB in.~%" (floor (/ *total-bandwidth-in* 1024.0))))
814 (pipe-to-stdout destination)))
815 (t (error)
816 (progn
817 (format t "Something went wrong~%")
818 (print error)))))
819
820 ;; we allow ecl to use a new kind of argument
821 ;; not sure how it works but that works
822 #+ecl
823 (defconstant +uri-rules+
824 '(("*DEFAULT*" 1 "" :stop)))