From 27d1cf641ce3bbfe96e6fe18f24dd46f95efbe5e Mon Sep 17 00:00:00 2001 From: s-ol Date: Mon, 29 Oct 2018 19:35:53 +1100 Subject: better conversion inverence for mmmfs --- Tupfile | 1 + app/mmmfs/browser.moon | 5 +- app/mmmfs/conversion.moon | 121 +++++++++++++++++++++ app/mmmfs/gallery.moon | 51 --------- app/mmmfs/init.moon | 221 +++++++++------------------------------ app/mmmfs/tree.moon | 249 -------------------------------------------- app/mmmfs/tree/gallery.moon | 49 +++++++++ app/mmmfs/tree/init.moon | 247 +++++++++++++++++++++++++++++++++++++++++++ app/mmmfs/tree/twisted.moon | 48 +++++++++ app/mmmfs/twisted.moon | 48 --------- lib/component.client.moon | 1 + lib/init.client.moon | 21 +++- lib/init.server.moon | 22 +++- 13 files changed, 559 insertions(+), 525 deletions(-) create mode 100644 app/mmmfs/conversion.moon delete mode 100644 app/mmmfs/gallery.moon delete mode 100644 app/mmmfs/tree.moon create mode 100644 app/mmmfs/tree/gallery.moon create mode 100644 app/mmmfs/tree/init.moon create mode 100644 app/mmmfs/tree/twisted.moon delete mode 100644 app/mmmfs/twisted.moon diff --git a/Tupfile b/Tupfile index bcd9563..e555914 100644 --- a/Tupfile +++ b/Tupfile @@ -9,6 +9,7 @@ preload app app/tags lib CLIENT += app/*.moon CLIENT += app/tags/*.moon CLIENT += app/mmmfs/*.moon +CLIENT += app/mmmfs/tree/*.moon CLIENT += lib/*.client.moon CLIENT += lib/*.shared.moon diff --git a/app/mmmfs/browser.moon b/app/mmmfs/browser.moon index 50da38b..427d952 100644 --- a/app/mmmfs/browser.moon +++ b/app/mmmfs/browser.moon @@ -78,8 +78,9 @@ class Browser active = @active\get! ok, res = pcall -> - val, key = active\get prop.name, prop.type - CONVERT 'mmm/dom', val, key + -- val, key = active\get prop.name, prop.type + -- CONVERT 'mmm/dom', val, key + active\get prop.name, 'mmm/dom' if ok and res res diff --git a/app/mmmfs/conversion.moon b/app/mmmfs/conversion.moon new file mode 100644 index 0000000..a670e2e --- /dev/null +++ b/app/mmmfs/conversion.moon @@ -0,0 +1,121 @@ +import text, code from require 'lib.html' +import tohtml from require 'lib.component' + +-- limit function to one argument +single = (func) -> (val) -> func val + +-- list of converts +-- converts each have +-- * inp - input type. can capture subtypes using `(.+)` +-- * out - output type. can substitute subtypes from inp with %1, %2 etc. +-- * transform - function (val: inp, fileder) -> val: out +converts = { + { + inp: 'moon -> (.+)', + out: '%1', + transform: (val, fileder) -> val fileder + }, + { + inp: 'text/plain', + out: 'mmm/dom', + transform: single text + }, + { + inp: 'alpha', + out: 'mmm/dom', + transform: single code + }, + { + inp: 'URL -> .*', + out: 'mmm/dom', + transform: single code + }, + { + inp: 'mmm/component', + out: 'mmm/dom', + transform: single tohtml + }, + { + inp: 'mmm/dom', + out: 'text/html', + transform: (node) -> if MODE == 'SERVER' then node else node.outerHTML + }, + { + inp: 'text/html', + out: 'mmm/dom', + transform: if MODE == 'SERVER' + (...) -> ... + else + (html) -> + tmp = document\createElement 'div' + tmp.innerHTML = html + if tmp.childElementCount == 1 + tmp.firstChild + else + tmp + } +} + +do + local markdown + if MODE == 'SERVER' + success, discount = pcall require, 'discount' + markdown = discount if success + else + markdown = window and window.marked and window\marked + + if markdown + table.insert converts, { + inp: 'text/markdown', + out: 'text/html', + transform: (val) -> + html = markdown val + warn html + html + } + +count = (base, pattern='->') -> select 2, base\gsub pattern, '' +escape_inp = (inp) -> "^#{inp\gsub '([-/])', '%%%1'}$" + +-- attempt to find a conversion path from 'have' to 'want' +-- * have - start type string or list of type strings +-- * want - stop type string +-- * limit - limit conversion amount +-- returns a list of conversion steps +get_conversions = (want, have, limit=3) -> + assert have, 'need starting type(s)' + + if 'string' == type have + have = { have } + + assert #have > 0, 'need starting type(s) (list was empty)' + + iterations = limit + math.max table.unpack [count type for type in *have] + have = [{ :start, rest: start, conversions: {} } for start in *have] + + for i=1, iterations + next_have, c = {}, 1 + for { :start, :rest, :conversions } in *have + if want == rest + return conversions, start + else + for convert in *converts + inp = escape_inp convert.inp + matches = { rest\match inp } + continue unless #matches > 0 + result = rest\gsub inp, convert.out + if result + next_have[c] = { + :start, + rest: result, + conversions: { convert, table.unpack conversions } + } + c += 1 + + have = next_have + return unless #have > 0 + +{ + :converts + :get_conversions +} diff --git a/app/mmmfs/gallery.moon b/app/mmmfs/gallery.moon deleted file mode 100644 index e1868d1..0000000 --- a/app/mmmfs/gallery.moon +++ /dev/null @@ -1,51 +0,0 @@ -import div, h1, a, img, br from require 'lib.html' - -URL = (url) => url - -children = for i=1,100 - id = math.floor math.random! * 200 - Fileder { - 'name: alpha': "image#{id}" - 'URL -> image/png': "https://picsum.photos/600/600/?image=#{id}" - 'preview: URL -> image/png': "https://picsum.photos/200/200/?image=#{id}" - } - -props = { - 'name: alpha': 'gallery', - 'title: text/plain': "A Gallery of 100 random pictures, come in!", - 'preview: moon -> mmm/dom': => div { - 'the first pic as a little taste:', - br!, - img src: @children[1]\get 'preview', 'image/png', :URL - } - 'moon -> mmm/dom': => - link = (child) -> a { - href: '#', - onclick: -> BROWSER\navigate { 'gallery', (child\get 'name', 'alpha'), nil }, - img src: child\gett 'preview', 'image/png', :URL - } - - content = [link child for child in *@children] - table.insert content, 1, h1 'gallery index' - div content - - 'slideshow: moon -> mmm/dom': => - import ReactiveVar, text, elements from require 'lib.component' - - index = ReactiveVar 1 - - prev = (i) -> math.max 1, i - 1 - next = (i) -> math.min #@children, i + 1 - - e = elements - e.div { - e.div { - e.a 'prev', href: '#', onclick: -> index\transform prev - index\map (i) -> text " image ##{i} " - e.a 'next', href: '#', onclick: -> index\transform next - }, - index\map (i) -> img src: @children[i]\gett nil, 'image/png', :URL - } -} - -Fileder props, children diff --git a/app/mmmfs/init.moon b/app/mmmfs/init.moon index 1f2d2f6..19cb028 100644 --- a/app/mmmfs/init.moon +++ b/app/mmmfs/init.moon @@ -1,143 +1,35 @@ -export ^ - --- list of interps --- interp signature is (fileder, value) -> value -interps = { - moon: (method) => method @ -} - -split = (str, delim='->') -> - return {}, str if nil == str\find delim +require = relative ... +import get_conversions from require '.conversion' - -- @TODO interp chain? - interp, rest = str\match ' *(%w+) *-> *(.*)' - { interp }, rest +export ^ -- Key of a Fileder Property -- contains: -- * @name - key name or '' for main content --- * @type - final type after interps --- * @interps - array describing interp chain +-- * @type - type string (type -> type -> type) class Key -- instantiate from table w/ keys described above -- or string like 'name: interp -> interp -> type' (name + interps optional) - new: (opts) => - if 'string' == type opts - @name, rest = opts\match '(%w+): *(.+)' + new: (opts, second) => + if 'string' == type second + @name, @type = (opts or ''), second + elseif 'string' == type opts + @name, @type = opts\match '(%w+): *(.+)' if not @name @name = '' - rest = opts - @interps, @type = split rest, '->' + @type = opts elseif 'table' == type opts @name = opts.name - @type = assert opts.type, 'no type given' - @interps = opts.interps or {} + @type = opts.type else - error 'wrong argument type' - - -- get a function that interpretes a raw value according to @interps - -- and returns a value of @type - -- overrides is a map of interp overrides - get_interp: (overrides={}) => - return ((val) => val) if #@interps == 0 - - assert #@interps == 1, 'not supported rn' -- @TODO - _name = @interps[1] - - return overrides[_name] if overrides and overrides[_name] - - assert overrides[_name] or interps[_name], "interp not found: '#{_name}'" + error "wrong argument type: #{type opts}, #{type second}" -- format as a string (see constructor) tostring: => - list = { table.unpack @interps } - table.insert list, @type - - type = table.concat list, ' -> ' - if @name == '' - type + @type else - "#{@name}: #{type}" - - -import text, code from require 'lib.html' -import tohtml from require 'lib.component' - --- list of converts --- converts each have --- * inp - input type --- * out - output type --- * transform - function (inp) -> out -converts = { - { - inp: 'text/plain', - out: 'mmm/dom', - transform: text - }, - { - inp: 'alpha', - out: 'mmm/dom', - transform: code - }, - { - inp: 'mmm/dom', - out: 'text/html', - transform: (node) -> if MODE == 'SERVER' then node else node.outerHTML - }, - { - inp: 'mmm/component', - out: 'mmm/dom', - transform: tohtml - }, - { - -- @TODO this chained rule *should* be inferred, but that's way too hot rn - inp: 'mmm/component', - out: 'text/html', - transform: (node) -> - node = tohtml node - if MODE == 'SERVER' then node else node.outerHTML - }, -} - -table.insert converts, { - inp: 'text/html', - out: 'mmm/dom', - transform: if MODE == 'SERVER' - (...) -> ... - else - (html) -> - tmp = document\createElement 'div' - tmp.innerHTML = html - tmp.firstChild - } - -do - local markdown - if MODE == 'SERVER' - success, discount = pcall require, 'discount' - markdown = discount if success - else - markdown = window and window\marked - - if markdown - table.insert converts, { - inp: 'text/markdown', - out: 'text/html', - transform: markdown, - } - - -- @TODO chained w above - table.insert converts, { - inp: 'text/markdown', - out: 'mmm/dom', - transform: if MODE == 'SERVER' - (md) -> markdown md - else - (md) -> - with document\createElement 'div' - .innerHTML = markdown md - } + "#{@name}: #{@type}" -- Fileder itself -- contains: @@ -158,69 +50,56 @@ class Fileder -- find property key according to criteria, nil if no value or conversion path -- * name - property name (optional: defaults to main content) -- * type - wanted result type - -- * convert - allow conversion (optional: default true) - find: (name='', type, convert=true) => - if not type - type = name - name = '' + find: (...) => + want = Key ... - -- first pass, interps only - for key, value in pairs @props - continue unless key.name == name and key.type == type + -- filter props by name + matching = [ key for key in pairs @props when key.name == want.name ] + return unless #matching > 0 - return key + -- get shortest conversion path + shortest_path, start = get_conversions want.type, [ key.type for key in *matching ] - if convert - -- second pass, interps + converts - for key, value in pairs @props - continue unless key.name == name + if start + for key in *matching + if key.type == start + return key, shortest_path - for { :inp, :out, :transform } in *converts - return key if inp == key.type and out == type + error "couldn't find key after resolution?" -- get property according to criteria, nil if no value or conversion path -- * name - property name (optional: defaults to main content) -- * type - wanted result type - -- * overrides - map of interp overrides (optional) - get: (name='', type, overrides) => - if not type - type = name - name = '' + get: (...) => + want = Key ... - key = @find name, type, not overrides + -- find matching key and shortest conversion path + key, conversions = @find want if key - interp = key\get_interp overrides - - return (interp @, @props[key]), key if key.type == type + value = @props[key] - for { :inp, :out, :transform } in *converts - return (transform interp @, @props[key]), key if inp == key.type and out == type + -- apply conversions (in reverse order) + for i=#conversions,1,-1 + { :inp, :out, :transform } = conversions[i] + value = transform value, @ - nil, nil + value, key - -- like get, throw if no value or conversion path - gett: (name='', type, overrides) => - if not type - type = name - name = '' + -- like @get, throw if no value or conversion path + gett: (...) => + want = Key ... - val, key = @get name, type, overrides - assert val, "node doesn't have value for #{name}:#{type}" - val, key + value, key = @get want + assert value, "node doesn't have value for #{want\tostring!}" + value, key -CONVERT = (type, val, key) -> - return unless val and key - - if key.type == type - val - else - for { :inp, :out, :transform } in *converts - return transform val if inp == key.type and out == type - -require = relative ... -import Browser from require '.browser' root = require '.tree' - -BROWSER = Browser root -append BROWSER.node +if MODE == 'CLIENT' + import Browser from require '.browser' + + export BROWSER + BROWSER = Browser root + append BROWSER.node +else + append root\get 'mmm/dom' diff --git a/app/mmmfs/tree.moon b/app/mmmfs/tree.moon deleted file mode 100644 index e270cf8..0000000 --- a/app/mmmfs/tree.moon +++ /dev/null @@ -1,249 +0,0 @@ -require = relative ..., 1 - -Fileder { - -- main content - -- doesn't have a name prefix (e.g. preview: moon -> mmm/dom) - -- uses the 'moon' interp to execute the lua/moonscript function on get - -- resolves to a value of type mmm/dom - 'moon -> mmm/dom': () => - html = require 'lib.html' - import article, h1, h2, h3, p, div, a, sup, ol, li, span, code, pre, br from html - - code = do - _code = code - (str) -> _code str\match '^ *(..-) *$' - - article with _this = {} - append = (a) -> table.insert _this, a - - footnote, getnotes = do - local * - notes = {} - - id = (i) -> "footnote-#{i}" - - footnote = (stuff) -> - i = #notes + 1 - notes[i] = stuff - sup a "[#{i}]", style: { 'text-decoration': 'none' }, href: '#' .. id i - - footnote, -> - args = for i, note in ipairs notes - li (span (tostring i), id: id i), ': ', note - notes = {} - table.insert args, style: { 'list-style': 'none', 'font-size': '0.8em' } - ol table.unpack args - - append h1 'mmmfs', style: { 'margin-bottom': 0 } - append p "a file and operating system to live in", style: { 'margin-top': 0, 'margin-bottom': '1em' } - - append p do - fileder = footnote "fileder: file + folder. 'node', 'table' etc. are too general to be used all over." - child = footnote "fileders can have multiple values, like the mentioned script, but these are not considered - children of the fileder, as they are not fileders themselves. One fileder can have many values of different - types/keys associated, but these have unspecified schemas and don't nest. In addition it can have many - children, which are fileders themselves and can nest, but they are not labelled." - - "in mmmfs, directories are files and files are directories, or something like that. - Listen, I don't really know yet either. The idea is that every node knows how to display it's contents; - so for example your 'Pictures' fileder", fileder, " contains a script within itself that renders - all the picture files you put into it at the children level", child, "." - - append p "a fileder should also be responsible for how it's children are sorted, filtered and interacted with. - For example you should be able to create a fileder that is essentially a 'word document' equivalent: it could - contain images, websites, links and of course text as children and let you reorder, layout and edit them in - it's own edit interface." - - append p "a picture fileder could have an alternate slideshow view (whoops - built that. Click on the 'gallery' - example below), or one that shows your geotagged images on a world map, if you really want that. - Maybe you could build a music folder that contains links to youtube videos, spotify tracks and just plain mp3 - files, and the folder knows how to play them all." - - append p "Sounds cool, no? Here's some examples of things a fileder can be or embed:" - - -- render a preview block - preview = (title, content, name) -> div { - h3 title, style: { margin: 0, cursor: 'pointer' }, onclick: -> BROWSER\navigate { name } - content or span '(no renderable content)', style: { color: 'red' }, - style: { - display: 'inline-block', - width: '300px', - height: '200px', - padding: '4px', - margin: '8px', - border: '4px solid #eeeeee', - overflow: 'hidden', - }, - } - - append div for child in *@children - -- get 'title' as 'text/plain' (error if no value or conversion possible) - title = child\gett 'title', 'text/plain' - - -- get 'preview' as a DOM description (nil if no value or conversion possible) - content = child\get 'preview', 'mmm/dom' - - -- get 'preview' as a DOM description (nil if no value or conversion possible) - name = child\gett 'name', 'alpha' - - preview title, content, name - - - append h2 "details" - append do - mmmdom = code ('mmm/dom'), footnote span (code 'mmm/dom'), " is a polymorphic content type; - on the server it is just an HTML string (like ", (code 'text/html'), "), - but on the client it is a JS DOM Element instance." - fengari = a 'fengari.io', href: 'https://fengari.io' - p "What you are viewing right now is a fileder that has a Lua/Moonscript function as a value which - is rendering all this text. It returns a value whose type interface is known as ", mmmdom, ", which is - basically an HTML subtree. The function can be run, and the results generated, statically on the server - (resulting in an HTML file), or dynamically on the client (via ", fengari, ").", br!, - "The function is passed the fileder itself (as a Lua table) and potentially also receives some other - helpers for accessing it's environment (parent fileders, functions for querying the tree etc) or info - specific to the function key/type, but I haven't built or thought about any of that yet. Sorry." - - append do - github = footnote a 's-ol/mmm', href: 'https://github.com/s-ol/mmm/tree/master/app/mmmfs' - p "Anyway, this node is set up as some sort of wiki/index thing and just lists its children-fileders' ", (code 'title: text/plain'), - " values and ", (code 'preview: mmm/dom'), " previews (if set). Oh and also everything is on github and stuff", github, - " if you care about that." - - append h3 "converts" - append p "Well actually it's a bit more complex. You see, the code that renders these previews ", (html.i "asks"), " for those - name/type pairs (", (code 'title: text/plain'), ', ', (code 'preview: mmm/dom'), "), but the values don't actually have to - be ", (html.i "defined"), " as these types." - - append pre code [[ --- render a preview block -preview = (title, content) -> div { - h3 title, style: { ... }, - content or span '(no renderable content)', style: { ... }, - style: { ... } -} - -append div for child in *@children - -- get 'title' as 'text/plain' (error if no value or conversion possible) - title = child\gett 'title', 'text/plain' - - -- get 'preview' as a DOM description (nil if no value or conversion possible) - content = child\get 'preview', 'mmm/dom' - - preview title, content - ]] - - append p "For example, the markdown child below only provides ", (code 'preview'), " as ", (code 'text/markdown'), ":" - - append pre code [[ -Fileder { - 'title: text/plain': "I'm not even five lines of markdown but i render myself!", - 'preview: text/markdown': "See I have like - -- a list of things -- (two things) - -and some bold **text** and `code tags` with me.", -} - ]] - - append p "Then, globally, there are some conversion paths specified; such as one that maps from ", - (code 'text/markdown'), " to ", (code 'mmm/dom'), ":" - - append pre code [[ -table.insert converts, { - inp: 'text/markdown', - out: 'mmm/dom', - transform: (md) -> - -- polymorphic client/serverside implementation here, - -- uses lua-discount on the server, marked.js on the client -} - ]] - - append h3 "interps" - append p "In addition, a property can be encoded using ", (code 'interps'), ". For example the root node you are viewing - currently is defined as ", (code 'moon -> mmm/dom'), ", meaning it is to be interpreted by the ", (code 'moon'), - " interp before being treated as a regular ", (code 'mmm/dom'), " value." - - append p "The ", (code 'moon'), " interp takes a function value and calls it, passing the Fileder it is processing as ", - (code 'self'), ":" - - append pre code [[ -{ - name: 'moon', - transform: (method) => method @ -} - ]] - - append p "Both interps and converts are resolved automatically when asking for values, so this page is being - rendered just using ", (code "append root\\get 'mmm/dom'"), " as well." - - append h3 "interp overloading" - append p "The example with the image is curious as well. In mmmfs, you might want to save a link to an image, - without ever saving the actual image on your hard drive (or wherever the data may ever be stored - it is - quite transient currently). The image Fileder below has it's main (unnamed) value tagged as ", - (code 'URL -> image/png'), " - a png image, encoded as an URL. When accessed as ", (code 'image/png'), " - the URL should be resolved, and the binary data provided in it's place (yeah right - I haven't build that yet). - However, if a script is aware of URLs and knows a better way to handle them, then it can overload the URL - interp for it's fetch, to get at the raw data and use that URL instead. This is what the image demo does in - order to pass the URL to an ", (code 'img'), " tag's ", (code 'src'), " attribute:" - - append pre code [[ -Fileder { - 'title: text/plain': "Hey I'm like a link to picture or smth", - 'URL -> image/png': 'https://picsum.photos/200?random', - 'preview: moon -> mmm/dom': => - import img from require 'lib.html' - img src: @gett nil, -- look for: main content - 'image/png', -- with image type, and - URL: (url) => url -- override URL interp to get raw URL -} - ]] - - append getnotes! - - 'text/markdown': "this is a markdown version or something. - -There's no content here so switch back to the real one! -(Assuming there is a switching UI by the time you are reading this, which I assume since you are reading this at all. -If you are reading this in the source, then c'mon, just scroll past and give me a break.) - -(the switching UI has now been built.)" - - Fileder { - 'name: alpha': 'empty', - 'title: text/plain': "Hey I'm an ad-hoc child with no content at all", - } - - Fileder { - 'name: alpha': 'image', - 'title: text/plain': "Hey I'm like a link to picture or smth", - - -- main content is image/png, to be interpreted by URL to access - 'URL -> image/png': 'https://picsum.photos/200?random', - - -- preview is a lua/moonscript function that neturns an mmm/dom value - 'preview: moon -> mmm/dom': => - import img from require 'lib.html' - img src: @gett nil, -- look for: main content - 'image/png', -- with image type, and - URL: (url) => url -- override URL interp to get raw URL - } - - Fileder { - 'name: alpha': 'markdown', - 'title: text/plain': "I'm not even five lines of markdown but i render myself!", - - -- preview can be rendered using global convert - 'preview: text/markdown': "See I have like - -- a list of things -- (two things) - -and some bold **text** and `code tags` with me.", - } - - require '.gallery', - - -- if we are on client, throw in twisted as a child - if MODE == 'CLIENT' then require '.twisted' -} diff --git a/app/mmmfs/tree/gallery.moon b/app/mmmfs/tree/gallery.moon new file mode 100644 index 0000000..93ca58d --- /dev/null +++ b/app/mmmfs/tree/gallery.moon @@ -0,0 +1,49 @@ +import div, h1, a, img, br from require 'lib.html' + +children = for i=1,100 + id = math.floor math.random! * 200 + Fileder { + 'name: alpha': "image#{id}" + 'URL -> image/png': "https://picsum.photos/600/600/?image=#{id}" + 'preview: URL -> image/png': "https://picsum.photos/200/200/?image=#{id}" + } + +props = { + 'name: alpha': 'gallery', + 'title: text/plain': "A Gallery of 100 random pictures, come in!", + 'preview: moon -> mmm/dom': => div { + 'the first pic as a little taste:', + br!, + img src: @children[1]\get 'preview', 'URL -> image/png' + } + 'moon -> mmm/dom': => + link = (child) -> a { + href: '#', + onclick: -> BROWSER\navigate { 'gallery', (child\get 'name', 'alpha'), nil }, + img src: child\gett 'preview', 'URL -> image/png' + } + + content = [link child for child in *@children] + table.insert content, 1, h1 'gallery index' + div content + + 'slideshow: moon -> mmm/dom': => + import ReactiveVar, text, elements from require 'lib.component' + + index = ReactiveVar 1 + + prev = (i) -> math.max 1, i - 1 + next = (i) -> math.min #@children, i + 1 + + e = elements + e.div { + e.div { + e.a 'prev', href: '#', onclick: -> index\transform prev + index\map (i) -> text " image ##{i} " + e.a 'next', href: '#', onclick: -> index\transform next + }, + index\map (i) -> img src: @children[i]\gett nil, 'URL -> image/png' + } +} + +Fileder props, children diff --git a/app/mmmfs/tree/init.moon b/app/mmmfs/tree/init.moon new file mode 100644 index 0000000..1d6a116 --- /dev/null +++ b/app/mmmfs/tree/init.moon @@ -0,0 +1,247 @@ +require = relative ... + +Fileder { + -- main content + -- doesn't have a name prefix (e.g. preview: moon -> mmm/dom) + -- uses the 'moon' interp to execute the lua/moonscript function on get + -- resolves to a value of type mmm/dom + 'moon -> mmm/dom': () => + html = require 'lib.html' + import article, h1, h2, h3, p, div, a, sup, ol, li, span, code, pre, br from html + + code = do + _code = code + (str) -> _code str\match '^ *(..-) *$' + + article with _this = {} + append = (a) -> table.insert _this, a + + footnote, getnotes = do + local * + notes = {} + + id = (i) -> "footnote-#{i}" + + footnote = (stuff) -> + i = #notes + 1 + notes[i] = stuff + sup a "[#{i}]", style: { 'text-decoration': 'none' }, href: '#' .. id i + + footnote, -> + args = for i, note in ipairs notes + li (span (tostring i), id: id i), ': ', note + notes = {} + table.insert args, style: { 'list-style': 'none', 'font-size': '0.8em' } + ol table.unpack args + + append h1 'mmmfs', style: { 'margin-bottom': 0 } + append p "a file and operating system to live in", style: { 'margin-top': 0, 'margin-bottom': '1em' } + + append p do + fileder = footnote "fileder: file + folder. 'node', 'table' etc. are too general to be used all over." + child = footnote "fileders can have multiple values, like the mentioned script, but these are not considered + children of the fileder, as they are not fileders themselves. One fileder can have many values of different + types/keys associated, but these have unspecified schemas and don't nest. In addition it can have many + children, which are fileders themselves and can nest, but they are not labelled." + + "in mmmfs, directories are files and files are directories, or something like that. + Listen, I don't really know yet either. The idea is that every node knows how to display it's contents; + so for example your 'Pictures' fileder", fileder, " contains a script within itself that renders + all the picture files you put into it at the children level", child, "." + + append p "a fileder should also be responsible for how it's children are sorted, filtered and interacted with. + For example you should be able to create a fileder that is essentially a 'word document' equivalent: it could + contain images, websites, links and of course text as children and let you reorder, layout and edit them in + it's own edit interface." + + append p "a picture fileder could have an alternate slideshow view (whoops - built that. Click on the 'gallery' + example below), or one that shows your geotagged images on a world map, if you really want that. + Maybe you could build a music folder that contains links to youtube videos, spotify tracks and just plain mp3 + files, and the folder knows how to play them all." + + append p "Sounds cool, no? Here's some examples of things a fileder can be or embed:" + + -- render a preview block + preview = (title, content, name) -> div { + h3 title, style: { margin: 0, cursor: 'pointer' }, onclick: -> BROWSER\navigate { name } + content or span '(no renderable content)', style: { color: 'red' }, + style: { + display: 'inline-block', + width: '300px', + height: '200px', + padding: '4px', + margin: '8px', + border: '4px solid #eeeeee', + overflow: 'hidden', + }, + } + + append div for child in *@children + -- get 'title' as 'text/plain' (error if no value or conversion possible) + title = child\gett 'title', 'text/plain' + + -- get 'preview' as a DOM description (nil if no value or conversion possible) + content = child\get 'preview', 'mmm/dom' + + -- get 'preview' as a DOM description (nil if no value or conversion possible) + name = child\gett 'name', 'alpha' + + preview title, content, name + + + append h2 "details" + append do + mmmdom = code ('mmm/dom'), footnote span (code 'mmm/dom'), " is a polymorphic content type; + on the server it is just an HTML string (like ", (code 'text/html'), "), + but on the client it is a JS DOM Element instance." + fengari = a 'fengari.io', href: 'https://fengari.io' + p "What you are viewing right now is a fileder that has a Lua/Moonscript function as a value which + is rendering all this text. It returns a value whose type interface is known as ", mmmdom, ", which is + basically an HTML subtree. The function can be run, and the results generated, statically on the server + (resulting in an HTML file), or dynamically on the client (via ", fengari, ").", br!, + "The function is passed the fileder itself (as a Lua table) and potentially also receives some other + helpers for accessing it's environment (parent fileders, functions for querying the tree etc) or info + specific to the function key/type, but I haven't built or thought about any of that yet. Sorry." + + append do + github = footnote a 's-ol/mmm', href: 'https://github.com/s-ol/mmm/tree/master/app/mmmfs' + p "Anyway, this node is set up as some sort of wiki/index thing and just lists its children-fileders' ", (code 'title: text/plain'), + " values and ", (code 'preview: mmm/dom'), " previews (if set). Oh and also everything is on github and stuff", github, + " if you care about that." + + append h3 "converts" + append p "Well actually it's a bit more complex. You see, the code that renders these previews ", (html.i "asks"), " for those + name/type pairs (", (code 'title: text/plain'), ', ', (code 'preview: mmm/dom'), "), but the values don't actually have to + be ", (html.i "defined"), " as these types." + + append pre code [[ +-- render a preview block +preview = (title, content) -> div { + h3 title, style: { ... }, + content or span '(no renderable content)', style: { ... }, + style: { ... } +} + +append div for child in *@children + -- get 'title' as 'text/plain' (error if no value or conversion possible) + title = child\gett 'title', 'text/plain' + + -- get 'preview' as a DOM description (nil if no value or conversion possible) + content = child\get 'preview', 'mmm/dom' + + preview title, content + ]] + + append p "For example, the markdown child below only provides ", (code 'preview'), " as ", (code 'text/markdown'), ":" + + append pre code [[ +Fileder { + 'title: text/plain': "I'm not even five lines of markdown but i render myself!", + 'preview: text/markdown': "See I have like + +- a list of things +- (two things) + +and some bold **text** and `code tags` with me.", +} + ]] + + append p "Then, globally, there are some conversion paths specified; such as one that maps from ", + (code 'text/markdown'), " to ", (code 'mmm/dom'), ":" + + append pre code [[ +table.insert converts, { + inp: 'text/markdown', + out: 'mmm/dom', + transform: (md) -> + -- polymorphic client/serverside implementation here, + -- uses lua-discount on the server, marked.js on the client +} + ]] + + append h3 "interps" + append p "In addition, a property can be encoded using ", (code 'interps'), ". For example the root node you are viewing + currently is defined as ", (code 'moon -> mmm/dom'), ", meaning it is to be interpreted by the ", (code 'moon'), + " interp before being treated as a regular ", (code 'mmm/dom'), " value." + + append p "The ", (code 'moon'), " interp takes a function value and calls it, passing the Fileder it is processing as ", + (code 'self'), ":" + + append pre code [[ +{ + name: 'moon', + transform: (method) => method @ +} + ]] + + append p "Both interps and converts are resolved automatically when asking for values, so this page is being + rendered just using ", (code "append root\\get 'mmm/dom'"), " as well." + + append h3 "interp overloading" + append p "The example with the image is curious as well. In mmmfs, you might want to save a link to an image, + without ever saving the actual image on your hard drive (or wherever the data may ever be stored - it is + quite transient currently). The image Fileder below has it's main (unnamed) value tagged as ", + (code 'URL -> image/png'), " - a png image, encoded as an URL. When accessed as ", (code 'image/png'), " + the URL should be resolved, and the binary data provided in it's place (yeah right - I haven't build that yet). + However, if a script is aware of URLs and knows a better way to handle them, then it can overload the URL + interp for it's fetch, to get at the raw data and use that URL instead. This is what the image demo does in + order to pass the URL to an ", (code 'img'), " tag's ", (code 'src'), " attribute:" + + append pre code [[ +Fileder { + 'title: text/plain': "Hey I'm like a link to picture or smth", + 'URL -> image/png': 'https://picsum.photos/200?random', + 'preview: moon -> mmm/dom': => + import img from require 'lib.html' + img src: @gett nil, -- look for: main content + 'image/png', -- with image type, and + URL: (url) => url -- override URL interp to get raw URL +} + ]] + + append getnotes! + + 'text/markdown': "this is a markdown version or something. + +There's no content here so switch back to the real one! +(Assuming there is a switching UI by the time you are reading this, which I assume since you are reading this at all. +If you are reading this in the source, then c'mon, just scroll past and give me a break.) + +(the switching UI has now been built.)" + + Fileder { + 'name: alpha': 'empty', + 'title: text/plain': "Hey I'm an ad-hoc child with no content at all", + } + + Fileder { + 'name: alpha': 'image', + 'title: text/plain': "Hey I'm like a link to picture or smth", + + -- main content is image/png, to be interpreted by URL to access + 'URL -> image/png': 'https://picsum.photos/200?random', + + -- preview is a lua/moonscript function that neturns an mmm/dom value + 'preview: moon -> mmm/dom': => + import img from require 'lib.html' + img src: @gett nil, 'URL -> image/png' -- look for main content with 'URL to png' type + } + + Fileder { + 'name: alpha': 'markdown', + 'title: text/plain': "I'm not even five lines of markdown but i render myself!", + + -- preview can be rendered using global convert + 'preview: text/markdown': "See I have like + +- a list of things +- (two things) + +and some bold **text** and `code tags` with me.", + } + + require '.gallery', + + -- if we are on client, throw in twisted as a child + if MODE == 'CLIENT' then require '.twisted' +} diff --git a/app/mmmfs/tree/twisted.moon b/app/mmmfs/tree/twisted.moon new file mode 100644 index 0000000..ec3fa83 --- /dev/null +++ b/app/mmmfs/tree/twisted.moon @@ -0,0 +1,48 @@ +Math = window.Math + +import CanvasApp from require 'lib.canvasapp' +import hsl from require 'lib.color' + +class TwistedDemo extends CanvasApp + width: 500 + height: 400 + length: math.pi * 4 + new: (preview) => + if preview + @width, @height = 120, 120 + super false, true + else + super true + @background = {Math.random!, Math.random!/3+.2, Math.random!/4} + hue = Math.random! + @shades = setmetatable {}, __index: (key) => + with val = { hue, .7, key * .3 + .1} do rawset @, key, val + + draw: => + @ctx.fillStyle = hsl @background + @ctx\fillRect 0, 0, @width, @height + @ctx\translate @width/2, @height/2 + 70 + + draw = (i) -> + @ctx\save! + @ctx\translate 0, -120*i + s = 1 - 0.1 * math.sin @time + i*2 + s *= 0.8 - i * .4 * math.cos @time + @ctx\scale s, s/2 + @ctx\rotate @time/4 + i * .6 * math.cos @time + @ctx.fillStyle = hsl table.unpack @shades[i] + @ctx\fillRect -80, -80, 160, 160 + @ctx\restore! + + for i=0,1,1/(20 + 19 * math.sin(@time / 2)) + draw i + draw 1 + +TwistedDemo! + +Fileder { + 'name: alpha': 'twisted', + 'title: text/plain': "canvas animation with static preview", + 'preview: moon -> mmm/component': => TwistedDemo true + 'moon -> mmm/component': => TwistedDemo! +} diff --git a/app/mmmfs/twisted.moon b/app/mmmfs/twisted.moon deleted file mode 100644 index ec3fa83..0000000 --- a/app/mmmfs/twisted.moon +++ /dev/null @@ -1,48 +0,0 @@ -Math = window.Math - -import CanvasApp from require 'lib.canvasapp' -import hsl from require 'lib.color' - -class TwistedDemo extends CanvasApp - width: 500 - height: 400 - length: math.pi * 4 - new: (preview) => - if preview - @width, @height = 120, 120 - super false, true - else - super true - @background = {Math.random!, Math.random!/3+.2, Math.random!/4} - hue = Math.random! - @shades = setmetatable {}, __index: (key) => - with val = { hue, .7, key * .3 + .1} do rawset @, key, val - - draw: => - @ctx.fillStyle = hsl @background - @ctx\fillRect 0, 0, @width, @height - @ctx\translate @width/2, @height/2 + 70 - - draw = (i) -> - @ctx\save! - @ctx\translate 0, -120*i - s = 1 - 0.1 * math.sin @time + i*2 - s *= 0.8 - i * .4 * math.cos @time - @ctx\scale s, s/2 - @ctx\rotate @time/4 + i * .6 * math.cos @time - @ctx.fillStyle = hsl table.unpack @shades[i] - @ctx\fillRect -80, -80, 160, 160 - @ctx\restore! - - for i=0,1,1/(20 + 19 * math.sin(@time / 2)) - draw i - draw 1 - -TwistedDemo! - -Fileder { - 'name: alpha': 'twisted', - 'title: text/plain': "canvas animation with static preview", - 'preview: moon -> mmm/component': => TwistedDemo true - 'moon -> mmm/component': => TwistedDemo! -} diff --git a/lib/component.client.moon b/lib/component.client.moon index 617deb5..af0b38a 100644 --- a/lib/component.client.moon +++ b/lib/component.client.moon @@ -102,6 +102,7 @@ class ReactiveElement @node.style[k] = v return + print "setting attr #{attr}" @node[attr] = value append: (child, last) => diff --git a/lib/init.client.moon b/lib/init.client.moon index c1aea07..d39143d 100644 --- a/lib/init.client.moon +++ b/lib/init.client.moon @@ -5,8 +5,25 @@ window = js.global { :document, :console } = window MODE = 'CLIENT' -print = console\log -warn = console\warn + +deep_tostring = (tbl, space='') -> + buf = space .. tostring tbl + + return buf unless 'table' == type tbl + + buf = buf .. ' {\n' + for k,v in pairs tbl + buf = buf .. "#{space} [#{k}]: #{deep_tostring v, space .. ' '}\n" + buf = buf .. "#{space}}" + buf + +print = (...) -> + contents = [deep_tostring v for v in *{ ... } ] + console\log table.unpack contents + +warn = (...) -> + contents = [deep_tostring v for v in *{ ... } ] + console\warn table.unpack contents package.path = '/?.shared.moon.lua;/?.client.moon.lua;/?.moon.lua;/?/init.moon.lua;/?.lua;/?/init.lua' diff --git a/lib/init.server.moon b/lib/init.server.moon index a91bd52..f81a2dc 100644 --- a/lib/init.server.moon +++ b/lib/init.server.moon @@ -1,9 +1,27 @@ -export MODE, warn, relative, append, on_client +export MODE, print, warn, relative, append, on_client MODE = 'SERVER' +deep_tostring = (tbl, space='') -> + buf = space .. tostring tbl + + return buf unless 'table' == type tbl + + buf = buf .. ' {\n' + for k,v in pairs tbl + buf = buf .. "#{space} [#{k}]: #{deep_tostring v, space .. ' '}\n" + buf = buf .. "#{space}}" + buf + +print = do + _print = print + (...) -> + contents = [deep_tostring v for v in *{ ... } ] + _print table.unpack contents + -- warning messages warn = (...) -> - io.stderr\write table.concat { ... }, '\t' + contents = [deep_tostring v for v in *{ ... } ] + io.stderr\write table.concat contents, '\t' io.stderr\write '\n' -- relative imports -- cgit v1.2.3