aboutsummaryrefslogtreecommitdiff
path: root/src/main.lisp
blob: e4916b784b6358dbf980915275fd7667993892fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
;;;; main.lisp -- oneliners.cli entrypoint

(defpackage oneliners.cli
  (:use :cl))
(in-package :oneliners.cli)

;;; CLI OPTIONS

(opts:define-opts
  (:name :add
   :description "Intaractively add a oneliner to the a wiki."
   :long "add")
  (:name :tags
   :description "A comma separated list of tags to filter search results."
   :short #\t
   :long "tags"
   :arg-parser #'identity
   :meta-var "'T1, T2, ...'")
  (:name :limit
   :description "An integer. The maximum number of results to return."
   :short #\l
   :long "limit"
   :meta-var "N"
   :default 10
   :arg-parser #'parse-integer)
  (:name :edit
   :description "An integer, a result number. Interactively edit af command."
   :long "edit"
   :meta-var "RESULT"
   :arg-parser #'parse-integer)
  (:name :not-flagged
   :description "Filter flagged oneliners from the search results"
   :long "not-flagged")
  (:name :audited-only
   :description "Filter unaudited oneliners from the search results"
   :long "audited-only"))

(defparameter +help-suffix+
  "Unless RESULT is an integer, search for oneliners that involve each command in COMMANDS.
E.g. 
    # search for oneliners involving both 'find' and 'grep'
    ol find grep   

Otherwise, when RESULT is an integer, run the oneliner with index RESULT from the most  
recent search command, and supply ARGS to that oneliner, if provided.
E.g. 
    # run the third result from the last search with arguments foo and bar
    ol 3 foo bar")

(defun print-help ()
  (opts:describe :prefix "Oneliners Wiki Command Line Tool" 
                 :args "[[RESULT ARGS |  COMMANDS]" 
                 :usage-of "ol"
                 :suffix +help-suffix+))

;;; CONFIG AND RESULTS FILE LOCATIONS

(defun config-file ()
  (merge-pathnames ".config/oneliners.config" (user-homedir-pathname)  ))

(defun last-search-file ()
  (merge-pathnames ".last_oneliners_search" (user-homedir-pathname)))

;;; UTILITIES

(defun parent-process-name ()
  "Prints the name of the parent process of the current process."
  (let ((ppidfile (format nil "/proc/~a/status" (osicat-posix:getppid))))
    (first (last 
            (ppcre:split "\\s" 
                         (with-open-file (input ppidfile)
                           (read-line input)))))))

(defmacro wait-until ((&key (timeout 1) (poll-every 0.01)) &body check)
  "Run CHECK every POLL-EVERY seconds until either TIMEOUT seconds
have passed or CHECK returns non-nil."
  (let ((clockvar (gensym))
        (var (gensym)))
    `(loop
       for ,clockvar from 0 by ,poll-every to ,timeout
       for ,var = (progn ,@check)
       when ,var
         return ,var
       do (sleep ,poll-every))))

(defun run-with-shell
    (command
     &key
       (shell-name (parent-process-name))
       (await-output-p 0.8)
       (output-stream *standard-output*))
  "run COMMAND, a string, in a fresh shell environment, initialized
with SHELL-NAME. The output from the command read line by line and is
printed to OUTPUT-STREAM. "
  (let ((shell
          (uiop:launch-program shell-name :input :stream :output :stream)))
    (symbol-macrolet ((shell-input (uiop:process-info-input shell))
                      (shell-output (uiop:process-info-output shell)))
      (write-line command shell-input)
      (finish-output shell-input)
      (when await-output-p
        (wait-until (:timeout await-output-p :poll-every 0.005)
          (listen shell-output))
        (loop while (listen shell-output)
              do (princ (read-line shell-output) output-stream)
                 (terpri output-stream))))))