"
44 | (buffer-name buf)
45 | (buffer-name buf))))
46 | (buffer-list)
47 | "\n"))))))))
48 |
49 | (elnode-start 'insideout-handler 8028 "localhost")
50 |
51 | ;; End
52 |
--------------------------------------------------------------------------------
/examples/elnode-org.el:
--------------------------------------------------------------------------------
1 | ;;; elnode-org.el
2 |
3 | (require 'elnode)
4 |
5 | (defun elnode-org-handler (httpcon)
6 | (elnode-docroot-for "~/work/org"
7 | with org-file
8 | on httpcon
9 | do (with-current-buffer (find-file-noselect org-file)
10 | (let ((org-html
11 | ;; This might throw errors so you could condition-case it
12 | (org-export-as-html 3 nil nil 'string)))
13 | (elnode-send-html httpcon org-html)))))
14 |
15 | (defun elnode-org-update-handler (httpcon)
16 | "Elnode handler to do org-mode updates.
17 |
18 | Specify `file-name' for the file to update, `node-match' for an
19 | org-agenda match, `in-node-match' to specify what will be
20 | replaced in the node matched org line, `replace-match' for the
21 | replacement."
22 | (elnode-method httpcon
23 | (POST
24 | (let* ((file-name (elnode-http-param httpcon "file-name"))
25 | (node-match (elnode-http-param httpcon "node-match"))
26 | (in-node-match (elnode-http-param httpcon "in-node-match"))
27 | (replace-match (elnode-http-param httpcon "replace-match")))
28 | (elnode-org--update
29 | file-name
30 | node-match
31 | in-node-match
32 | replace-match)))))
33 |
34 | (defun elnode-org--update (file-name node-match in-node-match replace-match)
35 | "Update org-mode specified FILE-NAME.
36 |
37 | NODE-MATCH specifies a match expression in the manner of org-agenda views.
38 |
39 | IN-NODE-MATCH specifies a string match expression used with the
40 | bounds of the matched node line.
41 |
42 | REPLACE-MATCH specifies the replacement for the IN-NODE-MATCH."
43 | (with-current-buffer (find-file-noselect file-name)
44 | (org-map-entries
45 | (lambda ()
46 | (replace-regexp
47 | in-node-match
48 | replace-match
49 | nil
50 | (point)
51 | (line-end-position)))
52 | node-match)))
53 |
54 | (elnode-start 'elnode-org-handler :port 8002)
55 |
56 |
57 | ;;; elnode-org.el ends here
58 |
--------------------------------------------------------------------------------
/examples/chat.el:
--------------------------------------------------------------------------------
1 | ;;; chat example - very simple webchat -*- lexical-binding: t -*-
2 | (require 'esxml)
3 | (require 'cl)
4 |
5 | (defvar chat-list '())
6 |
7 | (defun chat-add (user text)
8 | (add-to-list
9 | 'chat-list
10 | (list (current-time) user text)))
11 |
12 | (defun chat-list-since (since)
13 | (loop for rec in chat-list
14 | if (time-less-p since (car rec))
15 | collect rec))
16 |
17 | ;; And now the elnode
18 |
19 | (require 'elnode)
20 |
21 | (defun chat-comet-handler (httpcon)
22 | "Defer until there is new chat."
23 | (let ((entered (current-time)))
24 | (elnode-defer-until (chat-list-since entered)
25 | (elnode-send-json
26 | httpcon elnode-defer-guard-it :jsonp t))))
27 |
28 | (defun chat-send-handler (httpcon)
29 | (let* ((username (elnode-http-cookie httpcon "chatusername" t))
30 | (msg (elnode-http-param httpcon "msg")))
31 | (chat-add username msg)
32 | (elnode-send-json httpcon (json-encode '("thanks")))))
33 |
34 |
35 | ;; Main page setup stuff
36 |
37 | (defconst chat-dir (file-name-directory
38 | (or (buffer-file-name)
39 | load-file-name
40 | default-directory)))
41 |
42 | (defun chat-list-to-html ()
43 | "Return the `chat-list' as rows for initial chat display."
44 | (loop for entry in chat-list
45 | if (equal 3 (length entry))
46 | concat
47 | (esxml-to-xml
48 | `(tr
49 | ()
50 | (td
51 | ((class . ,(concat "username " (elt entry 1)))) ,(elt entry 1))
52 | (td ((class . "message")) ,(elt entry 2))))))
53 |
54 | (defun chat-main-templater ()
55 | "Return the `chat-list' as rows for initial chat display."
56 | (list
57 | (cons
58 | "messages"
59 | (let ((chat-list
60 | (subseq chat-list
61 | 0 (if (> (length chat-list) 10)
62 | 12
63 | (length chat-list)))))
64 | (chat-list-to-html)))))
65 |
66 | (defun chat-main-handler (httpcon)
67 | "The main handler."
68 | (let ((chat-js (concat chat-dir "chat.js"))
69 | (chat-html (concat chat-dir "chat.html"))
70 | (chat-css (concat chat-dir "styles.css")))
71 | (elnode-hostpath-dispatcher
72 | httpcon
73 | `(("^.*//chat/poll/" . chat-comet-handler)
74 | ("^.*//chat/send/" . chat-send-handler)
75 | ("^.*//chat.js" . ,(elnode-make-send-file chat-js))
76 | ("^.*//styles.css" . ,(elnode-make-send-file chat-css))
77 | ("^.*//" . ,(elnode-make-send-file
78 | chat-html
79 | :replacements 'chat-main-templater))))))
80 |
81 | ;;; chat.el ends here
82 |
--------------------------------------------------------------------------------
/default-wiki-index.creole:
--------------------------------------------------------------------------------
1 | = Elnode Wiki =
2 |
3 | This is Elnode's Wiki. It is based on the {{{creole}}} wiki language
4 | and is written completely in EmacsLisp.
5 |
6 | == What does it do? ==
7 |
8 | It does syntax coloring:
9 |
10 | {{{
11 | ##! emacs-lisp
12 | (defun elnode-wiki-handler (httpcon wikiroot)
13 | "A low level handler for Wiki operations.
14 |
15 | Send the Wiki page requested, which must be a file existing under
16 | the WIKIROOT, back to the HTTPCON.
17 |
18 | Update operations are protected by authentication."
19 | (elnode-method httpcon
20 | (GET
21 | (elnode-docroot-for wikiroot
22 | with target-path
23 | on httpcon
24 | do
25 | (if (equal target-path (expand-file-name (concat wikiroot "/")))
26 | (elnode-wiki-page httpcon (concat wikiroot "/index.creole"))
27 | (elnode-wiki-page httpcon target-path))))
28 | (POST
29 | (elnode-with-auth httpcon 'elnode-wiki-auth
30 | (let* ((path (elnode-http-pathinfo httpcon))
31 | (text (elnode-wiki--text-param httpcon)))
32 | (if (not (elnode-http-param httpcon "preview"))
33 | ;; A save request in which case save the new text and then
34 | ;; send the wiki text.
35 | (elnode-wiki--save-request httpcon wikiroot path text)
36 | ;; Might be a preview request in which case send back the WIKI
37 | ;; text that's been sent.
38 | (with-temp-file "/tmp/preview"
39 | (insert text))
40 | (elnode-wiki-send httpcon "/tmp/preview" path)))))))
41 | }}}
42 |
43 | It does links, for example to
44 | [[http://github.com/nicferrier/elwikicreole|Emacs Creole]] which is
45 | the Wiki render engine used to display pages.
46 |
47 | It does all the normal Wiki things like headings and lists.
48 |
49 | You can also do some special Emacs things, like org-mode tables:
50 |
51 | | Date | Amount | Description |
52 | |------------+--------+---------------------|
53 | | 2011-11-15 | 100.15 | Expensive lunch out |
54 | | 2011-11-18 | 7.30 | Dry cleaning |
55 | | 2011-11-21 | 22.50 | Takeaway curry |
56 | |------------+--------+---------------------|
57 | | | 129.95 | |
58 | #+TBLFM: @5$2=vsum(@I..@II)
59 |
60 | and lisp callouts:
61 |
62 | <<(
63 | (mapconcat
64 | (lambda (s)
65 | (format "* %s" s))
66 | '("which" "eval" "lisp" "and" "render" "the" "results")
67 | "\n")
68 | )>>
69 |
70 |
71 | == Authentication ==
72 |
73 | By default, the Wiki uses an authentication database in the Emacs
74 | instance running Elnode and the Wiki server.
75 |
76 | If you want to add a user to the Wiki so you can edit pages you can do this in Emacs:
77 |
78 | {{{
79 | M-x elnode-auth-user-add
80 | }}}
81 |
82 | and it will ask you for a username and a password. The user will be
83 | stored in a persistent database.
84 |
85 |
86 | == Where the Wiki pages are ==
87 |
88 | By default the Elnode Wiki stores files in your {{{~/.emacs.d}}}
89 | directory which is actually defined by the variable
90 | {{{user-emacs-directory}}} in Emacs.
91 |
92 | There is normally a directory {{{elnode}}} in that directory which
93 | contains directories for the Web server document root and the Wiki.
94 |
95 | The location of the Wiki files can be configured though, try:
96 |
97 | {{{
98 | M-x customize-variable [RET] elnode-wikiserver-wikiroot
99 | }}}
100 |
101 | == More customization ==
102 |
103 | There are many other things in Elnode's Wiki that can be customized,
104 | including the header and footer. Use:
105 |
106 | {{{
107 | M-x customize-group [RET] elnode-wikiserver [RET]
108 | }}}
109 |
110 | There is more to do with the Elnode Wiki server because there is so
111 | much that Emacs can do.
112 |
--------------------------------------------------------------------------------
/elnode_tutorial.org:
--------------------------------------------------------------------------------
1 |
2 | * suggestions
3 | ** How about going from "hello world" on?
4 | *** Start with that,
5 | *** and then, move on to publishing a static file,
6 | *** then a buffer,
7 | **** with calling functions to manipulate the buffer and re-present it?
8 | ** That'd save me a fair bit of tinkering :)
9 |
10 | * installing
11 | ** use elpa/marmalade
12 | * what elnode gives you by default
13 | ** require elnode
14 | *** elnode-init
15 | **** starts a server
16 | ***** on port 8000
17 |
18 |
19 | * hello world
20 | ** install elnode with marmalade
21 | ** open a new emacs buffer C-x C-f my-elnode-hello-world.el
22 | ** make a handler
23 | (defun my-elnode-hello-world-handler (httpcon)
24 | (elnode-http-start httpcon 200 '("Content-Type" . "text/html"))
25 | (elnode-http-return
26 | httpcon
27 | "
Hello World
"))
28 | (elnode-start my-elnode-hello-world-handler 8028 "localhost")
29 | ** now evaluate that with M-x eval-buffer
30 | ** now open localhost:8028 in your browser
31 |
32 | * publish some files
33 | ** elnode provides a webserver, more accurately a fileserver
34 | ** the webserver is turned on by default
35 | ** open localhost:8000 and you should see ~/public_html
36 | *** if you don't have ~/public_html then make one?
37 | *** or configure elnode-webserver-docroot
38 | ** make a new webserver
39 | *** make a new docroot
40 | **** mkdir ~/myspecialdocroot
41 | *** put an html file in there
42 | cat < ~/myspecialdocroot/saybum.html
43 |
44 |
BUM!
45 |
46 | *** open a new emacs buffer
47 | *** put the following lisp in
48 | (defvar my-elnode-webserver-handler
49 | (elnode-webserver-handler-maker "~/myspecialdocroot"))
50 | (elnode-start my-elnode-webserver-handler 8001 "localhost")
51 | *** now evaluate that with M-x eval-buffer
52 | *** now open localhost:8001/saybum.html
53 | *** now open localhost:8001
54 | **** you should see an automatic index
55 |
56 | * stopping a server
57 | ** stop 8028
58 | ** stop 8001
59 |
60 | * add a binding to the standard server
61 | ** we can add bindings to the standard elnode server
62 | ** go back to hello world - C-x b my-elnode-hello-world.el
63 | ** remove the server-start and add this:
64 | (add-to-list 'elnode-hostpath-default-table '("/helloworld/" . my-elnode-hello-world-handler))
65 | ** so now it should be:
66 | (defun my-elnode-hello-world-handler (httpcon)
67 | (elnode-http-start httpcon 200 '("Content-Type" . "text/html"))
68 | (elnode-http-return
69 | httpcon
70 | "
Hello World
"))
71 | (add-to-list 'elnode-hostpath-default-table '("/helloworld/" . my-elnode-hello-world-handler))
72 | ** now eval the buffer with M-x eval-buffer
73 | ** now open localhost:8000/helloworld/ in your browser
74 | ** just to prove the webserver is still there, open localhost:8000/
75 | *** check it's still the directory ~/public_html
76 | ** check the variable elnode-hostpath-default-table with C-h v elnode-hostpath-default-table
77 | Its value is (("/helloworld/" . my-elnode-hello-world-handler)
78 | ("[^/]+/.*" . elnode-webserver))
79 | ** elnode-hostpath-default-table can also be customized
80 | *** but any handler will have to be loaded so you probably need to package and load your elnode module
81 |
82 | * publishing something else?
83 | ** let's try and make an online editor
84 | ** make a new file my-elnode-editor.el
85 | (defvar my-elnode-editor-buffer (get-buffer-create "*my-elnode-editor-buffer*"))
86 |
87 | (defun my-elnode-editor-handler (httpcon)
88 | (elnode-http-start httpcon 200 '("Content-Type" . "text/plain"))
89 | (elnode-http-return
90 | httpcon
91 | (with-current-buffer my-elnode-editor-buffer
92 | (buffer-substring-no-properties (point-min) (point-max)))))
93 | ** eval that
94 | ** go type some data in *my-elnode-editor-buffer*
95 | ** then M-x elnode-start my-elnode-editor-handler 8002 localhost
96 | ** try and hit localhost:8002
97 | ** go update the buffer
98 | ** refresh the webpage
99 | ** but what about someone else updating the buffer?
100 | ** make another handler to handle updates
101 | (defun my-elnode-editor-update-handler (httpcon)
102 | (let ((change-text (elnode-http-param httpcon "change")))
103 | (with-current-buffer my-elnode-editor-buffer
104 | (goto-char (point-max))
105 | (insert (if (stringp change-text)
106 | change-text
107 | ""))))
108 | (elnode-http-start httpcon 302 '("Location" . "/"))
109 | (elnode-http-return httpcon))
110 | ** now we need to map these two handlers
111 | *** one to / and the other to /update/
112 | ** make a new variable
113 | (defvar my-elnode-editor-urls
114 | `(
115 | ("$" . my-elnode-editor-handler)
116 | ("update/.*$" . my-elnode-editor-update-handler)))
117 | ** and make a dispatcher handler for the urls
118 | (defun my-elnode-editor-dispatcher-handler (httpcon)
119 | (elnode-dispatcher httpcon my-elnode-editor-urls))
120 | *** a dispatcher handler is a handler that accepts requests and dispatches them to further handlers.
121 | *** moar about dispatcher handlers.
122 | ** now stop the old server
123 | ** M-x elnode-stop 8002
124 | ** Now start the new server with the dispatcher handler
125 | ** then M-x elnode-start my-elnode-editor-dispatcher-handler 8002 localhost
126 | ** now visit localhost:8002 and see the buffer
127 | ** now visit localhost:8002/update/?change=lah+dee+dah%0d and see the updated buffer
128 |
--------------------------------------------------------------------------------
/examples/demo.el:
--------------------------------------------------------------------------------
1 | ;; Demo handlers for elnode
2 |
3 | (require 'elnode)
4 |
5 | (defun nicferrier-handler (httpcon)
6 | "Demonstration function.
7 |
8 | This is a simple handler that just sends some HTML in response to
9 | any request."
10 | (let* ((host (elnode-http-header httpcon "Host"))
11 | (pathinfo (elnode-http-pathinfo httpcon))
12 | )
13 | (elnode-http-start httpcon 200 '("Content-type" . "text/html"))
14 | (elnode-http-return
15 | httpcon
16 | (format
17 | "
18 |
19 |
%s
20 | HELLO @ %s %s %s
21 |
22 |
23 | "
24 | (or (cdr (assoc "name" (elnode-http-params httpcon))) "no name")
25 | host
26 | pathinfo
27 | (elnode-http-version httpcon)))))
28 |
29 | (defun nicferrier-process-handler (httpcon)
30 | "Demonstration function
31 |
32 | This is a handler based on an asynchronous process."
33 | (let* ((host (elnode-http-header httpcon "Host"))
34 | (pathinfo (elnode-http-pathinfo httpcon))
35 | )
36 | (elnode-http-start httpcon 200 '("Content-type" . "text/plain"))
37 | (elnode-child-process httpcon "cat" (expand-file-name "~/elnode/node.el"))))
38 |
39 | (defun nicferrier-process-webserver (httpcon)
40 | "Demonstration webserver.
41 |
42 | Shows how to use elnode's built in webserver toolkit to make
43 | something that will serve a docroot."
44 | ;; Find the directory where this file is defined so we can serve
45 | ;; files from there
46 | (let ((docroot (file-name-directory
47 | (buffer-file-name
48 | (car
49 | (save-excursion
50 | (find-definition-noselect 'nicferrier-process-webserver nil)))))))
51 | (let ((webserver (elnode-webserver-handler-maker docroot)))
52 | (funcall webserver httpcon))))
53 |
54 | (defun nicferrier-mapper-handler (httpcon)
55 | "Demonstration function
56 |
57 | Shows how a handler can contain a dispatcher to make it simple to
58 | handle more complex requests."
59 | (elnode-dispatcher httpcon
60 | '(("$" . nicferrier-handler)
61 | ("nicferrier/$" . nicferrier-handler))))
62 |
63 | (defun nicferrier-post-handler (httpcon)
64 | "Handle a POST.
65 |
66 | If it's not a POST send a 400."
67 | (if (not (equal "POST" (elnode-http-method httpcon)))
68 | (progn
69 | (elnode-http-start httpcon 200 '("Content-type" . "text/html"))
70 | (elnode-http-return httpcon (format "
71 |
72 |
73 |
78 |
79 |
80 | " (elnode-http-pathinfo httpcon))))
81 | (let ((params (elnode-http-params httpcon)))
82 | (elnode-http-start httpcon 200 '("Content-type" . "text/html"))
83 | (elnode-http-return
84 | httpcon
85 | (format "
\n"
115 | `(:params
116 | (lambda ()
117 | (mapconcat
118 | (lambda (e)
119 | `(:paramcar ,(car e))
120 | :paramcdr ,(cdr e))
121 | params
122 | "\n"))))
123 | )
124 | )
125 | )
126 | )
127 |
128 |
129 | (defun nicferrier-everything-mapper-handler (httpcon)
130 | "Demonstration function
131 |
132 | Shows how a handler can contain a dispatcher to make it simple to
133 | handle more complex requests."
134 | (elnode-dispatcher
135 | httpcon
136 | `(("$" . nicferrier-post-handler)
137 | ("nicferrier/\\(.*\\)$" . ,(elnode-webserver-handler-maker "~/public_html")))))
138 |
139 |
140 |
141 | ;; need to update this so something can
142 | (defvar nicferrier-defer-switch nil)
143 |
144 | (defun nicferrier-defer-handler (httpcon)
145 | (if nicferrier-defer-switch
146 | (progn
147 | (elnode-http-start httpcon 200 '("Content-type" . "text/html"))
148 | (elnode-http-return httpcon "BING!")
149 | )
150 | (progn
151 | (setq nicferrier-defer-switch 't)
152 | (elnode-defer-now 'nicferrier-defer-handler))))
153 |
154 | ;; Boot elpad on port 8002
155 | (elnode-start 'nicferrier-defer-handler 8002 "localhost")
156 |
157 | ;; End
158 |
--------------------------------------------------------------------------------
/elnode.org:
--------------------------------------------------------------------------------
1 |
2 | * planned FAQ
3 | ** FAQ for installation
4 | *** why does it not start?
5 | **** have you got something running on port 8000?
6 | **** how to change the port and restart
7 |
8 | ** programming
9 | *** what's a hello world handler look like?
10 | **** the simplest elnode handler is
11 | (defun hello-world (httpcon)
12 | (elnode-send-html httpcon "Hello World"))
13 |
14 | *** how do I start a handler?
15 | **** using M-x elnode-start [RET] handler-name
16 | ***** the handler-name is any function
17 | ***** it will complete in the normal minibuffer way
18 | *** how can I make a handler serve some static files?
19 | **** so easy, like this:
20 | (setq my-webserver-handler (elnode-webserver-handler-maker "~/directory"))
21 | *** what if I want to do something a bit unusual to the file before it's served? like tempalting
22 | **** you need to write a proper handler for that
23 | **** let's say you want to replace {{{name}}} with a single word
24 | (defun my-files-handler (httpcon)
25 | (elnode-docroot-for DIRECTORY
26 | with target-filename
27 | on httpcon
28 | do
29 | (with-current-buffer (find-file-noselect target-filename)
30 | (elnode-http-start httpcon 200 '("Content-type" . "text/html"))
31 | (elnode-http-return
32 | httpcon
33 | (replace-regexp-in-string "{{{\\(.*\\)}}}" "bleh"
34 | (buffer-substring (point-min) (point-max)))))))
35 | *** the logging is crazy, can I turn if off?
36 | **** yep.
37 | M-x customize-variable [RET] elnode-log-files-directory
38 | **** and
39 | M-x customize-variable [RET] elnode-error-log-to-messages
40 | **** are 2 interesting ones
41 |
42 | ** Other questions
43 | *** What if my friends laugh at me for running a web browser in my editor?
44 | **** Get better friends? #emacs is a good source of fun people
45 | **** Alternately start a new business that uses elnode and pisses on the competition
46 | ***** because it is faster and more reliable.
47 | ***** then buy new friends.
48 | **** Or go back to using Ruby because Ruby is, ya know, really cool. Like your friends say.
49 |
50 |
51 | * auth stuff
52 | ** things auth requires
53 | *** test
54 | **** are you currently authenticated?
55 | **** most often this is testing a cookie
56 | **** on success do whatever you were going to do
57 | *** failure action
58 | **** redirect to a login page
59 | **** serve a login page
60 |
61 | ** login pages
62 | *** test
63 | **** are the credentials correct?
64 | *** success
65 | **** set a token to remember the request somehow
66 | ***** store something in the server?
67 | ****** so you can validatethe auth
68 | ******* login makes token
69 | ******* store token against username
70 | ******* put token:username:server-secretkey on cookie
71 | *******
72 | **** redirect to some page
73 | ***** maybe identified by a parameter or the referrer
74 | *** failure
75 | **** redirect to a login failed page
76 | **** return the same page with errors
77 | *** links
78 | **** registration page
79 |
80 |
81 | (with-elnode-auth
82 | (:test cookie
83 | :cookie-name my-auth
84 | :failure-type redirect
85 | :redirect "/mylogin")
86 | ...)
87 |
88 | :redirect could be:
89 |
90 | a string - which would point to a relative or absolute url which must
91 | be mapped indepentently
92 |
93 | a (dispatcher . handler) pair - a cons of a dispatcher and a handler,
94 | the dispatcher is automatically wrapped with a detector for a url
95 | that serves the handler
96 |
97 | a (dispatcher handler string) list - as for the
98 | dispatcher/handler cons but with the addition of the string to name
99 | the url to login
100 |
101 | ** idea about data/handlers
102 | *** for login, the processing of the authentication request (the username and password check) is the bit we can specify as part of the auth system
103 | **** it goes
104 | ***** get a username/password
105 | ****** and possibly other things like "cookie approval"
106 | ***** check against database
107 | ****** plus any other rules, like "cookie approval is yes"
108 | ***** make cookie
109 | ***** redirect to wherever we were supposed to be redirecting
110 | ****** this could have been specified
111 | ******* as a parameter
112 | ******* or it could be fixed
113 | ******* or looked up in the server side environment
114 | *** the bit we can't specify
115 | **** the look of the login page
116 | **** or even the url of the login page
117 | **** or how the login page works
118 | ***** we need to be able to support AJAX login
119 | ***** so you can login from the main page and from non-contextual flows
120 | *** it's frustrating because the only thing we care about on the login page is
121 | **** the login form, which is very specifiable
122 | **** particularly the url which the form POSTs to
123 | ***** which must have our auth handler on the end of it
124 | *** so we need a high level abstraction for dealing with this
125 | *** if we could specify interactive elements, like FORMs as
126 | **** a description of the data
127 | **** possibly a template
128 | ***** it should be possible to have a default template
129 | ***** client side template?
130 | **** the handler code to handle the call
131 | *** and then have those wrap in the same way as the (dispatcher . handler) form above
132 | *** reasons this would be good
133 | **** the separate description of the data means it could be used for ajax and context pages
134 | **** the template is optional
135 | ***** maybe we could have contextual templates as well
136 | ****** a template for ajax
137 | ****** a template for page
138 | **** the authentication processor is probably fixed
139 | **** this could be the tip of a larger abstraction to do better website building
140 |
141 | how about we make a function to return a wrap spec?
142 |
143 | like this:
144 |
145 | (with-elnode-auth
146 | (:test cookie
147 | :cookie-name my-auth
148 | :failure-type redirect
149 | :redirect (elnode-make-auth-wrap 'my-app form-template))
150 | ...)
151 |
152 | where (elnode-make-auth-wrap to-wrap template &optional path)
153 | => '(my-app (lambda (httpcon) (do-something-with template)) path)
154 |
155 | ** templates for auth - capturing some thoughts
156 | *** the current vogue is for mustache like templates
157 | *** these are dumb text replacers
158 | *** I prefer sed/xslt like templates
159 | **** not dumb, but more introspectively transformative
160 | *** can we make a simpler, less generic, transform language than xslt?
161 | *** it needs to transform data, such as json into HTML or XML
162 | *** things it might be
163 | **** a sequence of rules
164 | ***** for this bit of data, do this
165 | ****** { "password": "" } ->
166 | ***** questions about this
167 | ****** what's the pattern matching language???
168 | ****** how do we link the "things" together?
169 | ******* eg: BR tags?
170 | ******* wrapping individually in DIVs?
171 | **** a bunch of associated rules
172 | ***** wrap everything we produce in some tag
173 | ****** eg: FORM tags
174 |
175 |
176 | * v0.9.9 todo
177 | ** new async stuff with RLE
178 | ** default wiki page and webserver root
179 |
180 | * screencasts
181 | ** introducing elnode
182 | *** start with plain emacs24
183 | *** install marmalade
184 | *** install elnode
185 | *** what does elnode do out of the box?
186 | **** webserver
187 | **** wiki
188 | **** auth database
189 | **** logging
190 | ** programming with elnode
191 | *** start with some files
192 | *** make a webserver with elnode-webserver-handler-maker
193 | *** org-mode
194 | ** chat
195 | *** what do you need?
196 | **** a list to store chat
197 | ***** a list of triples? (username date text)
198 | **** a handler to receive the chats
199 | ***** a POST or something
200 | **** a handler for people to call to wait and receive chats
201 | ***** should use elnode-defer-or-do to check for new chats
202 | * v1.00 todo
203 | ** stuff
204 | *** vagrant image
205 | *** heroku update
206 | **** vulcan helps build the version of unix you need to host the build pack
207 | ***** http://quickleft.com/blog/hacking-heroku-with-custom-build-packs
208 | **** the buildpack
209 | ***** https://github.com/technomancy/heroku-buildpack-emacs/tree/master/bin
210 | *** ami?
211 | ** code
212 | *** defer bugs?
213 | *** logging to processes
214 | *** client server stuff
215 | *** htmlize bugs?
216 | **** these seem to be fixed by new creole
217 |
--------------------------------------------------------------------------------
/elnode_tutorial.creole:
--------------------------------------------------------------------------------
1 | = Getting Started with Elnode - the webserver for Emacs =
2 |
3 | This is a tutorial that will hopefully show you how to install and get
4 | started making web services with Elnode.
5 |
6 | Elnode is a node.js like webserver tool for Emacs. It let's you make
7 | and run web servers and services from inside Emacs.
8 |
9 |
10 | == Installing Elnode ==
11 |
12 | You should install Elnode from the package available on
13 | [[http://marmalade-repo.org/packages/elnode|Marmalade]].
14 |
15 | For dealing with package repositories check out the
16 | [[http://www.emacswiki.org/emacs/ELPA|Emacs Wiki]] but the short version
17 | is to add the following to your {{{.emacs}}} or your
18 | {{{.emacs.d/init.el}}}:
19 |
20 | {{{
21 | (add-to-list
22 | 'package-archives
23 | '("marmalade" . "http://marmalade-repo.org/packages/"))
24 | }}}
25 |
26 | And then do:
27 |
28 | {{{
29 | M-x list-packages
30 | }}}
31 |
32 | find Elnode in the list and press {{{i}}} or {{{RET}}} to install it.
33 |
34 | If you don't want to use packages you can just install {{{elnode.el}}}
35 | on your {{{load-path}}} somewhere and:
36 |
37 | {{{
38 | (require 'elnode)
39 | }}}
40 |
41 | == Hello World! ==
42 |
43 | Now we've installed Elnode you'll want to start making web services
44 | with it. Let's start with a Hello World example.
45 |
46 | open a new Emacs file
47 |
48 | {{{
49 | C-x C-f my-elnode-hello-world.el
50 | }}}
51 |
52 | enter the Lisp code for the handler, for that old time feel you could type this in, or if you're under 35, maybe just cut and paste
53 |
54 | {{{
55 | (defun my-elnode-hello-world-handler (httpcon)
56 | (elnode-http-start httpcon 200 '("Content-Type" . "text/html"))
57 | (elnode-http-return
58 | httpcon
59 | "
Hello World
"))
60 | (elnode-start 'my-elnode-hello-world-handler :port 8028 :host "localhost")
61 | }}}
62 |
63 | make the Lisp code //live//
64 |
65 | {{{
66 | M-x eval-buffer
67 | }}}
68 |
69 | now open [[http://localhost:8028]] in your browser - you should see //Hello World!//
70 |
71 |
72 | == Publish some files ==
73 |
74 | Elnode provides a builtin webserver that can serve files from a
75 | directory on your computer. The Elnode webserver is turned on by
76 | default (it's all configurable though).
77 |
78 | === The default webserver ===
79 |
80 | By default the webserver delivers files from:
81 |
82 | {{{
83 | ~/public_html
84 | }}}
85 |
86 | so if you have a public_html directory in your home directory then
87 | just browse to [[http://localhost:8000]] and you should see an index
88 | of that directory.
89 |
90 | If you don't have a {{{~/public_html}}} directory then just make one
91 | and drop a file or two in it.
92 |
93 | Alternately, try configuring the webserver root directory:
94 |
95 | {{{
96 | M-x customize-variable RET elnode-webserver-docroot RET
97 | }}}
98 |
99 | to another directory. Then try hitting [[http://localhost:8000]]
100 | again.
101 |
102 |
103 | === Making another webserver ===
104 | Now let's make a new webserver service.
105 |
106 | Make a new docroot:
107 |
108 | {{{
109 | mkdir ~/myspecialdocroot
110 | }}}
111 |
112 | Put an html file in there:
113 |
114 | {{{
115 | cat < ~/myspecialdocroot/saybum.html
116 |
117 |
BUM!
118 |
119 | }}}
120 |
121 | Now we have something to serve we can use Elnode to make the web service.
122 |
123 | Open a new Emacs file:
124 |
125 | {{{
126 | C-x C-f my-elnode-webserver.el
127 | }}}
128 |
129 | Add this Lisp:
130 |
131 | {{{
132 | (defconst my-elnode-webserver-handler
133 | (elnode-webserver-handler-maker "~/myspecialdocroot"))
134 | (elnode-start 'my-elnode-webserver-handler :port 8001 :host "localhost")
135 | }}}
136 |
137 | Now evaluate that with: {{{M-x eval-buffer}}}
138 |
139 | Now open [[http://localhost:8001/saybum.html]]
140 |
141 | Now open [[http://localhost:8001]] - you should see an automated index
142 | of {{{~/myspecialdocroot}}}.
143 |
144 | == Stopping a server ==
145 |
146 | We've started a couple of servers now. Let's stop the two servers that
147 | we've started:
148 |
149 | {{{
150 | M-x elnode-stop RET 8028 RET
151 | M-x elnode-stop RET 8001 RET
152 | }}}
153 |
154 | Those servers are now stopped and you won't be able to hit them.
155 |
156 | == Add a binding to the builtin server ==
157 |
158 | Instead of starting new servers all the time we can add bindings to
159 | the standard Elnode server. Why would we do this? I think using a
160 | separate server for developing something initially is a good idea, but
161 | then you either have something you want to package up as it's own
162 | server (a wiki engine you've developed and want to give to other
163 | people, for example) or you have something you want to make available
164 | in your own default server. Of course, it's always a judgement, the
165 | way URLs work mean that you can pretty much always make any service
166 | available on it's own server or under a URL on another one.
167 |
168 | Let's make our Hello World example available again by binding it to
169 | the default server (which is still listening on port 8000 if you
170 | haven't changed anything).
171 |
172 | Go back to hello world:
173 |
174 | {{{
175 | C-x b my-elnode-hello-world.el
176 | }}}
177 |
178 | Remove the {{{elnode-start}}} line and add this:
179 |
180 | {{{
181 | (add-to-list 'elnode-hostpath-default-table '("/helloworld/" . my-elnode-hello-world-handler))
182 | }}}
183 |
184 | So now it should look like this:
185 |
186 | {{{
187 | (defun my-elnode-hello-world-handler (httpcon)
188 | (elnode-http-start httpcon 200 '("Content-Type" . "text/html"))
189 | (elnode-http-return
190 | httpcon
191 | "
Hello World
"))
192 | (add-to-list 'elnode-hostpath-default-table '("/helloworld/" . my-elnode-hello-world-handler))
193 | }}}
194 |
195 | Now eval the buffer with {{{M-x eval-buffer}}}
196 |
197 | Now open [[http://localhost:8000/helloworld/]] in your browser.
198 |
199 | Just to prove the webserver is still there, open
200 | [[http://localhost:8000/]]. This should still show your
201 | {{{~/public_html}}} directory (or whatever you configured
202 | {{{elnode-webserver-docroot}}} to).
203 |
204 | Check the variable {{{elnode-hostpath-default-table}}} with {{{C-h v elnode-hostpath-default-table}}}
205 |
206 | The value should be something like:
207 |
208 | {{{
209 | (("/helloworld/" . my-elnode-hello-world-handler)
210 | ("[^/]+/.*" . elnode-webserver))
211 | }}}
212 |
213 | {{{elnode-hostpath-default-table}}} can also be customized to add more
214 | services. But any handler mapped in there will have to be loaded in at
215 | Emacs startup so you either need to package and load your Elnode code
216 | or put it in your {{{load-path}}} and {{{require}}} it from Emacs
217 | init.
218 |
219 | == A more advanced example - publishing a buffer ==
220 |
221 | So far, all the examples have been quite trivial. Though I hope you
222 | think it's interesting that you can do all these things quite easily
223 | from inside Emacs.
224 |
225 | But now let's try something harder - let's make an web based editor.
226 |
227 | This is an exercise that will grow with the tutorial. I hope you'll be
228 | interested in the first draft, even though it's going to be relatively
229 | simple.
230 |
231 | Make a new file {{{C-x C-f my-elnode-editor.el}}}.
232 |
233 | Add the following Lisp code:
234 |
235 | {{{
236 | (defvar my-elnode-editor-buffer (get-buffer-create "*my-elnode-editor-buffer*"))
237 |
238 | (defun my-elnode-editor-handler (httpcon)
239 | (elnode-http-start httpcon 200 '("Content-Type" . "text/plain"))
240 | (elnode-http-return
241 | httpcon
242 | (with-current-buffer my-elnode-editor-buffer
243 | (buffer-substring-no-properties (point-min) (point-max)))))
244 | }}}
245 |
246 | Eval that with {{{M-x eval-buffer}}}.
247 |
248 | Now go and type some text in ~*my-elnode-editor-buffer~*. This will be
249 | served by the editor service.
250 |
251 | Now let's start the service:
252 |
253 | {{{
254 | M-x elnode-start
255 | my-elnode-editor-handler
256 | 8002
257 | localhost
258 | }}}
259 |
260 | Now try and hit [[http://localhost:8002]] - you should see whatever
261 | you typed in the ~*my-elnode-editor-buffer~*.
262 |
263 | Try updating the text in the buffer and refreshing the browser. We're
264 | displaying that buffer whatever it has in it.
265 |
266 | Ok. So we've published a buffer. But what about someone else updating
267 | it?
268 |
269 | Let's make another handler to handle updates, add this to your {{{my-elnode-editor.el}}}:
270 |
271 | {{{
272 | (defun my-elnode-editor-update-handler (httpcon)
273 | (let ((change-text (elnode-http-param httpcon "change")))
274 | (with-current-buffer my-elnode-editor-buffer
275 | (goto-char (point-max))
276 | (if (stringp change-text)
277 | (insert change-text))))
278 | (elnode-http-start httpcon 302 '("Location" . "/"))
279 | (elnode-http-return httpcon))
280 | }}}
281 |
282 | Now we have two handlers we'll have to map them together
283 | somehow. Let's map one to the root ({{{/}}}) and one to
284 | {{{/update/}}}. Add the following code to {{{my-elnode-editor.el}}}:
285 |
286 | {{{
287 | (defconst my-elnode-editor-urls
288 | `(("$" . my-elnode-editor-handler)
289 | ("update/.*$" . my-elnode-editor-update-handler)))
290 | }}}
291 |
292 | And now we need to add a handler to do the dispatching for these URLs,
293 | add this to {{{my-elnode-editor.el}}} as well:
294 |
295 | {{{
296 | (defun my-elnode-editor-dispatcher-handler (httpcon)
297 | (elnode-dispatcher httpcon my-elnode-editor-urls))
298 | }}}
299 |
300 | //What is a dispatcher?// - a dispatcher is a handler that take a list
301 | of URL pattern mappings and works out, by reading the data from the
302 | HTTP connection, what handler should be invoked for what request.
303 |
304 | Now we have our new dispatcher based code we need to stop the old server:
305 |
306 | {{{
307 | M-x elnode-stop 8002
308 | }}}
309 |
310 | And now start the new server with the dispatcher handler:
311 |
312 | {{{
313 | M-x elnode-start
314 | my-elnode-editor-dispatcher-handler
315 | 8002
316 | localhost
317 | }}}
318 |
319 | Now visit [[http://localhost:8002]] and see the buffer as it stands
320 | and then visit
321 | [[http://localhost:8002/update/?change=%0dlah+dee+dah%0d]] and see the
322 | updated buffer.
323 |
324 | == More advanced again - Make a webapp around the service ==
325 |
326 | Let's take our editor on another step. Let's add some static files and
327 | have the Elnode handlers be called by client side Javascript.
328 |
329 | If we're going to add some static files, we'll need a webserver. We
330 | already know how to do that. Once we've got some javascript though,
331 | we'll probably not want to retrieve the text by {{{HTTP GET}}}ing the
332 | root url, so let's alter that binding to {{{/text/}}} as well:
333 |
334 | {{{
335 | (defconst my-elnode-editor-webserver-handler
336 | (elnode-webserver-handler-maker "~/my-directory"))
337 | "The webserver handler.")
338 |
339 | (defconst my-elnode-editor-urls
340 | '(("text/$" . my-elnode-editor-handler)
341 | ("update/.*$" . my-elnode-editor-update-handler)
342 | ("[^/]+/.*" . my-elnode-editor-webserver-handler)))
343 | }}}
344 |
345 | Obviously {{{~/my-directory}}} needs to be the place where you are
346 | going to save your HTML and Javascript files.
347 |
348 | Now we need those HTML and Javascript files. Let's make the HTML
349 | first:
350 |
351 | {{{
352 |
353 |
354 |
357 |
359 |
360 |
361 |
363 |
364 |
365 | }}}
366 |
367 | We're going to pull jQuery from Google's Content Delivery
368 | Network. We've put in a placeholder for our own Javascript file and
369 | other than that the HTML is really just a {{{textarea}}}
370 | element. We'll use //that// for putting the buffer text in.
371 |
372 | Now, what should the Javascript do?
373 |
374 | * when the page loads
375 | * make an AJAX call to Elnode for the buffer text
376 | * stick the received text into the {{{textarea}}}
377 |
378 | Ok. So here is {{{my-elnode-editor.js}}}:
379 |
380 | {{{
381 | var my_elnode_editor = (function () {
382 | var self = {
383 | /** Get the text from Emacs.
384 | */
385 | get_text: function () {
386 | $.ajax("/text/", {
387 | dataType: "text",
388 | success: function (data, textStatus, jqXHR) {
389 | $("#text").text(data);
390 | }
391 | });
392 | }
393 | };
394 | return self;
395 | })();
396 |
397 | $(document).ready(
398 | function () {
399 | my_elnode_editor.get_text();
400 | }
401 | );
402 | }}}
403 |
404 | Save this as {{{my-elnode-editor.js}}} (in whatever directory the
405 | webserver is serving) and save the HTML in the same directory, call it
406 | {{{my-elnode-editor.html}}}, say?
407 |
408 | You don't even have to restart the Elnode handler, because it already
409 | is pointing to the dispatcher handler. If you just:
410 |
411 | {{{
412 | M-x eval-buffer
413 | }}}
414 |
415 | this will re-evaluate the URL mappings. Now if you visit
416 | [[http://localhost:8002/my-elnode-editor.html]] you should see the
417 | webpage with the {{{textarea}}} and the text of your buffer.
418 |
419 |
420 |
421 | == That's all for now! ==
422 |
423 | This is as far as Nic has got writing the tutorial. More will come soon I hope:
424 |
425 | * {{{defer}}} with an example based around the editor service
426 | * debugging a running Elnode service
427 |
--------------------------------------------------------------------------------
/README.creole:
--------------------------------------------------------------------------------
1 | = Elnode =
2 |
3 | An evented IO webserver in Emacs Lisp.
4 |
5 |
6 | == Requirements ==
7 |
8 | Elnode will not run properly on anything less than Emacs 24. Elnode
9 | requires Emacs 24's lexical binding as it makes extensive use of
10 | closures.
11 |
12 |
13 | == Rationale ==
14 |
15 | Elnode is a great for these things:
16 |
17 | * nice simple server with few dependancies (just Emacs and {{{cat}}} basically)
18 | * prototyping webapps
19 | * browser testing
20 | * asynchronous apps, like chat apps
21 |
22 |
23 | == Installation ==
24 |
25 | Elnode is packaged in [[http://marmalade-repo.org/packages/elnode|marmalade]].
26 |
27 | For dealing with package repositories check out the
28 | [[http://www.emacswiki.org/emacs/ELPA|Emacs Wiki]] but the short version
29 | is to add the following to your {{{.emacs}}} or your
30 | {{{.emacs.d/init.el}}}:
31 |
32 | {{{
33 | (add-to-list
34 | 'package-archives
35 | '("marmalade" . "http://marmalade-repo.org/packages/"))
36 | }}}
37 |
38 | And then do:
39 |
40 | {{{
41 | M-x list-packages
42 | }}}
43 |
44 | find Elnode in the list and press {{{i}}} or {{{ENTER}}} to install it.
45 |
46 | If you don't want to use packages you can just install {{{elnode.el}}}
47 | on your {{{load-path}}} somewhere and:
48 |
49 | {{{
50 | (require 'elnode)
51 | }}}
52 |
53 | === Install from this repository ===
54 |
55 | You can make a package locally, with this repository:
56 |
57 | {{{
58 | M-x compile [RET] make clean all [RET]
59 | }}}
60 |
61 | And then you can install that package:
62 |
63 | {{{
64 | M-x package-install-file [RET] elnode-0.9.9.tar [RET]
65 | }}}
66 |
67 | You can also use the other Makefile targets, such as {{{test}}}.
68 |
69 | Unless you are developing Elnode I advise that you use the packaged
70 | version.
71 |
72 | == Out of the box ==
73 |
74 | When Elnode initializes it automatically starts a webserver and a Wiki
75 | engine.
76 |
77 | If you:
78 |
79 | {{{
80 | M-x customize-group
81 | elnode
82 | }}}
83 |
84 | you can alter a number of variables pertaining to the default
85 | configuration, including the directory used to keep files.
86 |
87 | By default the package installs files in your {{{.emacs.d}}} - it uses
88 | a directory called {{{elnode}}} for the Wiki root and the
89 | webroot. Both are configurable with Elnode config variables.
90 |
91 | You can also just ignore the built in stuff completely and write your
92 | own servers.
93 |
94 |
95 | == How does it work? ==
96 |
97 | The simplest thing that Elnode does is let you start a webserver on a directory:
98 |
99 | {{{
100 | M-x elnode-make-webserver [RET]
101 | Serve files from: [enter directory] [RET]
102 | TCP Port (try something over 8000): 8009 [RET]
103 | }}}
104 |
105 | and there will be a webserver started on port 8009 serving files from
106 | whatever directory you specified.
107 |
108 | Elnode's power is most visible to programmers though.
109 |
110 | You can define a handler function:
111 |
112 | {{{
113 | (defun my-test-handler (httpcon)
114 | "Demonstration function"
115 | (elnode-http-start httpcon 200 '("Content-type" . "text/html"))
116 | (elnode-http-return httpcon "HELLO!"))
117 | }}}
118 |
119 | And then start the server:
120 |
121 | {{{
122 | (elnode-start 'my-test-handler :port 8010 :host "localhost")
123 | }}}
124 |
125 | You can also start the server interactively... with:
126 |
127 | {{{
128 | M-x elnode-start
129 | }}}
130 |
131 | it interactively asks for the handler function and a port.
132 |
133 | === Stopping the server ===
134 |
135 | If you can remember the port you started your server on then you'll be
136 | able to stop it, like:
137 |
138 | {{{
139 | (elnode-stop 8010)
140 | }}}
141 |
142 | You can also stop interactively:
143 |
144 | {{{
145 | M-x elnode-stop
146 | }}}
147 |
148 |
149 |
150 | == API ==
151 |
152 | === Mapping paths to handlers ===
153 |
154 | {{{elnode-hostpath-dispatcher}}} takes a-list of path/handler mappings:
155 |
156 | {{{
157 | ##!emacs-lisp
158 | (defvar
159 | my-app-routes
160 | '(("^my-host.example.com//wiki/\\(.*\\)" . elnode-wikiserver)
161 | ("^admin.example.com//admintool/\\(.*\\)" . user-admin)
162 | ("^.*//\\(.*\\)" . elnode-webserver)))
163 |
164 | (defun root-handler (httpcon)
165 | (elnode-hostpath-dispatcher httpcon my-app-routes))
166 |
167 | (elnode-start 'root-handler :port 8009)
168 | }}}
169 |
170 | This will create a server on port 8009 being handled by
171 | {{{root-handler}}} which will root the requests to the appropriate handler.
172 |
173 | Any request for the host {{{my-host.example.com}}} with the path
174 | {{{/wiki/}}} will be sent to the Elnode Wiki engine.
175 |
176 | Any request for the host {{{admin.example.com}}} with the path
177 | {{{/admintool/}}} will be sent to the {{{user-admin}}} handler,
178 | presumably that is defined somewhere.
179 |
180 | Any other request will be sent to the default Elnode webserver.
181 |
182 | Elnode itself uses a hostpath dispatcher on the default Elnode server.
183 | This can actually be configured with the variable
184 | {{{elnode-hostpath-default-table}}}, so you can actually change the
185 | default behaviour of the Elnode default server just with Emacs config.
186 |
187 |
188 | The use of regexs in Elnode's mapping is supported by other
189 | tools. Sub-expressions are capturable in mapping support routines such
190 | as {{{elnode-docroot-for}}}.
191 |
192 | When a handler is called by {{{elnode-hostpath-dispatcher}}} then the
193 | parts of the match are available through the function
194 | {{{elnode-http-mapping}}}. So we could code the {{{user-admin}}}
195 | handler like this:
196 |
197 | {{{
198 | ##! emacs-lisp
199 | (defun user-admin (httpcon)
200 | (let ((username (elnode-http-mapping httpcon 1)))
201 | (user-admin-send-admin-page httpcon username)))
202 | }}}
203 |
204 | The {{{(elnode-http-mapping httpcon 1)}}} accesses the first
205 | sub-expression of the regex that caused the match:
206 |
207 | {{{
208 | ("^admin.example.com//admintool/\\(.*\\)" . user-admin)
209 | }}}
210 |
211 | so, everything AFTER the {{{admintool/}}}.
212 |
213 | Some tools in Elnode do this for you, so you don't have to. Again,
214 | look at {{{elnode-docroot-for}}}.
215 |
216 | === Serving files ===
217 |
218 | There are several helpers for serving files with Elnode. You can serve
219 | directories of files directly by making a webserver handler. A
220 | function {{{elnode-webserver-handler-maker}}} can make webservers:
221 |
222 | {{{
223 | ##! emacs-lisp
224 |
225 | (setq my-webserver
226 | (elnode-webserver-handler-maker "~/my-webroot"))
227 |
228 | (elnode-start my-webserver :port 8010)
229 | }}}
230 |
231 | The Elnode webserver also produces index pages and can be configured
232 | with a number of variables:
233 |
234 | * {{{elnode-webserver-index-page-template}}} defines the page template used for the index
235 | * {{{elnode-webserver-index-file-template}}} defines the template for each file in the index, normally it's just an A tag ponting to the file.
236 |
237 |
238 | === More controlled serving ===
239 |
240 | If you need more control over serving files you can write handlers
241 | with {{{elnode-docroot-for}}}. This does a lot of complex work for you
242 | to map a directory tree to a webserver namespace.
243 |
244 | This example shows how to use {{{elnode-docroot-for}}}
245 |
246 | {{{
247 | ##! emacs-lisp
248 |
249 | (defun elnode-org-handler (httpcon)
250 | (elnode-docroot-for "~/work/org"
251 | with org-file
252 | on httpcon
253 | do (with-current-buffer (find-file-noselect org-file)
254 | (let ((org-html
255 | ;; This might throw errors so you could condition-case it
256 | (org-export-as-html 3 nil nil 'string)))
257 | (elnode-send-html httpcon org-html)))))
258 | }}}
259 |
260 | The first argument is the directory of files which you want to serve,
261 | then {{{with variable}}} specifies the name of a variable to use in
262 | the body of the code which will be bound to the filename of the file
263 | the user wants. Then {{{on httpcon}}} specifies the HTTP connection to
264 | use and then {{{do ....}}} specifies the code to use.
265 |
266 | {{{elnode-docroot-for}}} processes incomming requests on the
267 | {{{httpcon}}} you specify by checking the request matches a file in
268 | the directory you specify (it sends a 404 if it does not find one).
269 |
270 | It also does last modified caching on the file and sends an HTTP 304
271 | response if the file has not been updated since the last request.
272 |
273 | If a matching file exists and the it is not cached then
274 | {{{elnode-docroot-for}}} runs the {{{do}}} code to send the response
275 | correctly.
276 |
277 | === Sending files ===
278 |
279 | Elnode also has {{{elnode-send-file}}} for sending files to the response,
280 | along with {{{elnode-docroot-for}}} this makes a powerful simple
281 | webserver tool. {{{elnode-send-file}}} can be used to send any
282 | arbitary file:
283 |
284 | {{{
285 | ##! emacs-lisp
286 | (defun my-status-page (httpcon)
287 | (elnode-http-start httpcon 200 '("Content-type" . "text/html"))
288 | (elnode-send-file httpcon "~/static-status-file.html"))
289 | }}}
290 |
291 | A handler that will only ever respond with one static file. Of
292 | course, this isn't very interesting, combined with
293 | {{{elnode-docroot-for}}} it can be used to serve directories and the
294 | like, or you could work out the filename to be sent with some other
295 | method.
296 |
297 | There is another use for {{{elnode-send-file}}} which is simple
298 | templating. You can pass parameters to {{{elnode-send-file}}} and it
299 | will template them into the file:
300 |
301 | {{{
302 | (defun my-templater(httpcon)
303 | (let ((hash (make-hash-table
304 | :test 'equal
305 | :data "username" "nicferrier")))
306 | (elnode-http-start httpcon 200 '("Content-type" . "text/html"))
307 | (elnode-send-file
308 | httpcon "~/my-template.html"
309 | :replacements hash)))
310 | }}}
311 |
312 | The template file must have sections marked up like:
313 |
314 | <>
317 |
318 | for each of the variables.
319 |
320 | This makes for simple but quite powerful templating.
321 |
322 | === Really Really simple file sending ===
323 |
324 | It's also possible to make send file functions automatically so if you
325 | want to map a handler that serves just one file in a dispatcher that's
326 | possible:
327 |
328 | {{{
329 | ##! emacs-lisp
330 | `(("^my-host.example.com//wiki/\\(.*\\)" . elnode-wikiserver)
331 | ("^.*//styles.css" . ,(elnode-make-send-file "~/mainstyles.css"))
332 | ("^.*//\\(.*\\)" . elnode-webserver))
333 | }}}
334 |
335 | It's also possible to use templating with this style of programming by
336 | passing a function returning the alist variable map as
337 | {{{:replacements}}}:
338 |
339 | {{{
340 | ##! emacs-lisp
341 | (defun my-templater ()
342 | '(("username" . "william occam")))
343 |
344 | `(("^my-host.example.com//wiki/\\(.*\\)" . elnode-wikiserver)
345 | ("^.*//styles.css" . ,(elnode-make-send-file
346 | "~/mainstyles.css"
347 | :replacements 'my-templater))
348 | ("^.*//\\(.*\\)" . elnode-webserver))
349 | }}}
350 |
351 | This makes templating and setting up very simple websites very easy
352 | indeed.
353 |
354 | === Accessing data in the HTTP request ===
355 |
356 | There are a bunch of functions that do what you would expect about
357 | data in the HTTP request:
358 |
359 | {{{
360 | ##! emacs-lisp
361 |
362 | (elnode-http-method httpcon)
363 | => "POST"
364 |
365 | (elnode-http-pathinfo httpcon)
366 | => "/wiki/blah.creole"
367 |
368 | (elnode-http-query httpcon)
369 | => "a=10&b=20&c=the+quick+brown+fox"
370 |
371 | (elnode-http-params httpcon)
372 | => (("a" . "10")("b" . "20")("c" . "the quick brown fox"))
373 |
374 | (elnode-http-param httpcon "username")
375 | => "nicferrier"
376 |
377 | (elnode-http-cookie httpcon "session-id")
378 | => "1213313"
379 |
380 | (elnode-http-header httpcon "Date")
381 | => "Mon, Feb 27 2012 22:10:21 GMT"
382 |
383 | (elnode-http-header httpcon 'date)
384 | => "Mon, Feb 27 2012 22:10:21 GMT"
385 |
386 | (elnode-http-header httpcon 'date :time) ;; with convert flag set to :time
387 | => (20299 65357)
388 | }}}
389 |
390 | Note that Elnode generally can accept symbol's as well as strings to
391 | name things, if it can't it's a bug,
392 | [[https://github.com/nicferrier/elnode/issues|please report it]].
393 |
394 | Also, Elnode can handle some conversions sometimes. I would like to
395 | respond to user demand about when and where to do that and what to
396 | do. Please give me feedback.
397 |
398 | === Elnode's raw data ===
399 |
400 | Elnode stores most of it's internal state on the connection object and
401 | it's all accessible, interesting properties and how to access them:
402 |
403 | {{{
404 | ##! emacs-lisp
405 |
406 | (process-get httpcon :elnode-http-status)
407 | => "GET / HTTP/1.1"
408 |
409 | (process-get httpcon :elnode-http-resource)
410 | => "/"
411 |
412 | (process-get httpcon :elnode-http-version)
413 | => "1.1"
414 | }}}
415 |
416 | These are not supported by Elnode at all, there is no guarantee that
417 | the names of these properties won't change. If you feel that you want
418 | official support (ie: a function) then make an issue on the Elnode
419 | github.
420 |
421 |
422 | == To Do? ==
423 |
424 | If you're playing with elnode but you can't think of anything to do with it...
425 |
426 | * an elpa repository written with elnode
427 | ** turn the package list into html
428 | ** allow packages to be downloaded from elnode
429 | ** upload of packages will require fixing the request management a little
430 | * an emacsclient with elnode
431 | ** write a command line client that submits data to the server over HTTP
432 | ** it should interact with the emacs user in the same way that emacs server does
433 | ** //why?// because then a single Emacs could have just 1 server socket open for all sorts of different roles
434 | * alter {{{elnode-webserver-handler-maker}}} to do indexing better
435 | ** take an optional index producing function?
436 | ** take keyword flags that set the behaviour?
437 | ** eg: {{{:doindexes 't }}}
438 | * browse-current-buffer
439 | ** start an elnode server on some random port exposing the current buffer
440 | ** automatically open a browser on the started server
441 |
--------------------------------------------------------------------------------
/elnode-wiki.el:
--------------------------------------------------------------------------------
1 | ;;; elnode-wiki.el --- a wiki with Elnode -*- lexical-binding: t -*-
2 |
3 | ;; Copyright (C) 2010, 2011, 2012 Nic Ferrier
4 |
5 | ;; Author: Nic Ferrier
6 | ;; Maintainer: Nic Ferrier
7 | ;; Created: 5th October 2010
8 | ;; Keywords: lisp, http, hypermedia
9 |
10 | ;; This file is NOT part of GNU Emacs.
11 |
12 | ;; This program is free software; you can redistribute it and/or modify
13 | ;; it under the terms of the GNU General Public License as published by
14 | ;; the Free Software Foundation, either version 3 of the License, or
15 | ;; (at your option) any later version.
16 |
17 | ;; This program is distributed in the hope that it will be useful,
18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 | ;; GNU General Public License for more details.
21 |
22 | ;; You should have received a copy of the GNU General Public License
23 | ;; along with this program. If not, see .
24 |
25 | ;;; Commentary:
26 | ;;
27 | ;; This is a Wiki Engine completely written in EmacsLisp, using Elnode
28 | ;; as a server.
29 | ;;
30 | ;;; Source code
31 | ;;
32 | ;; elnode's code can be found here:
33 | ;; http://github.com/nicferrier/elnode
34 |
35 | ;;; Style note
36 | ;;
37 | ;; This codes uses the Emacs style of:
38 | ;;
39 | ;; elnode-wiki--private-function
40 | ;;
41 | ;; for private functions.
42 |
43 |
44 | ;;; Code:
45 |
46 | (require 'elnode)
47 | (require 'db)
48 | (eval-when-compile 'fakir)
49 | (require 'creole nil 't)
50 | ;;(require 'vc)
51 |
52 | (defgroup elnode-wikiserver nil
53 | "A Wiki server written with Elnode."
54 | :group 'elnode)
55 |
56 | ;;;###autoload
57 | (defconst elnode-wikiserver-wikiroot-default
58 | (expand-file-name (concat elnode-config-directory "wiki/"))
59 | "The default location of the wiki root.
60 |
61 | This is used to detect whether elnode needs to create this
62 | directory or not.")
63 |
64 | ;;;###autoload
65 | (defcustom elnode-wikiserver-wikiroot
66 | elnode-wikiserver-wikiroot-default
67 | "The root for the Elnode wiki files.
68 |
69 | This is where elnode-wikiserver serves wiki files from."
70 | :type '(directory)
71 | :group 'elnode-wikiserver)
72 |
73 | (defcustom elnode-wikiserver-body-header
74 | ""
75 | "HTML BODY preamable of a rendered Wiki page."
76 | :type '(string)
77 | :group 'elnode-wikiserver)
78 |
79 | (defcustom elnode-wikiserver-body-footer
80 | ""
93 | "HTML BODY footter for a rendered Wiki page."
94 | :type '(string)
95 | :group 'elnode-wikiserver)
96 |
97 | (defcustom elnode-wikiserver-body-footer-not-loggedin
98 | ""
101 | "HTML BODY footter for a rendered Wiki page."
102 | :type '(string)
103 | :group 'elnode-wikiserver)
104 |
105 | (defun elnode-wiki--setup ()
106 | "Setup the wiki."
107 | (elnode--dir-setup elnode-wikiserver-wikiroot
108 | elnode-wikiserver-wikiroot-default
109 | "default-wiki-index.creole"
110 | "index.creole"))
111 |
112 | (ert-deftest elnode-wiki--setup ()
113 | "Test the wiki setup function."
114 | ;; Test that it's not called if we can't find the source file
115 | (let (called)
116 | (flet ((make-directory (dirname &optional parents)
117 | (setq called t))
118 | ;; We fake buffer-file-name so that the wiki-index-source
119 | ;; will not be found
120 | (buffer-file-name ()
121 | "/tmp/elnode/elnode-wiki.el"))
122 | (elnode-wiki--setup)
123 | (should-not called)))
124 | ;; Test that when called we're going to copy things right
125 | (let (make-dir
126 | copy-file
127 | ;; Ensure the configurable wikiroot is set to the default
128 | (elnode-wikiserver-wikiroot elnode-wikiserver-wikiroot-default))
129 | (flet ((make-directory (dirname &optional parents)
130 | (setq make-dir (list dirname parents)))
131 | (dired-copy-file (from to ok-flag)
132 | (setq copy-file (list from to ok-flag)))
133 | ;; Mock the source filename environment
134 | (buffer-file-name ()
135 | "/tmp/elnode--wiki-setup-test/elnode-wiki.el")
136 | (file-exists-p (filename)
137 | (equal
138 | filename
139 | "/tmp/elnode--wiki-setup-test/default-wiki-index.creole")))
140 | (elnode-wiki--setup)
141 | (should
142 | (equal
143 | (list
144 | ;; This is the dir we should make
145 | '("/home/nferrier/.emacs.d/elnode/wiki/" t)
146 | ;; This is the copy file spec
147 | '("/tmp/elnode--wiki-setup-test/default-wiki-index.creole"
148 | "/home/nferrier/.emacs.d/elnode/wiki/index.creole"
149 | nil))
150 | ;; So this is the directory that make-directory will create
151 | ;; and the copy-file spec
152 | (list make-dir copy-file))))))
153 |
154 | (defun elnode--wiki-call (out-buf page-text page)
155 | "Call a wiki page sending output OUT-BUF.
156 |
157 | The page is faked with PAGE-TEXT."
158 | (flet
159 | ((elnode--worker-lisp-helper (child-lisp)
160 | `((progn
161 | (require 'creole)
162 | (require 'cl)
163 | (flet ((creole--get-file (filename)
164 | (let ((buf (get-buffer-create "wikibuf")))
165 | (with-current-buffer buf
166 | (insert ,page-text))
167 | buf)))
168 | ,@child-lisp)))))
169 | (elnode-wait-for-exit
170 | (elnode-worker-elisp
171 | out-buf
172 | ((target page)
173 | (page-info page)
174 | (header elnode-wikiserver-body-header)
175 | (footer elnode-wikiserver-body-footer))
176 | (require 'creole)
177 | (creole-wiki
178 | target
179 | :destination t
180 | :variables `((page . ,page-info))
181 | :body-header header
182 | :body-footer footer)))))
183 |
184 | ;; Deprecated
185 | (defun elnode-wiki-send (httpcon wikipage &optional pageinfo)
186 | "Sends the WIKIPAGE to the HTTPCON.
187 |
188 | If PAGEINFO is specified it's the HTTP path to the Wiki page.
189 |
190 | Uses Elnode's worker elisp stuff which is now deprecated."
191 | (elnode-http-start httpcon 200 `("Content-type" . "text/html"))
192 | (let ((page (or pageinfo (elnode-http-pathinfo httpcon))))
193 | (elnode-worker-elisp
194 | httpcon
195 | ((target wikipage)
196 | (page-info page)
197 | (header elnode-wikiserver-body-header)
198 | (footer elnode-wikiserver-body-footer))
199 | (require 'creole)
200 | (creole-wiki
201 | target
202 | :destination t
203 | :variables `((page . ,page-info))
204 | :body-header header
205 | :body-footer footer))))
206 |
207 | (define-obsolete-function-alias
208 | 'elnode-wiki-send
209 | 'elnode-wiki-page
210 | "24.1"
211 | "The worker elisp code that this depends on is deprecated in
212 | favour of Enode RLE.")
213 |
214 | (defvar elnode-wiki-page-use-rle nil
215 | "Whether to use RLE for this wiki or not.
216 |
217 | The RLE stuff is not really stable yet so this is a switch that
218 | let's developers play with but does not affect use.")
219 |
220 | (defun elnode-wiki-page-rle (httpcon wikipage &optional pageinfo)
221 | "Creole render the WIKIPAGE to the HTTPCON.
222 |
223 | If PAGEINFO is specified it's the HTTP path to the Wiki page.
224 |
225 | This version uses RLE which renders the Wiki page in a child
226 | Emacs."
227 | (let ((authenticated (elnode-http-cookie httpcon "elnodeauth")))
228 | (let ((page-info (or pageinfo (elnode-http-pathinfo httpcon)))
229 | (header elnode-wikiserver-body-header)
230 | (footer (if authenticated
231 | elnode-wikiserver-body-footer
232 | elnode-wikiserver-body-footer-not-loggedin)))
233 | (elnode-async-do
234 | httpcon
235 | requires (creole elnode)
236 | with-environment ((target wikipage)
237 | (page-info page-info)
238 | (header header)
239 | (footer footer))
240 | do
241 | (creole-wiki
242 | target
243 | :destination t
244 | :variables `((page . ,page-info))
245 | :body-header header
246 | :body-footer footer)))))
247 |
248 | (defun elnode-wiki-page (httpcon wikipage &optional pageinfo)
249 | "Creole render a WIKIPAGE back to the HTTPCON."
250 | (if elnode-wiki-page-use-rle
251 | (elnode-wiki-page-rle httpcon wikipage pageinfo)
252 | ;; Otherwise just do it
253 | (elnode-http-start httpcon 200 `("Content-type" . "text/html"))
254 | (with-stdout-to-elnode httpcon
255 | (let ((page-info (or pageinfo (elnode-http-pathinfo httpcon)))
256 | (header elnode-wikiserver-body-header)
257 | (footer (if-elnode-auth httpcon 'elnode-wiki-auth
258 | elnode-wikiserver-body-footer
259 | elnode-wikiserver-body-footer-not-loggedin)))
260 | (creole-wiki
261 | wikipage
262 | :destination t
263 | :variables (list (cons 'page page-info))
264 | :body-header header
265 | :body-footer footer)))))
266 |
267 | (defun elnode-wiki--text-param (httpcon)
268 | "Get the text param from HTTPCON and convert it."
269 | (replace-regexp-in-string
270 | "\r" "" ; browsers send text in DOS line ending format
271 | (elnode-http-param httpcon "wikitext")))
272 |
273 | (defun elnode-wiki--save-request (httpcon wikiroot path text)
274 | "Process an update request."
275 | (let* ((page (if path
276 | (save-match-data
277 | (string-match "/wiki/\\(.*\\)$" path)
278 | (match-string 1 path))))
279 | (comment (elnode-http-param httpcon "comment"))
280 | (file-name (if (equal page "")
281 | (concat wikiroot "index.creole")
282 | (concat (file-name-as-directory wikiroot) page)))
283 | (buffer (find-file-noselect file-name)))
284 | (with-current-buffer buffer
285 | (erase-buffer)
286 | (insert text)
287 | (save-buffer)
288 | (let ((git-buf
289 | (get-buffer-create
290 | (generate-new-buffer-name
291 | "* elnode wiki commit buf *"))))
292 | (shell-command
293 | (format "git commit -m '%s' %s" comment file-name)
294 | git-buf)
295 | (kill-buffer git-buf))
296 | (elnode-wiki-page httpcon file-name))))
297 |
298 | (defun elnode-wiki-handler (httpcon wikiroot)
299 | "A low level handler for Wiki operations.
300 |
301 | Send the Wiki page requested, which must be a file existing under
302 | the WIKIROOT, back to the HTTPCON.
303 |
304 | Update operations are protected by authentication."
305 | (elnode-method httpcon
306 | (GET
307 | (elnode-docroot-for wikiroot
308 | with target-path
309 | on httpcon
310 | do
311 | (if (equal target-path (expand-file-name (concat wikiroot "/")))
312 | (elnode-wiki-page httpcon (concat wikiroot "/index.creole"))
313 | (elnode-wiki-page httpcon target-path))))
314 | (POST
315 | (with-elnode-auth httpcon 'elnode-wiki-auth
316 | (let* ((path (elnode-http-pathinfo httpcon))
317 | (text (elnode-wiki--text-param httpcon)))
318 | (if (not (elnode-http-param httpcon "preview"))
319 | ;; A save request in which case save the new text and then
320 | ;; send the wiki text.
321 | (elnode-wiki--save-request httpcon wikiroot path text)
322 | ;; Might be a preview request in which case send back the WIKI
323 | ;; text that's been sent.
324 | (with-temp-file "/tmp/preview"
325 | (insert text))
326 | (elnode-wiki-send httpcon "/tmp/preview" path)))))))
327 |
328 | ;;;###autoload
329 | (defun elnode-wikiserver-test ()
330 | "Test whether we should serve Wiki or not."
331 | (featurep 'creole))
332 |
333 | ;;;###autoload
334 | (define-elnode-handler elnode-wikiserver (httpcon)
335 | "Serve Wiki pages from `elnode-wikiserver-wikiroot'.
336 |
337 | HTTPCON is the request.
338 |
339 | The Wiki server is only available if the `creole' package is
340 | provided. Otherwise it will just error."
341 | (if (not (elnode-wikiserver-test))
342 | (elnode-send-500 httpcon "The Emacs feature 'creole is required.")
343 | (elnode-wiki--setup)
344 | (elnode-wiki-handler httpcon elnode-wikiserver-wikiroot)))
345 |
346 | (defvar elnode-wiki-db
347 | (db-make
348 | `(db-hash
349 | :filename
350 | ,(expand-file-name
351 | (concat elnode-config-directory "elnode-wiki-auth")))))
352 |
353 | ;; Define the authentication scheme for the wiki
354 | (elnode-auth-define-scheme
355 | 'elnode-wiki-auth
356 | :auth-db elnode-wiki-db
357 | :redirect (elnode-auth-make-login-wrapper
358 | 'elnode-wikiserver
359 | :target "/wiki/login/"))
360 |
361 |
362 | ;;; Tests
363 |
364 | (ert-deftest elnode-wiki-page ()
365 | "Full stack Wiki test."
366 | (with-elnode-mock-server
367 | ;; The dispatcher function
368 | (lambda (httpcon)
369 | (let ((elnode-wikiserver-wikiroot "/home/elnode/wiki"))
370 | (elnode-hostpath-dispatcher
371 | httpcon
372 | '(("[^/]*//wiki/\\(.*\\)" . elnode-wikiserver))))) t
373 | (fakir-mock-file (fakir-file
374 | :filename "test.creole"
375 | :directory "/home/elnode/wiki"
376 | :content "= Hello World =\nthis is a creole wiki file!\n")
377 | (let* ((elnode--do-error-logging nil)
378 | (elnode--do-access-logging-on-dispatch nil))
379 | (should-elnode-response
380 | (elnode-test-call "/wiki/test.creole")
381 | :status-code 200
382 | :body-match ".*
Hello World
.*")))))
383 |
384 | (provide 'elnode-wiki)
385 |
386 | ;;; elnode-wiki.el ends here
387 |
--------------------------------------------------------------------------------
/elnode-rle.el:
--------------------------------------------------------------------------------
1 | ;;; elnode-rle.el --- Remote Lisp Executiion with Elnode -*- lexical-binding: t -*-
2 |
3 | ;; Copyright (C) 2012 Nic Ferrier
4 |
5 | ;; Author: Nic Ferrier
6 | ;; Keywords: lisp, hypermedia, processes
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 | ;;
23 | ;; This is an elnode handler and tools for doing asynchrous
24 | ;; programming.
25 | ;;
26 | ;; The idea is that you can setup associated child processes and pass
27 | ;; them work to do and receive their output over HTTP.
28 |
29 | ;;; Code:
30 |
31 | (require 'elnode)
32 | (require 'web)
33 | (require 'loadhist)
34 | (require 'server)
35 |
36 | (defun elnode-rle--handler (httpcon)
37 | "Remote Lisp Evaluator handler.
38 |
39 | This can be spawned in a client to allow any lisp code to be
40 | passed over the client-server link."
41 | (let* ((lisp-to-run (elnode-http-param httpcon "lisp"))
42 | (lisp
43 | (if lisp-to-run
44 | (car (read-from-string lisp-to-run))))
45 | (bindings-to-use (elnode-http-param httpcon "bindings"))
46 | (bindings
47 | (if bindings-to-use
48 | (car (read-from-string bindings-to-use))))
49 | (to-eval (list 'let bindings lisp)))
50 | (elnode-http-start httpcon 200 '("Content-type" . "text/plain"))
51 | (let ((nomessage t))
52 | (with-stdout-to-elnode httpcon
53 | (eval to-eval)))))
54 |
55 | (ert-deftest elnode-rle--handler ()
56 | "Test the Remote Lisp Evaluator handler."
57 | (flet ((lisp-encode (param lisp)
58 | (cons param (format "%S" lisp)))
59 | (do-test (lisp bindings)
60 | (fakir-mock-process
61 | :httpcon
62 | ((:elnode-http-params (list lisp bindings)))
63 | (elnode-rle--handler :httpcon)
64 | (with-current-buffer (process-buffer :httpcon)
65 | (goto-char (point-min))
66 | ;; Find the header end.
67 | (re-search-forward "\r\n\r\n" nil 't)
68 | (buffer-substring (point) (point-max))))))
69 | (should
70 | (equal
71 | ;; Match the content transfer encoded
72 | "c\r\nhello world!\r\n0\r\n\r\n"
73 | (let*
74 | ((lisp (lisp-encode
75 | "lisp" '(let ((a "hello world!")) (princ a))))
76 | (bindings (lisp-encode
77 | "bindings" '((a 10)(b 20)))))
78 | (do-test lisp bindings))))
79 | (should
80 | (equal
81 | "2\r\n30\r\n0\r\n\r\n"
82 | (let*
83 | ((lisp (lisp-encode
84 | "lisp" '(let ((a (+ b 10))) (princ a))))
85 | (bindings (lisp-encode
86 | "bindings" '((a 10)(b 20)))))
87 | (do-test lisp bindings))))))
88 |
89 | (defvar elnode-rle--servers (make-hash-table :test 'equal)
90 | "The hash of RLE servers available.")
91 |
92 | (defun elnode-rle--load-path-ize (lisp)
93 | "Wrap LISP in the current load-path."
94 | (concat
95 | ;; There is a very strange thing with sending lisp to
96 | ;; (read) over a piped stream... (read) can't cope with
97 | ;; multiple lines; so we encode newline here.
98 | ;;(replace-regexp-in-string
99 | ;; "\n"
100 | ;; "\\\\n"
101 | (format "(progn (setq load-path (quote %S)) %s)"
102 | (append (list default-directory) load-path)
103 | lisp)))
104 |
105 | (defun elnode-rle--handler-lisp (to-require)
106 | "Return a file with Lisp to start Elnode with TO-REQUIRE.
107 |
108 | Used to construct the lisp to send. You're unlikely to need to
109 | override this at all, the function is just here to make the
110 | implementation easier to debug.
111 |
112 | TO-REQUIRE is a list of things to require, currently only 1 is
113 | allowed."
114 | (let ((temp-file
115 | (make-temp-file
116 | (format "elnode-rle-%s" (symbol-name to-require)))))
117 | (with-temp-file temp-file
118 | (insert
119 | (elnode-rle--load-path-ize
120 | (format "(progn
121 | (setq elnode-do-init nil)
122 | (setq elnode--do-error-logging nil)
123 | (require (quote %s))
124 | (require (quote elnode-rle))
125 | (toggle-debug-on-error)
126 | (setq elnode-rle-port (elnode-find-free-service))
127 | (elnode-start 'elnode-rle--handler :port elnode-rle-port)
128 | (print (format \"\\nelnode-port=%%d\\n\" port)))"
129 | to-require))))
130 | temp-file))
131 |
132 | (defun elnode-rle--httpcon-mapper (client-header
133 | client-data
134 | elnode-httpcon
135 | &optional end-callback)
136 | "Elnode specific client connection to HTTP connection mapper.
137 |
138 | Maps client async data responses to an elnode server response."
139 | (unless (process-get elnode-httpcon :elnode-rle-header-sent)
140 | (elnode-http-start
141 | elnode-httpcon
142 | (gethash 'status-code client-header))
143 | (process-put elnode-httpcon :elnode-rle-header-sent t))
144 | (if (eq client-data :done)
145 | (elnode-http-return elnode-httpcon) ; return if we're done
146 | ;; Else just send the data
147 | (elnode-http-send-string elnode-httpcon client-data)))
148 |
149 | (defun elnode-rle--client-data-mapper (con header data stream end-callback)
150 | "Recevies data from the RLE server and sends it to the STREAM.
151 |
152 | END-CALLBACK is to be called when the client sees EOF."
153 | (cond
154 | ((processp stream) ; this should really elnode-http-p
155 | (elnode-rle--httpcon-mapper header data stream end-callback))
156 | ((bufferp stream)
157 | (if (not (eq data :done))
158 | (with-current-buffer stream
159 | (save-excursion
160 | (goto-char (point-max))
161 | (insert data)))
162 | ;; Process is done.
163 | (and (functionp end-callback)
164 | (funcall end-callback header))))))
165 |
166 | (defun elnode-rle--call-mapper (data-to-send stream port
167 | &optional end-callback)
168 | "Make a client call to PORT mapping response to STREAM.
169 |
170 | When it finishes, call END-CALLBACK, if present, with the header."
171 | (web-http-post
172 | (lambda (con header data)
173 | (elnode-rle--client-data-mapper
174 | con
175 | header
176 | data
177 | stream
178 | end-callback))
179 | "/"
180 | :host "localhost"
181 | :port port
182 | :data data-to-send
183 | :mime-type "application/x-elnode"
184 | :mode 'stream))
185 |
186 | (defun elnode-rle--make-server (to-require)
187 | "Make an RLE server, a child Emacs running the RLE handler.
188 |
189 | Return a proc that represents the child process. The child
190 | process has a property `:exec' which is a function that calls the
191 | RLE handler in the child's Elnode server (waiting for the server
192 | to start first and provide the relevant port) by calling
193 | `elnode-rle-call-mapper' with the stream from the `:exec' call
194 | and the child's remote HTTP port.
195 |
196 | The `:exec' proc will signal `elnode-rle-child-port' if the child
197 | server does not start properly." ; yes. I know it's bloody complicated.
198 | (let* ((proc-buffer
199 | (get-buffer-create
200 | (format "* %s *" "thingy")))
201 | (emacsrun
202 | "/usr/bin/emacs -Q --daemon=elnode-debugit")
203 | (proc
204 | (start-process-shell-command
205 | "elnode-rle-server"
206 | proc-buffer
207 | emacsrun))
208 | (file-of-lisp
209 | (elnode-rle--handler-lisp
210 | to-require)))
211 | ;; Start elnode in it
212 | (server-eval-at "elnode-debugit" `(load-file ,file-of-lisp))
213 | (process-put proc :daemonhandle "elnode-debugit")
214 | (process-put
215 | proc
216 | :port
217 | (server-eval-at
218 | (process-get proc :daemonhandle)
219 | 'elnode-rle-port))
220 | ;; Collect the port from the remote Emacs
221 | ;; - FIXME this should also collect the secure token
222 | (set-process-filter
223 | proc
224 | (lambda (proc data)
225 | ;; Optional delay for test reasons
226 | (with-current-buffer (process-buffer proc)
227 | (save-excursion
228 | (goto-char (point-max))
229 | (insert data)))))
230 | ;; Make a handler to call the server
231 | (process-put
232 | proc :exec
233 | (lambda (data stream &optional end-callback)
234 | (let ((ephemeral-port (process-get proc :port)))
235 | (elnode-rle--call-mapper data stream ephemeral-port end-callback))))
236 | proc))
237 |
238 | (defun elnode-rle--sender (stream to-require bindings body
239 | &optional end-callback)
240 | "Make a call using a client to the RLE server elsewhere.
241 |
242 | The RLE server is reused over TO-REQUIRE, if it's not already
243 | existing, it is created."
244 | (let ((server (gethash to-require elnode-rle--servers)))
245 | ;; Make the server if we don't have it
246 | (unless server
247 | (setq server
248 | (puthash to-require
249 | (elnode-rle--make-server (car to-require))
250 | elnode-rle--servers)))
251 | ;; Now make the call to the server
252 | (let ((data (make-hash-table :test 'equal)))
253 | (puthash "bindings" (format "%S" bindings) data)
254 | (puthash "lisp" (format "%S" body) data)
255 | (let ((client-connection
256 | (funcall
257 | (process-get server :exec)
258 | data
259 | stream
260 | end-callback)))
261 | ;; If we're streaming to elnode then we need to mark the connection
262 | (when (processp stream)
263 | (process-put
264 | stream
265 | :elnode-child-process
266 | client-connection))))))
267 |
268 | (defvar elnode-rle--async-do-end-callback nil
269 | "Used by `elnode-async-do' as the source of an end-callback.
270 |
271 | This is just used by tests for end signalling.")
272 |
273 | (defmacro elnode-async-do (stream
274 | requires requirements
275 | with-environment bindings
276 | do &rest body)
277 | "Execute the BODY in a remote Emacs.
278 |
279 | The STREAM is used to handle any output.
280 |
281 | The REQUIREMENTS is a list of provide symbol names that will be
282 | used to establish the right environment in the remote.
283 |
284 | The BINDINGS are also sent to the remote.
285 |
286 | TODO
287 |
288 | security for the remote using the stored key."
289 | (assert (eq with-environment 'with-environment))
290 | (assert (eq requires 'requires))
291 | (assert (eq do 'do))
292 | (let ((bodyv (make-symbol "body"))
293 | (bindsv (make-symbol "binds"))
294 | (streamv (make-symbol "streamv"))
295 | (requirev (make-symbol "providing")))
296 | `(let* ((,streamv ,stream)
297 | (,bodyv (quote (progn ,@body)))
298 | (,bindsv (list
299 | ,@(loop for p in bindings
300 | collect
301 | (if (and p (listp p))
302 | (list 'list `(quote ,(car p)) (cadr p))
303 | (list 'cons `,p nil)))))
304 | (,requirev (quote ,requirements)))
305 | (elnode-rle--sender
306 | ,streamv ,requirev ,bindsv ,bodyv
307 | elnode-rle--async-do-end-callback))))
308 |
309 | (defmacro with-elnode-rle-wait (&rest body)
310 | "Simplify the wait for RLE; for testers."
311 | `(unwind-protect
312 | (let (ended)
313 | (progn
314 | ,@body)
315 | (while (not ended) (sit-for 1)))
316 | ;; FIXME - can we get to the name of this?
317 | (server-eval-at "elnode-debugit" '(kill-emacs))))
318 |
319 | (ert-deftest elnode-rle--make-server ()
320 | "Test making an RLE server.
321 |
322 | Do it all 3 ways: directly with the `elnode-rle-make-server',
323 | with the `elnode-rle--sender' function and finally with the user
324 | facing macro `elnode-async-do'.
325 |
326 | The output from the RLE call is collected in a buffer
327 | and tested."
328 | (flet ((make-hash (bindings)
329 | (let ((h (make-hash-table :test 'equal)))
330 | (loop for b in bindings
331 | do (puthash (car b) (cadr b) h))
332 | h)))
333 | ;; Do it RAW
334 | (should
335 | (equal
336 | "hello"
337 | (with-temp-buffer
338 | (let* ((child-proc (elnode-rle--make-server 'elnode))
339 | (daemon-handler (process-get child-proc :daemonhandle))
340 | (collect-buf (current-buffer)))
341 | (with-elnode-rle-wait
342 | (funcall
343 | (process-get child-proc :exec)
344 | (make-hash '(("bindings" "((a \"hello\"))")
345 | ("lisp" "(princ \"hello\")")))
346 | (current-buffer)
347 | (lambda (hdr) ; the end proc
348 | (setq ended t))))
349 | (buffer-substring (point-min) (point-max))))))
350 | ;; Do it via the sender func
351 | (should
352 | (equal
353 | "40"
354 | (with-temp-buffer
355 | (with-elnode-rle-wait
356 | (let ((elnode-rle--servers (make-hash-table :test 'equal)))
357 | (elnode-rle--sender
358 | (current-buffer)
359 | '(elnode)
360 | '((a 10) (b 20))
361 | '(let ((c 30))(princ (+ c a)))
362 | (lambda (header)
363 | (message "elnode-rle: all done!")(setq ended t)))))
364 | (buffer-substring (point-min) (point-max)))))
365 | ;; Do it with the macro
366 | (should
367 | (equal
368 | "hello"
369 | (with-temp-buffer
370 | (with-elnode-rle-wait
371 | (let ((elnode-rle--servers (make-hash-table :test 'equal))
372 | (elnode-rle--async-do-end-callback
373 | (lambda (header)
374 | (message "elnode-rle: in the dyn bound callback!")
375 | (setq ended t))))
376 | (elnode-async-do
377 | (current-buffer)
378 | requires (elnode enode-rle)
379 | with-environment ((a 10)(b 20))
380 | do (princ "hello"))))
381 | (buffer-substring (point-min) (point-max)))))))
382 |
383 | (provide 'elnode-rle)
384 |
385 | ;; elnode-rle ends here
386 |
--------------------------------------------------------------------------------
/examples/chat.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * snack.js (c) Ryan Florence
3 | * https://github.com/rpflorence/snack
4 | * MIT License
5 | * Inspiration and code adapted from
6 | * MooTools (c) Valerio Proietti MIT license
7 | * jQuery (c) John Resig Dual license MIT or GPL Version 2
8 | * contentLoaded (c) Diego Perini MIT License
9 | * Zepto.js (c) Thomas Fuchs MIT License
10 | */
11 | typeof Object.create!="function"&&(Object.create=function(a){function b(){}b.prototype=a;return new b}),!function(a){var b=a.snack={},c=0,d=Object.prototype.toString,e=[].indexOf,f=[].push;b.extend=function(){if(arguments.length==1)return b.extend(b,arguments[0]);var a=arguments[0];for(var c,d=1,e=arguments.length;d=200&&a.status<300?[!1,a.xhr.responseText||"",a.xhr.responseXML]:[a.status];a.callback.apply(a,f)}},setHeader:function(a,b){this.headers[a]=b;return this},getHeader:function(a){try{return this.xhr.getResponseHeader(a)}catch(b){return null}},send:function(){var b=this,d=b.options;if(b.running)return b;b.running=!0;var e=d.data||"",f=String(d.url),g=d.method.toLowerCase();typeof e!="string"&&(e=a.toQueryString(e));if(d.emulation&&a.indexOf(g,["get","post"])<0){var h="_method="+g;e=e?h+"&"+e:h,g="post"}if(d.urlEncoded&&a.indexOf(g,["post","put"])>-1){var i=d.encoding?"; charset="+d.encoding:"";b.headers["Content-type"]="application/x-www-form-urlencoded"+i}f||(f=c.location.pathname);var j=f.lastIndexOf("/");j>-1&&(j=f.indexOf("#"))>-1&&(f=f.substr(0,j)),e&&g=="get"&&(f+=(f.indexOf("?")>-1?"&":"?")+e,e=null);var k=b.xhr;k.open(g.toUpperCase(),f,open.async,d.user,d.password),d.user&&"withCredentials"in k&&(k.withCredentials=!0),k.onreadystatechange=a.bind(b.onStateChange,b);for(var l in b.headers)try{k.setRequestHeader(l,b.headers[l])}catch(m){d.exception.apply(b,[l,b.headers[l]])}k.send(e),d.async||b.onStateChange();return b},cancel:function(){var a=this;if(!a.running)return a;a.running=!1;var b=a.xhr;b.abort(),b.onreadystatechange=e,a.xhr=new d;return a}}}(snack,window,document),!function(a,b){function d(b,c,d,e){var f=b.data(d);f&&a.each(f,function(a){a[c].apply(b,e)});return b}function c(a){return a.replace(/\s+/g," ").replace(/^\s+|\s+$/g,"")}a.wrap.define({data:function(){var a={};return function(b,c){var d=a[this.id];d||(d=a[this.id]={});if(c===void 1)return d[b];return d[b]=c}}(),each:function(b,c){return a.each(this,b,c)},addClass:function(a){return this.each(function(b){c(b.className).indexOf(a)>-1||(b.className=c(b.className+" "+a))})},removeClass:function(a){return this.each(function(b){b.className=b.className.replace(new RegExp("(^|\\s)"+a+"(?:\\s|$)"),"$1")})},attach:function(b,c,d){var e=b.split("."),f=[];e[1]&&(f=this.data(e[1])||[]),this.each(function(b){var g={node:b,event:e[0]};d&&(g.delegate=d),f.push(a.listener(g,c))}),e[1]&&this.data(e[1],f);return this},detach:function(a){d(this,"detach",a,null,!0),this.data(a,null);return this},fire:function(a,b){return d(this,"fire",a,b)},delegate:function(a,b,c){return this.attach(a,c,b)}})}(snack,document);
12 |
13 | /*!
14 | * @preserve Qwery - A Blazing Fast query selector engine
15 | * https://github.com/ded/qwery
16 | * copyright Dustin Diaz & Jacob Thornton 2012
17 | * MIT License
18 | */
19 | (function(a,b,c){typeof module!="undefined"&&module.exports?module.exports=b():typeof c["define"]=="function"&&c.define.amd?define(a,b):c[a]=b()})("qwery",function(){function C(){this.c={}}function H(a){return D.g(a)||D.s(a,"(^|\\s+)"+a+"(\\s+|$)",1)}function I(a,b){var c=0,d=a.length;for(;c~+]/,q=/^\s+|\s*([,\s\+\~>]|$)\s*/g,r=/[\s\>\+\~]/,s=/(?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\]|[\s\w\+\-]*\))/,t=/([.*+?\^=!:${}()|\[\]\/\\])/g,u=/^(\*|[a-z0-9]+)?(?:([\.\#]+[\w\-\.#]+)?)/,v=/\[([\w\-]+)(?:([\|\^\$\*\~]?\=)['"]?([ \w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^]+)["']?)?\]/,w=/:([\w\-]+)(\(['"]?([^()]+)['"]?\))?/,x=new RegExp(l.source+"|"+n.source+"|"+m.source),y=new RegExp("("+r.source+")"+s.source,"g"),z=new RegExp(r.source+s.source),A=new RegExp(u.source+"("+v.source+")?"+"("+w.source+")?"),B={" ":function(a){return a&&a!==b&&a.parentNode},">":function(a,b){return a&&a.parentNode==b.parentNode&&a.parentNode},"~":function(a){return a&&a.previousSibling},"+":function(a,b,c,d){return a?(c=L(a))&&(d=L(b))&&c==d&&c:!1}};C.prototype={g:function(a){return this.c[a]||undefined},s:function(a,b,c){return b=c?new RegExp(b):b,this.c[a]=b}};var D=new C,E=new C,F=new C,G=new C,$="compareDocumentPosition"in b?function(a,b){return(b.compareDocumentPosition(a)&16)==16}:"contains"in b?function(a,c){return c=c[h]===9||c==window?b:c,c!==a&&c.contains(a)}:function(a,b){while(a=a.parentNode)if(a===b)return 1;return 0},_=function(){var b=a.createElement("p");return(b.innerHTML='x')&&b.firstChild.getAttribute("href")!="#x"?function(a,b){return b==="class"?a.className:b==="href"||b==="src"?a.getAttribute(b,2):a.getAttribute(b)}:function(a,b){return a.getAttribute(b)}}(),ba=!!a[c],bb=a.querySelector&&a[e],bc=function(a,b){var c=[],d,f;try{return b[h]===9||!p.test(a)?K(b[e](a)):(I(d=a.split(","),Z(b,function(a,b){f=a[e](b),f.length==1?c[c.length]=f.item(0):f.length&&(c=c.concat(K(f)))})),d.length>1&&c.length>1?U(c):c)}catch(g){}return bd(a,b)},bd=function(a,b){var c=[],e,f,g,i,j,k;a=a.replace(q,"$1");if(f=a.match(o)){j=H(f[2]),e=b[d](f[1]||"*");for(g=0,i=e.length;g1&&c.length>1?U(c):c},be=function(a){typeof a[f]!="undefined"&&(i=a[f]?bb?bc:bd:bd)};return be({useNativeQSA:!0}),Y.configure=be,Y.uniq=U,Y.is=R,Y.pseudos={},Y},this);
20 |
21 | /*!
22 | * Bonzo: DOM Utility (c) Dustin Diaz 2012
23 | * https://github.com/ded/bonzo
24 | * License MIT
25 | */
26 | (function(e,t,n){typeof module!="undefined"&&module.exports?module.exports=t():typeof n["define"]=="function"&&n.define.amd?define(e,t):n[e]=t()})("bonzo",function(){function M(e){return new RegExp("(^|\\s+)"+e+"(\\s+|$)")}function _(e,t,n,r){var i,s=0,o=e.length;for(;s0?J(o,r):r)},null,r)},this,r),o.length=s,_(u,function(e){o[--s]=e},null,!r),o}function W(e,t,n){var r=Y(e),i=r.css("position"),s=r.offset(),o="relative",u=i==o,a=[parseInt(r.css("left"),10),parseInt(r.css("top"),10)];i=="static"&&(r.css("position",o),i=o),isNaN(a[0])&&(a[0]=u?0:e.offsetLeft),isNaN(a[1])&&(a[1]=u?0:e.offsetTop),t!=null&&(e.style.left=t-s.left+a[0]+E),n!=null&&(e.style.top=n-s.top+a[1]+E)}function X(e,t){return typeof t=="function"?t(e):t}function V(e){this.length=0;if(e){e=typeof e!="string"&&!e.nodeType&&typeof e.length!="undefined"?e:[e],this.length=e.length;for(var t=0;t","",1],a=["
"&&!/0){t=t.split(" ");for(a=t.length;a--;)I(e,t[a],n);return e}i=l&&t.replace(o,""),i&&N[i]&&(i=N[i].type);if(!t||l){if(u=l&&t.replace(s,""))u=u.split(".");f(e,i,n,u)}else if(typeof t=="function")f(e,null,t);else for(r in t)t.hasOwnProperty(r)&&I(e,r,t[r]);return e},q=function(e,t,n,r,i){var s,o,u,a,f=n,l=n&&typeof n=="string";if(t&&!n&&typeof t=="object")for(s in t)t.hasOwnProperty(s)&&q.apply(this,[e,s,t[s]]);else{a=arguments.length>3?y.call(arguments,3):[],o=(l?n:t).split(" "),l&&(n=F(t,f=r,i||O))&&(a=y.call(a,1)),this===x&&(n=H(I,e,t,n,f));for(u=o.length;u--;)j(e,o[u],n,f,a)}return e},R=function(){return q.apply(x,arguments)},U=m?function(e,t,r){var i=d.createEvent(e?"HTMLEvents":"UIEvents");i[e?"initEvent":"initUIEvent"](t,!0,!0,n,1),r.dispatchEvent(i)}:function(e,t,n){n=k(n,e),e?n.fireEvent("on"+t,d.createEventObject()):n["_on"+t]++},z=function(e,t,n){var r,i,u,a,f,l=t.split(" ");for(r=l.length;r--;){t=l[r].replace(o,"");if(a=l[r].replace(s,""))a=a.split(".");if(!a&&!n&&e[g])U(T[t],t,e);else{f=A.get(e,t),n=[!1].concat(n);for(i=0,u=f.length;i
"
102 | + d[0][1]
103 | + "
" + d[0][2]
104 | + "
"
105 | );
106 | setTimeout(chatPoll, 1000);
107 | }
108 | );
109 | }
110 |
111 | setTimeout(function () { chatPoll(); }, 1000);
112 |
113 | /* end chat.js */
114 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/elnode-tests.el:
--------------------------------------------------------------------------------
1 | ;;; elnode-tests.el --- tests for Elnode -*- lexical-binding: t -*-
2 |
3 | ;; Copyright (C) 2010, 2011, 2012 Nic Ferrier
4 |
5 | ;; Author: Nic Ferrier
6 | ;; Maintainer: Nic Ferrier
7 | ;; Created: 5th October 2010
8 | ;; Keywords: lisp, http, hypermedia
9 |
10 | ;; This file is NOT part of GNU Emacs.
11 |
12 | ;; This program is free software; you can redistribute it and/or modify
13 | ;; it under the terms of the GNU General Public License as published by
14 | ;; the Free Software Foundation, either version 3 of the License, or
15 | ;; (at your option) any later version.
16 |
17 | ;; This program is distributed in the hope that it will be useful,
18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 | ;; GNU General Public License for more details.
21 |
22 | ;; You should have received a copy of the GNU General Public License
23 | ;; along with this program. If not, see .
24 |
25 | ;;; Commentary:
26 | ;;
27 | ;; This is just the tests for Elnode.
28 |
29 | ;;; Style note
30 | ;;
31 | ;; This codes uses the Emacs style of:
32 | ;;
33 | ;; elnode--private-function
34 | ;;
35 | ;; for private functions.
36 |
37 | ;;; Code:
38 |
39 | (require 'ert)
40 | (require 'fakir)
41 | (require 'elnode)
42 | (require 'kv)
43 |
44 | (ert-deftest elnode-join ()
45 | "Test the path joining."
46 | (should
47 | (equal "/la/la/file"
48 | (elnode-join "/la" "la" "file")))
49 | (should
50 | (equal "/la/la/file/"
51 | (elnode-join "/la" "la" "file/")))
52 | (should
53 | (equal "/la/la/file/"
54 | (elnode-join "/la" "la/file/" ""))))
55 |
56 | (ert-deftest elnode-url-encode-path ()
57 | "Test the path encoding."
58 | (should
59 | (equal
60 | "/path/the%20path"
61 | (elnode-url-encode-path "/path/the path")))
62 | (should
63 | (equal
64 | "/path/the%20path/"
65 | (elnode-url-encode-path "/path/the path/")))
66 | (should
67 | (equal
68 | "/path/the%20%27path%27"
69 | (elnode-url-encode-path "/path/the 'path'"))))
70 |
71 | (defun elnode--log-buffer-read-text (buffer)
72 | "Turn the buffer into a list of text.
73 |
74 | Strips off the date format from each text line. Primarily this
75 | is just a test helper."
76 | (let* ((log-line-regex "[0-9]\\{14\\}: \\(.*\\)")
77 | (lines
78 | (split-string
79 | (with-current-buffer buffer
80 | (buffer-substring (point-min) (point-max)))
81 | "\n")))
82 | (loop for line in lines
83 | if (string-match log-line-regex line)
84 | collect (match-string 1 line))))
85 |
86 | (ert-deftest elnode-log-buffer-log ()
87 | "Test the log buffer stuff."
88 | (let ((tf (make-temp-file "logbufferlog")))
89 | (with-temp-buffer
90 | (elnode-log-buffer-log "test it" (current-buffer) tf)
91 | (should
92 | (equal
93 | (marker-position elnode-log-buffer-position-written)
94 | (point-max)))
95 | (elnode-log-buffer-log "test again" (current-buffer) tf)
96 | (should
97 | (equal
98 | '("test it" "test again")
99 | (elnode--log-buffer-read-text (current-buffer)))))
100 | ;; Test that we can read it back from the file.
101 | (let* ((log-buf (find-file-noselect tf)))
102 | (should
103 | (equal
104 | '("test it" "test again")
105 | (elnode--log-buffer-read-text log-buf))))))
106 |
107 | (ert-deftest elnode-log-buffer-log-truncates ()
108 | "Test the log buffer gets truncated stuff."
109 | (let ((log-line-regex "[0-9]\\{14\\}: \\(.*\\)")
110 | (tf (make-temp-file "logbufferlog"))
111 | (elnode-log-buffer-max-size 8))
112 | (with-temp-buffer
113 | (elnode-log-buffer-log "test it" (current-buffer) tf)
114 | (elnode-log-buffer-log "test again" (current-buffer) tf)
115 | (elnode-log-buffer-log "test three" (current-buffer) tf)
116 | (elnode-log-buffer-log "test four" (current-buffer) tf)
117 | (elnode-log-buffer-log "test five" (current-buffer) tf)
118 | (elnode-log-buffer-log "test six" (current-buffer) tf)
119 | (elnode-log-buffer-log "test seven" (current-buffer) tf)
120 | (elnode-log-buffer-log "test eight" (current-buffer) tf)
121 | (elnode-log-buffer-log "test nine" (current-buffer) tf)
122 | (elnode-log-buffer-log "test ten" (current-buffer) tf)
123 | (should
124 | (equal
125 | 8
126 | (length
127 | (loop for i in
128 | (split-string
129 | (buffer-substring
130 | (point-min)
131 | (point-max))
132 | "\n")
133 | if (not (equal i ""))
134 | collect i)))))))
135 |
136 | (ert-deftest elnode-test-logs-dont-log ()
137 | "Test the logs don't log when we turn stuff off."
138 | (let ((elnode-log-files-directory nil))
139 | ;; FIXME this is not a test. duh.
140 | (elnode-error "test message!")))
141 |
142 | (ert-deftest elnode-test-error-log ()
143 | (let ((err-message "whoops!! something went wrong! %s" )
144 | (err-include "some included value"))
145 | (with-temp-buffer
146 | (let ((test-log-buf (current-buffer)))
147 | ;; Setup a fake server log buffer
148 | (flet ((elnode--get-error-log-buffer ()
149 | test-log-buf))
150 | (elnode-error err-message err-include))
151 | ;; Assert the message sent to the log buffer is correctly formatted.
152 | (should (string-match
153 | (format
154 | "^.*: %s\n$"
155 | (apply 'format `(,err-message ,@(list err-include))))
156 | (buffer-substring (point-min) (point-max))))))))
157 |
158 | (ert-deftest elnode-test-error-log-list ()
159 | (let ((err-message "whoops!! something went wrong! %s %s")
160 | (err-include '("included value 1" "included value 2")))
161 | (with-temp-buffer
162 | (let ((test-log-buf (current-buffer)))
163 | ;; Setup a fake server log buffer
164 | (flet ((elnode--get-error-log-buffer ()
165 | test-log-buf))
166 | (elnode-error
167 | err-message
168 | "included value 1" "included value 2"))
169 | ;; Assert the message sent to the log buffer is correctly formatted.
170 | (should (string-match
171 | (format
172 | "^.*: %s\n$"
173 | (apply 'format `(,err-message ,@err-include)))
174 | (buffer-substring (point-min) (point-max))))))))
175 |
176 | (ert-deftest elnode-test-access-log ()
177 | "Test the access logging."
178 | (fakir-mock-process
179 | :httpcon
180 | ((:buffer
181 | (elnode--http-make-hdr
182 | 'get "/"
183 | '(host . "localhost")
184 | '(user-agent . "test-agent")))
185 | (:elnode-httpresponse-status 200)
186 | (:elnode-bytes-written 2048))
187 | (should
188 | (equal
189 | 'done
190 | (catch 'elnode-parse-http
191 | (elnode--http-parse :httpcon))))
192 | (let* ((logname "ert-test")
193 | (buffername (format "*%s-elnode-access*" logname)))
194 | (flet ((elnode--log-filename
195 | (log-name)
196 | (make-temp-file "elnode-access")))
197 | (unwind-protect
198 | (progn
199 | (elnode-log-access logname :httpcon)
200 | (should
201 | (string-match
202 | (concat "^[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}"
203 | "-[0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}:[ ]+"
204 | "200[ ]+2048[ ]+GET[ ]+/$")
205 | (with-current-buffer buffername
206 | (buffer-substring (point-min)(point-max))))))
207 | (kill-buffer buffername))))))
208 |
209 | (ert-deftest elnode-deferring ()
210 | "Testing the defer setup."
211 | (let* ((result :not-done)
212 | (httpcon :fake)
213 | (handler (lambda (httpcon)
214 | (message "here!")
215 | (setq result :done)))
216 | (elnode--deferred (list)))
217 | (fakir-mock-process httpcon ()
218 | ;; The queue starts empty
219 | (should (equal 0 (length elnode--deferred)))
220 | ;; Then we add to it...
221 | (elnode--deferred-add httpcon handler)
222 | (should (equal 1 (length elnode--deferred)))
223 | ;; Then we process it...
224 | (flet-overrides (lambda (obj) (eq obj :fake))
225 | ((process-status proc (proc) 'open))
226 | (elnode--deferred-processor))
227 | ;; ... that should have emptied it out...
228 | (should (eq result :done))
229 | (should (equal 0 (length elnode--deferred)))
230 | ;; Now we add a handler that defers...
231 | (elnode--deferred-add httpcon
232 | (lambda (httpcon)
233 | (elnode-defer-now handler)))
234 | (should (equal 1 (length elnode--deferred)))
235 | ;; Now we process...
236 | (flet-overrides (lambda (obj) (eq obj :fake))
237 | ((process-status proc (proc) 'open))
238 | (elnode--deferred-processor))
239 | ;; ... should still have the deferred handler in it...
240 | (should (equal 1 (length elnode--deferred)))
241 | ;; ... process again ...
242 | (flet-overrides (lambda (obj) (eq obj :fake))
243 | ((process-status proc (proc) 'open))
244 | (elnode--deferred-processor))
245 | (should (equal 0 (length elnode--deferred))))))
246 |
247 |
248 | (ert-deftest elnode--make-http-hdr ()
249 | "Test the construction of headers"
250 | (should
251 | (equal
252 | (elnode--http-make-hdr
253 | 'get "/"
254 | '(host . "host1")
255 | '(user-agent . "test-agent"))
256 | "GET / HTTP/1.1\r
257 | Host: host1\r
258 | User-Agent: test-agent\r
259 | \r
260 | "))
261 | (should
262 | (equal
263 | (elnode--http-make-hdr
264 | 'get "/"
265 | '(host . "host2")
266 | '(user-agent . "test-agent")
267 | '(body . "my test data"))
268 | "GET / HTTP/1.1\r
269 | Host: host2\r
270 | User-Agent: test-agent\r
271 | \r
272 | my test data")))
273 |
274 | (ert-deftest elnode--http-parse-header-complete ()
275 | "Test the HTTP parsing."
276 | (fakir-mock-process
277 | :httpcon
278 | ((:buffer
279 | (elnode--http-make-hdr
280 | 'get "/"
281 | '(host . "localhost")
282 | '(user-agent . "test-agent"))))
283 | ;; Parse the header
284 | (should
285 | (equal 'done
286 | (catch 'elnode-parse-http
287 | (elnode--http-parse :httpcon))))
288 | ;; Now check the side effects
289 | (should
290 | (equal
291 | (process-get :httpcon :elnode-http-header)
292 | '(("Host" . "localhost")
293 | ("User-Agent" . "test-agent"))))))
294 |
295 | (ert-deftest elnode--http-parse-header-incomplete ()
296 | "Test the HTTP parsing of an incomplete header.
297 |
298 | An HTTP request with an incomplete header is setup and tested,
299 | then we finish the request (fill out the header) and then test
300 | again."
301 | (fakir-mock-process
302 | :httpcon
303 | ((:buffer
304 | "GET / HTTP/1.1\r\nHost: localh"))
305 | ;; Now parse
306 | (should
307 | ;; It fails with incomplete 'header signal
308 | (equal 'header
309 | (catch 'elnode-parse-http
310 | (elnode--http-parse :httpcon))))
311 | ;; Now put the rest of the header in the buffer
312 | (with-current-buffer (process-buffer :httpcon)
313 | (goto-char (point-max))
314 | (insert "ost\r\n\r\n"))
315 | (should
316 | ;; Now it succeeds with the 'done signal
317 | (equal 'done
318 | (catch 'elnode-parse-http
319 | (elnode--http-parse :httpcon))))))
320 |
321 |
322 | (ert-deftest elnode--http-parse-body-incomplete ()
323 | "Tests the HTTP parsing of an incomplete body.
324 |
325 | An HTTP request with an incomplete body is setup and tested, then
326 | we finish the request (fill out the content to content-length)
327 | and then test again."
328 | (let ((hdr
329 | (elnode--http-make-hdr
330 | 'get "/"
331 | '(host . "localhost")
332 | '(user-agent . "test-agent")
333 | `(content-length . ,(format "%d" (length "this is not finished")))
334 | '(body . "this is not fin"))))
335 | (fakir-mock-process
336 | :httpcon
337 | ((:buffer hdr))
338 | ;; Now parse
339 | (should
340 | (equal 'content
341 | (catch 'elnode-parse-http
342 | (elnode--http-parse :httpcon))))
343 | ;; Now put the rest of the text in the buffer
344 | (with-current-buffer (process-buffer :httpcon)
345 | (goto-char (point-max))
346 | (insert "ished"))
347 | ;; And test again
348 | (should
349 | (equal 'done
350 | (catch 'elnode-parse-http
351 | (elnode--http-parse :httpcon)))))))
352 |
353 |
354 | (ert-deftest elnode-http-start ()
355 | "Test starting a response.
356 |
357 | Especially tests the mix of header setting techniques."
358 | (fakir-mock-process :httpcon ()
359 | (elnode-http-header-set :httpcon "Content-Type" "text/html")
360 | (elnode-http-header-set :httpcon "Accept" "application/javascript")
361 | (elnode-http-start :httpcon 200 '("Content-Type" . "text/plain"))
362 | ;; Test that we have the correct text in the fake process buffer
363 | (with-current-buffer (fakir-get-output-buffer)
364 | (goto-char (point-min))
365 | (should
366 | (re-search-forward "^Content-Type: text/html\r\n" nil t))
367 | (goto-char (point-min))
368 | (should
369 | (re-search-forward "^Accept: application/javascript\r\n" nil t)))))
370 |
371 |
372 | (defun elnode-test-handler (httpcon)
373 | "A simple handler for testing `elnode-test-call'.
374 |
375 | The text spat out is tested, so is the status."
376 | (elnode-http-start
377 | httpcon 200
378 | '("Content-Type" . "text/html")
379 | '("User-Agent" . "elnode-test"))
380 | (let ((params (elnode-http-params httpcon)))
381 | (elnode-http-return
382 | httpcon
383 | (format
384 | "