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