1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
add = (tmpl) ->
package.path ..= ";#{tmpl}.lua"
package.moonpath ..= ";#{tmpl}.moon"
add '?'
add '?.server'
add '?/init'
add '?/init.server'
require 'mmm'
import dir_base, Key, Fileder from require 'mmm.mmmfs.fileder'
import convert from require 'mmm.mmmfs.conversion'
import get_store from require 'mmm.mmmfs.stores'
import render from require 'mmm.mmmfs.layout'
import Browser from require 'mmm.mmmfs.browser'
import decodeURI from require 'http.util'
lfs = require 'lfs'
server = require 'http.server'
headers = require 'http.headers'
class Server
new: (@store, opts={}) =>
opts = {k,v for k,v in pairs opts}
opts.host = 'localhost' unless opts.host
opts.port = 8000 unless opts.port
opts.onstream = @\stream
opts.onerror = @\error
@server = server.listen opts
listen: =>
assert @server\listen!
_, ip, port = @server\localname!
print "[#{@@__name}]", "running at #{ip}:#{port}"
assert @server\loop!
handle: (method, path, facet) =>
fileder = Fileder @store, path
if not fileder
-- fileder not found
404, "fileder '#{path}' not found"
switch method
when 'GET', 'HEAD'
val = switch facet.name
when '?index', '?tree'
-- serve fileder index
-- '?index': one level deep
-- '?tree': recursively
depth = if facet.name == '?tree' then -1 else 1
index = @store\get_index path, depth
convert 'table', facet.type, index, fileder, facet.name
else
if facet.type == 'text/html+interactive'
root = Fileder @store
browser = Browser root, path, facet.name
deps = [[
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.6/svg.min.js"></script>
<script type="text/javascript" src="//unpkg.com/mermaid@8.4.0/dist/mermaid.min.js"></script>
<script type="text/javascript" src="//unpkg.com/marked@0.7.0/marked.min.js"></script>
<script type="text/javascript" src="/static/fengari-web/:text/javascript"></script>
<script type="text/lua" src="/static/mmm/:text/lua"></script>
<script type="text/lua">require 'mmm'</script>
]]
render browser\todom!, fileder, noview: true, scripts: deps .. "
<script type=\"text/lua\">
on_load = on_load or {}
table.insert(on_load, function()
local path = #{string.format '%q', path}
local facet = #{string.format '%q', facet.name}
local browser = require 'mmm.mmmfs.browser'
local fileder = require 'mmm.mmmfs.fileder'
local web = require 'mmm.mmmfs.stores.web'
local store = web.WebStore({ verbose = true })
local root = fileder.Fileder(store, store:get_index(nil, -1))
local err_and_trace = function (msg) return debug.traceback(msg, 2) end
local ok, browser = xpcall(browser.Browser, err_and_trace, root, path, facet, true)
if not ok then error(browser) end
end)
</script>"
else if not fileder\has_facet facet.name
404, "facet '#{facet.name}' not found in fileder '#{path}'"
else
fileder\get facet
if val
200, val
else
406, "cant convert facet '#{facet.name}' to '#{facet.type}'"
else
501, "not implemented"
err_and_trace = (msg) -> debug.traceback msg, 2
stream: (sv, stream) =>
req = stream\get_headers!
method = req\get ':method'
path = req\get ':path'
path = decodeURI path
path_facet, type = path\match '(.*):(.*)'
path_facet or= path
path, facet = path_facet\match '(.*)/([^/]*)'
type or= 'text/html+interactive'
type = type\match '%s*(.*)'
facet = Key facet, type
ok, status, body = xpcall @.handle, err_and_trace, @, method, path, facet
if not ok
warn "Error handling request (#{method} #{path} #{facet}):\n#{status}"
body = "Internal Server Error:\n#{status}"
status = 500
res = headers.new!
response_type = if status > 299 then 'text/plain'
else if facet.type == 'text/html+interactive' then 'text/html'
else facet.type
res\append ':status', tostring status
res\append 'content-type', response_type
stream\write_headers res, method == 'HEAD'
if method ~= 'HEAD'
stream\write_chunk body, true
error: (sv, ctx, op, err, errno) =>
msg = "#{op} on #{tostring ctx} failed"
msg = "#{msg}: #{err}" if err
-- usage:
-- moon server.moon [STORE] [host] [port]
{ store, host, port } = arg
store = get_store store
server = Server store, :host, port: port and tonumber port
server\listen!
|