Init
This commit is contained in:
commit
8cf1683903
|
@ -0,0 +1,18 @@
|
||||||
|
:Date 2018-09-23
|
||||||
|
|
||||||
|
"Don't despair that others don't know you--
|
||||||
|
despair that you don't know them."
|
||||||
|
-- Master Kung
|
||||||
|
|
||||||
|
%
|
||||||
|
:Date 2018-09-24
|
||||||
|
|
||||||
|
"Death to the bourgeousie class!"
|
||||||
|
-- Some comrade
|
||||||
|
|
||||||
|
%
|
||||||
|
:Date 2018-09-25
|
||||||
|
|
||||||
|
"I don't have a quote for today!"
|
||||||
|
-- Lazy sysadmin
|
||||||
|
%
|
|
@ -0,0 +1,16 @@
|
||||||
|
(defsystem "qotdd"
|
||||||
|
:version "0.1"
|
||||||
|
:author "Jaidyn Ann <jadedctrl@teknik.io>"
|
||||||
|
:license "AGPLv3"
|
||||||
|
:depends-on ("cl-strings" "usocket")
|
||||||
|
:components ((:module "src"
|
||||||
|
:components
|
||||||
|
((:file "package")
|
||||||
|
(:file "misc")
|
||||||
|
(:file "string")
|
||||||
|
(:file "stream")
|
||||||
|
(:file "quote")
|
||||||
|
(:file "server"))))
|
||||||
|
|
||||||
|
:description
|
||||||
|
"Question of the Day server (RFC 865-compliant)")
|
|
@ -0,0 +1,36 @@
|
||||||
|
(in-package :qotdd)
|
||||||
|
|
||||||
|
;; EXPRESSION BACKUP --> EXPRESSION_OR_BACKUP
|
||||||
|
(defun value-or (expression backup)
|
||||||
|
"If an expression is non-NIL, return it. Otherwise,
|
||||||
|
return a backup."
|
||||||
|
|
||||||
|
(if expression
|
||||||
|
expression
|
||||||
|
backup))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; UNIVERSAL_TIME --> ISO8601-FORMATTED_STRING
|
||||||
|
(defun iso8601 (universal-time)
|
||||||
|
"Return `universal-time` in ISO 8601 format. :)"
|
||||||
|
|
||||||
|
(multiple-value-bind
|
||||||
|
(second minute hour date month year)
|
||||||
|
(decode-universal-time universal-time)
|
||||||
|
|
||||||
|
(format nil "~A-~A-~A"
|
||||||
|
year
|
||||||
|
(min-string-length month 2 "0")
|
||||||
|
(min-string-length date 2 "0"))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; LIST --> RANDOM_ITEM
|
||||||
|
(defun random-item (list)
|
||||||
|
"Get a random item from a list."
|
||||||
|
|
||||||
|
(let* ((length (length list))
|
||||||
|
(n (random length)))
|
||||||
|
|
||||||
|
(nth n list)))
|
|
@ -0,0 +1,7 @@
|
||||||
|
(defpackage :qotdd
|
||||||
|
(:export
|
||||||
|
|
||||||
|
:server))
|
||||||
|
|
||||||
|
(in-package :qotdd)
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
(in-package :qotdd)
|
||||||
|
|
||||||
|
;; QUOTE_FILE_PATH --> LIST_OF_LISTS
|
||||||
|
(defun get-quotes (path)
|
||||||
|
"Read quotes into a list of quotes by path.
|
||||||
|
The list'll look like this:
|
||||||
|
|
||||||
|
((DATE QUOTE)
|
||||||
|
(DATE QUOTE)
|
||||||
|
(DATE QUOTE))"
|
||||||
|
|
||||||
|
(mapcar
|
||||||
|
(lambda (quote)
|
||||||
|
(list
|
||||||
|
(get-quote-date quote)
|
||||||
|
(remove-quote-date quote)))
|
||||||
|
(read-line-chunked (open path) "%")))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; QUOTE_STRING --> DATE_IN_STRING
|
||||||
|
(defun get-quote-date (quote)
|
||||||
|
"Get the date of a set quote."
|
||||||
|
|
||||||
|
(let ((date (get-colon-value quote "Date")))
|
||||||
|
(if date
|
||||||
|
date
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; QUOTE_STRING --> QUOTE_STRING_WITHOUT_DATE
|
||||||
|
(defun remove-quote-date (quote)
|
||||||
|
"Remove the date from a set quote."
|
||||||
|
|
||||||
|
(if (get-quote-date quote)
|
||||||
|
(remove-colon-value quote "Date")
|
||||||
|
quote))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; PATH_TO_QOTD_FILE [UNIVERSAL_TIME] --> DATED_OR_RANDOM_QUOTE
|
||||||
|
(defun get-quote (path &optional universal-time)
|
||||||
|
"Return a quote of the day.
|
||||||
|
If you pass `universal-time`, then it'll try to get a quote
|
||||||
|
specific to that day.
|
||||||
|
If a quote doesn't exist for that day (or you don't pass
|
||||||
|
`universal-time`), it'll return a random quote."
|
||||||
|
|
||||||
|
(let* ((quotes (get-quotes path))
|
||||||
|
(date (ignore-errors (iso8601 universal-time)))
|
||||||
|
(dated-quote (ignore-errors (cadr (getf-string quotes date)))))
|
||||||
|
|
||||||
|
(if dated-quote
|
||||||
|
dated-quote
|
||||||
|
(random-item quotes))))
|
|
@ -0,0 +1,53 @@
|
||||||
|
(in-package :qotdd)
|
||||||
|
|
||||||
|
;; [HOST] [PORT]
|
||||||
|
(defun server (&optional
|
||||||
|
(host "127.0.0.1")
|
||||||
|
(port 1117)
|
||||||
|
(qotd-path "/usr/share/games/qotdd/qotd2018"))
|
||||||
|
"Start the QOTD server."
|
||||||
|
|
||||||
|
(let ((socket (usocket:socket-listen host port)))
|
||||||
|
|
||||||
|
(unwind-protect
|
||||||
|
(loop
|
||||||
|
:do
|
||||||
|
(let ((connection (connection-get socket)))
|
||||||
|
(main connection qotd-path)
|
||||||
|
(connection-kill connection)))
|
||||||
|
|
||||||
|
(progn
|
||||||
|
(format t "Dying...")
|
||||||
|
(usocket:socket-close socket)))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; SOCKET --> CONNECTION_ON_SOCKET
|
||||||
|
(defun connection-get (socket)
|
||||||
|
"Return a Connection from a Socket; until Connection recieved,
|
||||||
|
wait patiently."
|
||||||
|
|
||||||
|
(usocket:socket-accept socket :element-type 'character))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; CONNECTION
|
||||||
|
(defun connection-kill (connection)
|
||||||
|
"Close a connection."
|
||||||
|
|
||||||
|
(usocket:socket-close connection))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defun main (connection qotd-path)
|
||||||
|
"Main function on a connection-- send the QOTD to them."
|
||||||
|
|
||||||
|
(connection-msg connection
|
||||||
|
(get-quote qotd-path (get-universal-time))))
|
||||||
|
|
||||||
|
|
||||||
|
;; CONNECTION
|
||||||
|
(defun connection-msg (connection message)
|
||||||
|
"Send a message to a connection."
|
||||||
|
|
||||||
|
(format (usocket:socket-stream connection) "~A" message))
|
|
@ -0,0 +1,37 @@
|
||||||
|
(in-package :qotdd)
|
||||||
|
|
||||||
|
;; STREAM [FOOTER-STRING] --> DATA_FROM_STREAM_UNTIL_FOOTER
|
||||||
|
(defun read-line-until (stream seperator-line)
|
||||||
|
"Read lines from a stream until a certain line is reached."
|
||||||
|
|
||||||
|
(let ((cur-line
|
||||||
|
(ignore-errors (read-line stream))))
|
||||||
|
|
||||||
|
(cond
|
||||||
|
((and cur-line (not (equal cur-line (string seperator-line))))
|
||||||
|
(format nil "~A~%~A"
|
||||||
|
cur-line
|
||||||
|
(value-or
|
||||||
|
(read-line-until stream seperator-line)
|
||||||
|
"")))
|
||||||
|
|
||||||
|
('T nil))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; STREAM SEPERATOR --> LIST_OF_STRINGS
|
||||||
|
(defun read-line-chunked (stream seperator-line)
|
||||||
|
"read-line over a stream until EOF into a single string--
|
||||||
|
but, whenver the `seperator-line` is reached, start a
|
||||||
|
brand new string.
|
||||||
|
Returns a list of (multi-lined) strings."
|
||||||
|
|
||||||
|
(let ((chunk
|
||||||
|
(read-line-until stream seperator-line)))
|
||||||
|
|
||||||
|
(if chunk
|
||||||
|
(concatenate 'list
|
||||||
|
(list chunk)
|
||||||
|
(read-line-chunked
|
||||||
|
stream
|
||||||
|
seperator-line)))))
|
|
@ -0,0 +1,182 @@
|
||||||
|
(in-package :qotdd)
|
||||||
|
|
||||||
|
;; MULTI-LINE_STRING --> LIST_OF_LINES
|
||||||
|
(defun string-line-list (string)
|
||||||
|
"Return a list of lines from a multi-line string."
|
||||||
|
|
||||||
|
(cl-strings:split string (format nil "~%")))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; LIST_OF_STRINGS --> MULTI-LINE_STRING
|
||||||
|
(defun line-list-string (line-list)
|
||||||
|
"Turn a list of lines (string) into a multi-line string."
|
||||||
|
|
||||||
|
(reduce
|
||||||
|
(lambda (x y)
|
||||||
|
(format nil "~A~%~A" x y))
|
||||||
|
line-list))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; SINGLE-LINE_STRING PREFIX_SUBSTRING --> POST-PREFIX_SUBSTRING
|
||||||
|
(defun string-after-prefix (string prefix)
|
||||||
|
"Return substring after a `prefix` substring at the start of a string."
|
||||||
|
|
||||||
|
(cl-strings:clean
|
||||||
|
(cadr (cl-strings:split string prefix))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; MULTI-LINE_STRING QUERY --> LINE_CONTAINING_QUERY
|
||||||
|
(defun get-line (string query)
|
||||||
|
"Return a single line that query starts from a multi-line string."
|
||||||
|
|
||||||
|
(let ((line-number (position-line string query)))
|
||||||
|
|
||||||
|
(if line-number
|
||||||
|
(nth line-number (string-line-list string))
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; MULTI-LINE_STRING QUERY --> LINE_NUMBER
|
||||||
|
(defun position-line (string query)
|
||||||
|
"Return the line number that the string `query` starts off--
|
||||||
|
from a multi-line string."
|
||||||
|
|
||||||
|
(position 'T
|
||||||
|
(mapcar
|
||||||
|
(lambda (line)
|
||||||
|
(cl-strings:starts-with line query))
|
||||||
|
(string-line-list string))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; NUMBER MULTI-LINE_STRING --> SINGLE-LINE_STRING
|
||||||
|
(defun nth-string (n string)
|
||||||
|
"Return line of number `n` from a multi-line string."
|
||||||
|
|
||||||
|
(nth n (string-line-list string)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; STRING COLON_VARIABLE_NAME --> COLON_VALUE
|
||||||
|
(defun get-colon-value (string variable)
|
||||||
|
"Return a the value of a `colon variable`; I.E.,
|
||||||
|
a line of a string starting with `:` followed by a variable
|
||||||
|
name, a space, then the value of said `colon variable`.
|
||||||
|
|
||||||
|
Here's an example:
|
||||||
|
(setq *example-string*
|
||||||
|
\"Blah blah blah
|
||||||
|
:Date 1999
|
||||||
|
Blah blah blah\")
|
||||||
|
|
||||||
|
If you ran
|
||||||
|
(get-colon-value *example-string* \"Date\")
|
||||||
|
|
||||||
|
you would get
|
||||||
|
\"1999\" in return.
|
||||||
|
|
||||||
|
Mainly useful for multi-line strings, but your use-case might
|
||||||
|
involve a `colon variable` in a single-lined string."
|
||||||
|
|
||||||
|
(let ((variable-string (format nil ":~A " variable)))
|
||||||
|
|
||||||
|
(values
|
||||||
|
(ignore-errors
|
||||||
|
(string-after-prefix
|
||||||
|
(get-line string variable-string)
|
||||||
|
variable-string)))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; STRING COLON_VARIABLE_NAME --> STRING_WITHOUT_COLON_VARIABLE
|
||||||
|
(defun remove-colon-value (string variable)
|
||||||
|
"Remove the colon-variable declaration from a string."
|
||||||
|
|
||||||
|
(let ((variable-string (format nil ":~A " variable)))
|
||||||
|
|
||||||
|
(line-list-string
|
||||||
|
(remove
|
||||||
|
(get-line string variable-string)
|
||||||
|
(string-line-list string)
|
||||||
|
:test 'equal))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; STRING DESIRED_LENGTH [PREFIX] [SUFFIX] --> STRING_OF_DESIRED_LENGTH
|
||||||
|
(defun min-string-length (string target-length
|
||||||
|
&optional (prefix-substitute "0")
|
||||||
|
(suffix-substitute ""))
|
||||||
|
"If a string *must* be a certain length (formatting reasons), use
|
||||||
|
this function.
|
||||||
|
|
||||||
|
Returns a string of `length`, using the `prefix-substitutor` or
|
||||||
|
`suffix-substitutor` to beef up the character-count if it's too short.
|
||||||
|
|
||||||
|
If both `prefix` and `suffix` are defined, `prefix` is used.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
(min-string-length \"9\" 3 \"0\")
|
||||||
|
|
||||||
|
\"009\""
|
||||||
|
|
||||||
|
(let* ((string (format nil "~A" string))
|
||||||
|
(cur-length (length string)))
|
||||||
|
|
||||||
|
(if (eq cur-length target-length)
|
||||||
|
string
|
||||||
|
|
||||||
|
(min-string-length
|
||||||
|
(pad-string string prefix-substitute suffix-substitute)
|
||||||
|
target-length
|
||||||
|
prefix-substitute
|
||||||
|
suffix-substitute))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; STRING PREFIX SUFFIX --> STRING_ONE_OR_TWO_CHARS_LARGER
|
||||||
|
(defun pad-string (string prefix-substitute suffix-substitute)
|
||||||
|
"Increase the character-count of a string by 1; either by
|
||||||
|
adding a prefix-substitutor or a suffix-substitutor.
|
||||||
|
|
||||||
|
Set the substitutor you don't want to use to \"\".
|
||||||
|
If you set both to a value, then the count will increase by 2."
|
||||||
|
|
||||||
|
(format nil "~A~A~A" prefix-substitute string suffix-substitute))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; LIST_OF_SUBLISTS STRING --> SUBLIST_WITH_STRING_AS_CAR
|
||||||
|
(defun getf-string (list string)
|
||||||
|
"Get an item from a list by an identifying string in `car`.
|
||||||
|
I.E., if the string is 'apple', the first sublist like this:
|
||||||
|
('apple' 1 2 3)
|
||||||
|
will be returned."
|
||||||
|
|
||||||
|
(car (getf-strings list string)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; LIST_OF_SUBLISTS STRING --> LIST_OF_SUBLISTS_WITH_STRING_AS_CAR
|
||||||
|
(defun getf-strings (list string &optional (stack '()))
|
||||||
|
"Get items from list by an identifying string in `car`.
|
||||||
|
I.E., if the string is 'apple', any sublists like this:
|
||||||
|
('apple' 1 2 3)
|
||||||
|
will be returned."
|
||||||
|
|
||||||
|
;; just recurse through the list, adding each new matching
|
||||||
|
;; item to the `stack`
|
||||||
|
|
||||||
|
(if (and (< 0 (length list)) (listp list))
|
||||||
|
(if (ignore-errors
|
||||||
|
;; the item might not be a list; for our purposes, let's ignore that.
|
||||||
|
(equal
|
||||||
|
(car (car list)) ;; '( ( here ) )
|
||||||
|
string))
|
||||||
|
(getf-strings (cdr list) string (concatenate 'list stack (list (car list))))
|
||||||
|
(getf-strings (cdr list) string stack))
|
||||||
|
stack))
|
Ŝarĝante…
Reference in New Issue