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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
|
;;; build-app.lisp -- Contains the command line frontend definition
;;; to build the cli, simply load this file from the command line.
(asdf:load-system "oneliners.cli")
(defpackage #:oneliners.cli.app
(:use #:cl #:net.didierverna.clon)
(:local-nicknames (#:a #:alexandria)
(#:cli #:oneliners.cli)))
(in-package :oneliners.cli.app)
;;; HELP TEXTS
(defparameter +invite-help-text+
"
New contributor accounts are added to the your oneliners server by
redeeming invite tokens.
When the --redeem option is passed, the ARGS section is expected to be
three items long, and is interpreted as TOKEN USERNAME PASSWORD. E.g.:
ol --redeem PHONEYTOKEN c00lhacker my1337pw
Would attempt to make a new user named c00lhacker with password
my1337pw.")
(defparameter +oneliners-variables-help-text+
"
Oneliners may contain variables. There are positional variables and
named variables.
POSITIONAL VARIABLES appear in the oneliner as a # followed by a
number, which must 1 or greater. For example:
echo Hello #1, Happy #2
The #1 and #2 are a positional variables. You might call the above
like
ol 8 Doofus Tuesday
Assuming that the above is the 8th search result, then \"Hello Doofus,
Happy Tuesday\" would print to the console.
NAMED VARIABLES are similar. They appear in the oneliner as # followed
by an letter in the Roman alphabet, followed by any sequence of Roman
letters, numbers, or the symbol _. For example:
echo Hello #name you get a #thing
The #name and #thing are named variables. You might call the above like so:
ol 3 name=Goober thing='sock in the nose'
Which should print \"Hello Goober you get a sock in the nose\".
Finally, you can MIX POSITIONAL AND NAMED VARIABLES so long as, when
you run the oneliner, all positional variables appear first.
")
;;; CLON SYNOPSIS DEFINITION
(defsynopsis (:postfix "[TAGS ...] | N [ARGS ...]")
(group (:header "SEARCH OPTIONS")
(text :contents "Return oneliners tagged with all of TAGS")
(lispobj :long-name "limit"
:argument-type :optional
:argument-name "NUMBER"
:default-value 10
:description "The maximum number of results to return."
:typespec 'integer)
(flag :long-name "all-flagged"
:description "Return flagged oneliners. Ignores TAGS. Respects --limit")
(flag :long-name "not-flagged"
:description "Request that no flagged oneliners are returned.")
(flag :long-name "newest"
:description "Return newest oneliners. Ignores TAGs. Respects --limit up to server specified maxiumum."))
(text :contents " ")
(group (:header "ONELINER EXECUTION OPTIONS")
(text :contents "Runs the Nth search result with possible arguments ARGS.")
(flag :long-name "id"
:description "Runs a oneliner by its id instead of by result number.")
(flag :long-name "clip"
:description "Put oneliner into clipboard instead of running it.")
(flag :long-name "clear-cache"
:description "Clears all cached search results from your system.")
(lispobj :long-name "timeout"
:argument-type :optional
:argument-name "SECONDS"
:default-value 2
:typespec 'integer
:description "How long to wait for standard output before giving up."))
(text :contents " ")
(group (:header "HELP OPTIONS")
(flag :long-name "explain"
:description "View oneliner explaination text.")
(flag :long-name "whois"
:description "View information about a contributor. ARGS is just a contributor handle.")
(enum :long-name "help"
:enum '(:account :wiki :invites :variables)
:argument-name "TOPIC"
:description "Print help for a topic.
Topics: wiki, account, invites, variables"))
(group (:header "Variables" :hidden t)
(text :contents +oneliners-variables-help-text+))
(group (:header "Wiki" :hidden t)
(text :contents "Options For Managing Oneliners")
(flag :long-name "add"
:description "Intaractively add a oneliner and update the wiki.")
(flag :long-name "edit"
:description "Interactively edit a oneliner and update the wiki.")
(flag :long-name "flag"
:description "Flag a oneliner for review.")
(flag :long-name "unflag"
:description "If you have admin priviliges, unflag a oneliner.")
(flag :long-name "lock"
:description "If you have admin priviliges, lock a oneliner from being edited.")
(flag :long-name "unlock"
:description "If you have admin priviliges, unlock a oneliner."))
(group (:header "Account" :hidden t)
(text :contents "Options for Managing Your Contributor Account")
(flag :long-name "login"
:description "Attempt to login to your contributor account. ARGS are interpreted as USERNAME PASSWORD.")
(flag :long-name "logout"
:description "Revoke your own access token.")
(flag :long-name "change-password"
:description "Change your password. ARGS are interpreted as CURRENTPW NEWPW NEWPWAGAIN")
(flag :long-name "change-signature"
:description "Change your contributor signature. You will be prompted."))
(group (:header "Invites" :hidden t)
(text :contents " ")
(text :contents "Options For Making Invites and Redeeming Tokens")
(flag :long-name "invite"
:description "Request an invite token to send to a friend.")
(flag :long-name "redeem"
:description "Redeem an invite token.")
(text :contents +invite-help-text+)))
;;; HELPERS
(defun find-group-with-header (header)
"This function should be built in. Is it? How to know? The
documentation is both extensive and trash. Any manual that expects
you to go to sleep with it at night is written for the author more
than the users."
(loop for item in (net.didierverna.clon::items *synopsis*)
when (and (typep item 'net.didierverna.clon::group)
(string-equal header (net.didierverna.clon::header item)))
return item))
;;; MAIN ENTRY POINT
(defun main ()
"Entry point for our standalone application."
(make-context)
(a:when-let (topic (getopt :long-name "help"))
(help :item (find-group-with-header (symbol-name topic)))
(uiop:quit))
(handler-case
(let ((arguments (remainder)))
(cond
((getopt :long-name "whois")
(assert (first arguments))
(cli::show-contributor (first arguments)))
((getopt :long-name "redeem")
(destructuring-bind (token name pass) arguments
(cli::redeem-invite token name pass)))
((getopt :long-name "login")
(destructuring-bind (user pass) arguments
(cli::login user pass)))
((getopt :long-name "change-password")
(destructuring-bind (current new repeated) arguments
(cli::change-pw current new repeated)))
((getopt :long-name "change-signature")
(cli::change-signature))
((getopt :long-name "invite")
(cli::request-invite-code))
((getopt :long-name "logout")
(cli::revoke-access))
((getopt :long-name "add")
(cli::add-new-oneliner))
((getopt :long-name "all-flagged")
(cli::all-flagged-oneliners (getopt :long-name "limit")))
((getopt :long-name "newest")
(cli::newest-oneliners (getopt :long-name "limit")))
((getopt :long-name "clear-cache")
(cli::wipe-cache))
(arguments
;; when the first argument is a number, try run a oneliner
(a:when-let (hist-number (parse-integer (first arguments) :junk-allowed t))
(cond
((getopt :long-name "flag")
(cli::flag-item hist-number))
((getopt :long-name "unflag")
(cli::unflag-item hist-number))
((getopt :long-name "lock")
(cli::lock-item hist-number))
((getopt :long-name "unlock")
(cli::unlock-item hist-number))
((getopt :long-name "edit")
(cli::edit-item hist-number))
((getopt :long-name "explain")
(cli::print-item-explanation hist-number))
;; ((getopt :long-name "alias")
;; (cli::make-alias-for-item hist-number (second arguments)))
(t
(cli::run-item hist-number (rest arguments)
:by-id (getopt :long-name "id")
:force-clip (getopt :long-name "clip")
:timeout (getopt :long-name "timeout"))))
(uiop:quit))
;; otherwise search for oneliners
(cli::search-for-oneliners arguments
(getopt :long-name "limit")
(getopt :long-name "not-flagged")))
(t
(help)))
(uiop:quit))
(error (e)
(format *error-output* "~%ERROR: ~a~%" e)
(uiop:quit))
(#+sbcl sb-sys:interactive-interrupt
#+ccl ccl:interrupt-signal-condition
#+ecl ext:interactive-interrupt
()
(format t "Aborted by User Interrupt.~%")
(uiop:quit))))
;;; DUMP EXECUTABLE
(dump "ol" main)
|