├── .github └── workflows │ └── test.yml ├── .gitignore ├── Makefile ├── README.md ├── bin └── sitegen ├── doc ├── creating_a_plugin.md ├── html_helpers.md ├── plugins.md ├── renderers_html.md ├── renderers_markdown.md └── renderers_moonscript.md ├── homepage ├── README.md ├── index.md ├── main.coffee ├── site.moon ├── snippets │ ├── command.md │ └── sitemoon.md ├── style.scss ├── templates │ └── index.html └── www │ ├── .gitignore │ └── img │ └── leafo.svg ├── lint_config.lua ├── lint_config.moon ├── sitegen-0.1-1.rockspec ├── sitegen-dev-1.rockspec ├── sitegen.moon ├── sitegen ├── cache.lua ├── cache.moon ├── cmd │ ├── actions.lua │ ├── actions.moon │ ├── argparser.lua │ ├── argparser.moon │ ├── util.lua │ └── util.moon ├── common.lua ├── common.moon ├── cosmo.lua ├── cosmo.moon ├── default │ ├── templates.lua │ └── templates.moon ├── dispatch.lua ├── dispatch.moon ├── formatters │ ├── default.lua │ └── default.moon ├── header.lua ├── header.moon ├── html.lua ├── html.moon ├── init.lua ├── init.moon ├── output.lua ├── output.moon ├── page.lua ├── page.moon ├── path.lua ├── path.moon ├── plugin.lua ├── plugin.moon ├── plugins │ ├── analytics.lua │ ├── analytics.moon │ ├── blog.lua │ ├── blog.moon │ ├── coffee_script.lua │ ├── coffee_script.moon │ ├── deploy.lua │ ├── deploy.moon │ ├── dump.lua │ ├── dump.moon │ ├── feed.lua │ ├── feed.moon │ ├── indexer.lua │ ├── indexer.moon │ ├── pygments.lua │ ├── pygments.moon │ ├── syntaxhighlight.lua │ ├── syntaxhighlight.moon │ ├── tupfile.lua │ └── tupfile.moon ├── query.lua ├── query.moon ├── renderer.lua ├── renderer.moon ├── renderers │ ├── html.lua │ ├── html.moon │ ├── lapis.lua │ ├── lapis.moon │ ├── markdown.lua │ ├── markdown.moon │ ├── moon.lua │ └── moon.moon ├── site.lua ├── site.moon ├── site_file.lua ├── site_file.moon ├── site_scope.lua ├── site_scope.moon ├── templates.lua ├── templates.moon ├── tools.lua ├── tools.moon ├── watch.lua └── watch.moon └── spec ├── .gitignore ├── dispatch_spec.moon ├── factory.moon ├── header_spec.moon ├── html_spec.moon ├── page_spec.moon ├── plugins_spec.moon ├── renderer_spec.moon └── sitegen_spec.moon /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | luaVersion: ["5.1", "5.2", "5.3", "5.4", "luajit"] 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@master 16 | 17 | - uses: leafo/gh-actions-lua@master 18 | with: 19 | luaVersion: ${{ matrix.luaVersion }} 20 | 21 | - uses: leafo/gh-actions-luarocks@master 22 | 23 | - name: build 24 | run: | 25 | luarocks install https://raw.githubusercontent.com/leafo/lua-cjson/master/lua-cjson-dev-1.rockspec 26 | luarocks install busted 27 | luarocks install moonscript 28 | luarocks install https://luarocks.org/manifests/leafo/lapis-dev-1.rockspec 29 | luarocks make 30 | 31 | - name: test 32 | run: | 33 | busted -o utfTerminal 34 | 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lint_config.lua 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: build local install lint 3 | 4 | local: build 5 | luarocks make --lua-version=5.1 --local 6 | 7 | build:: 8 | moonc sitegen 9 | 10 | lint:: 11 | moonc -l sitegen 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sitegen 2 | 3 | ![test](https://github.com/leafo/sitegen/workflows/test/badge.svg) 4 | 5 | A static site generator written in [MoonScript](http://moonscript.org). 6 | 7 | 8 | 9 | ## Sites built with sitegen 10 | 11 | * 12 | * 13 | * 14 | * 15 | * 16 | * 17 | * 18 | * 19 | 20 | -------------------------------------------------------------------------------- /bin/sitegen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env moon 2 | 3 | default_action = "build" 4 | 5 | argparser = require "sitegen.cmd.argparser" 6 | 7 | args = argparser\parse [v for _, v in ipairs _G.arg] 8 | 9 | import find_action from require "sitegen.cmd.actions" 10 | import catch_error, throw_error from require "sitegen.common" 11 | 12 | catch_error -> 13 | fn = find_action args.action or default_action 14 | unless fn 15 | throw_error "unknown task: " .. action 16 | 17 | fn args 18 | 19 | -- vim: set filetype=moon: 20 | -------------------------------------------------------------------------------- /doc/creating_a_plugin.md: -------------------------------------------------------------------------------- 1 | { 2 | target: "doc/creating-a-plugin" 3 | id: "creating_a_plugin" 4 | title: "Creating a plugin - Sitegen" 5 | } 6 | 7 | # Creating a Plugin 8 | 9 | $index{min_depth = 2} 10 | 11 | Much of the built in functionality in Sitegen is implemented by plugins. You 12 | can create your own plugins to add custom functionality to the various steps of 13 | building the site. A plugin is implemented as a MoonScript class. Functionality 14 | is injected into the build by implemending certain properties or methods. The 15 | *Plugin Lifecycle* describes what happens to a plugin when Sitegen builds the 16 | site. 17 | 18 | ## Plugin Lifecycle 19 | 20 | * Each plugin is instantiated for the site being compiling. The constructor receives one argument, the instance of the site. The instances of the plugins are stored in the site. 21 | * Before Sitegen reads `site.moon`... 22 | * For each plugin that provides a `mixin_funcs` property: 23 | * Each method listed in `mixin_funcs` is copied to the site's config scope. These methods can be called from `site.moon`, they are bound to the instance of the plugin 24 | * For each plugin with a `type_name` property: 25 | * Plugin is saved as an aggregator for that type 26 | * As Sitegen renders each page... 27 | * For each page that has an `is_a` property: 28 | * Any plugins marked as aggregators with a `type_name` matching a value in `is_a` are called via the `on_aggregate` method. The current page is passed to the method as the first argument 29 | * For each plugin that provides a `tpl_helpers` property: 30 | * Each method listed in `tpl_helpers` is made available to be called in any template, the methods are bound the instance of the plugin. They will also receive an instance of a page before any arguments passed to the method. 31 | * After Sitegen has rendered all the pages... 32 | * Any plugins implementing a `write` method are called. The `write` method is called with no arguments. This is when a plugin can create any necessary side effect files 33 | 34 | ### Terminology 35 | 36 | **site scope**: the object that represents the function environment that is 37 | used when running the initialize function. The `mixin_funcs` property on a 38 | plugin is used to extend this scope. 39 | 40 | **template scope**: the object that holds all the fields available to any 41 | template rendered for that page. The `tpl_helpers` property on a plugin is used 42 | to extend this scope. 43 | 44 | -------------------------------------------------------------------------------- /doc/html_helpers.md: -------------------------------------------------------------------------------- 1 | { 2 | target: "doc/html-helpers" 3 | id: "html_helpers" 4 | title: "HTML Variables & Helpers" 5 | } 6 | 7 | # Variables & helpers 8 | 9 | When a page is rendering it has access to a set of variables and helper 10 | functions. They can be provided in three scopes, with lowest precednece first: 11 | 12 | * Built in variables -- provided by `sitegen` 13 | * Site level variables -- set in `site.moon` 14 | * Page level variables -- set in the page 15 | 16 | 17 | The variables can be accessed depending on the page renderer used. In the 18 | [Markdown][markdown-renderer] and [HTML renderers][html-renderer], the templating language cosmo is used. You can 19 | access variables by prefixing them with `$`: 20 | 21 | 22 | ```html 23 | { 24 | title: "Hello and welcome to the page" 25 | date: "June 5th, 2022" 26 | } 27 | 28 |

$title

29 | 30 | 31 |

Hello!

32 | ``` 33 | 34 | $index{min_depth = 2} 35 | 36 | ## Built in variables 37 | 38 | There are a few variables that are always available, here they are: 39 | 40 | * `$root` -- a relative path prefix that points back to the top level directory of your site. For example, if the current page is `wwww/hello/world.html`, the `$root` would be `../`. This make it easy to built URLs to other pages without being concerned about where your page is being rendered to. 41 | * `$generate_date` -- a timestamp of when the page is being generated. Good for displaying on your page, and also adding to URLs of assets as a cache buster. 42 | 43 | [Plugins][plugins] may also introduce variables, in addition to functions, that can be accessed from a page. 44 | 45 | 46 | ## Built in functions 47 | 48 | In addition to variables, there are a handful of built in functions that can be 49 | called from pages and templates. Cosmo provides syntax for calling functions. 50 | You still prefix their name with `$` but you can pass arguments with `{}` and a 51 | subtemplate with `[[]]`. The examples below will demonstrate. 52 | 53 | ### `$render` 54 | 55 | Renders another template. Templates are searched relative to the directory 56 | where `site.moon` is located. Any of the page types supported by sitegen can be 57 | rendered. 58 | 59 | ```html 60 |

Here are my favorite links:

61 | $render{"favorite_links.md"} 62 | ``` 63 | 64 | ### `$markdown` 65 | 66 | Renders markdown directly into the current page. This is useful when you have 67 | an HTML page that you'd like to insert some formatted text into easily: 68 | 69 | Takes one argument, a string of markdown. 70 | 71 | ```html 72 | 83 | ``` 84 | 85 | ### `$wrap` 86 | 87 | ### `$url_for` 88 | 89 | ### `$query_pages` 90 | 91 | ### `$query_page` 92 | 93 | Like `$query_pages` but will throw an error unless 1 page is returned from the 94 | query. 95 | 96 | ### `$neq` 97 | 98 | ### `$eq` 99 | 100 | ### `$if{cond}[[subtemplate]]` 101 | 102 | ### `$each{items, "name"}[[subtemplate]]` 103 | 104 | Iterates through the array `items` executing the subtemplate each time. The 105 | current item is assigned to `name` within the subtemplate. 106 | 107 | ### `$is_page{query_args}[[subtemplate]]` 108 | 109 | Runs the subtemplate if if `query_args` matches the current page. 110 | 111 | ## Site variables 112 | 113 | Site variables are set in `site.moon` and apply to every page rendered. If a 114 | page variable happens to override the same name of a site variable, it does not 115 | affect other pages. 116 | 117 | You can set a site variable by storing it on `self` in the site constructor 118 | function: 119 | 120 | ```moon 121 | sitegen = require "sitegen" 122 | 123 | sitegen.create => 124 | @version = "1.2.3-alpha-gamma" 125 | add "index.html" 126 | ``` 127 | 128 | Inside your pages you can now access the variable using the appropriate syntax: 129 | 130 | ```html 131 |

Current version: $version

132 | ``` 133 | 134 | ## Page variables 135 | 136 | 137 | [cosmo]: http://cosmo.luaforge.net/ 138 | [plugins]: $root/doc/plugins.html 139 | [markdown-renderer]: $url_for{id="renderers.markdown"} 140 | [html-renderer]: $url_for{id="renderers.html"} 141 | 142 | -------------------------------------------------------------------------------- /doc/plugins.md: -------------------------------------------------------------------------------- 1 | { 2 | target: "doc/plugins" 3 | id: "plugins" 4 | title: "Plugins - Sitegen" 5 | } 6 | 7 | # Plugins 8 | 9 | $index{min_depth = 2} 10 | 11 | ## Method Types 12 | 13 | Plugins provide methods to different parts of the site generation pipeline. The 14 | method types are: 15 | 16 | * **template helpers**: Method made available in any template file (html, markdown, etc) 17 | * **site helpers**: available in the `site.moon` initialization function. 18 | * **command line tool**: available as an action in the command line tool, `sitegen`. 19 | 20 | Plugins can also change other aspects of the pipeline, for example, the 21 | [Pygments](#pygments) plugin adds a pre-renderer to all markdown files which 22 | let's you specify code blocks in a special syntax. 23 | 24 | You can create your own plugins, see the [Creating a Plugin]($root/doc/creating-a-plugin.html) guide. 25 | 26 | ## Available Plugins 27 | 28 | ### Feed 29 | 30 | Provides a site helper called `feed` that triggers a feed to be written from a 31 | MoonScript file when the site is written. First argument is source, second is 32 | destination. 33 | 34 | ```moon 35 | feed "my_feed.moon", "feeds/feed.xml" 36 | ``` 37 | 38 | The feed file must return a table of feed entires: 39 | 40 | ```moon 41 | -- my_feed.moon 42 | date = require "date" 43 | return { 44 | format: "markdown" 45 | title: "My Site's Title" 46 | { 47 | title: "The First Post" 48 | date: date 2011, 11, 26 49 | link: "http://example.com/my-post" 50 | description: [[ 51 | The things I did. 52 | 53 | * ordered pizza 54 | * ate it 55 | ]] 56 | } 57 | } 58 | ``` 59 | 60 | 61 | When rendering each entry, if a key is missing the entry, it will be searched 62 | for in the root. This lets you set defaults for entries. 63 | 64 | The `format` field is special. If it is set to `"markdown"` then all 65 | descriptions will be rendered to html from markdown. 66 | 67 | ### Deploy 68 | 69 | Provides site helper `deploy_to` and a command line helper `deploy`. 70 | 71 | `deply_to` is used to configure where the site should be deployed to, it takes 72 | two arguments, a host and a path. This can be done in the initialization 73 | function: 74 | 75 | ```moon 76 | deploy_to "leaf@leafo.net", "www/mysite" 77 | ``` 78 | 79 | Deploying is done over ssh with rsync. It uses the command `rsync -arvuz www/ 80 | $host:$path`. 81 | 82 | Assuming everything is configured correctly, the site can be deployed from the 83 | command line: 84 | 85 | ```bash 86 | $ sitegen deploy 87 | ``` 88 | 89 | The deploy command line helper will only deploy, it will not build. Make sure 90 | you build the site first. 91 | 92 | ### Indexer 93 | 94 | Provides a template helper, `index`, that indexes the current page based on 95 | headers. It scans the html for header tags (`h1`, `h2`, etc.) and inserts 96 | anchors inside of them. It then renders the tree of headers to a list, with 97 | links to the anchors. 98 | 99 | An example page with a header hierarchy. The index rendered at the top: 100 | 101 | $index 102 | 103 | # My Title 104 | ## Sub-section 105 | ## Another Sub-section 106 | ### Deeper 107 | # Upper Level 108 | ## Cool 109 | 110 | ### Pygments 111 | 112 | Provides new syntax for markdown rendered files. The syntax lets you describe a 113 | code block that should be highlighted according to a specified language. 114 | 115 | For example, to highlight Lua code in a page: 116 | 117 | ```lua 118 | local test = function(...) 119 | print("hello world", ...) 120 | end 121 | 122 | test("moon", 1, 2, 3) 123 | ``` 124 | 125 | The generated code does not have the colors embedded, only html tags with class 126 | names. Colors can be added in a stylesheet. 127 | 128 | This plugin requires that the [Pygments command line 129 | tool](http://pygments.org/docs/cmdline/) is installed. 130 | 131 | ### CoffeeScript 132 | 133 | Provies a template helper, `render_coffee` that lets you embed compiled 134 | CoffeeScript into the page from an external file. CoffeeScript must be 135 | installed on the system for this plugin to work. 136 | 137 | In some page: 138 | 139 | 140 | $render_coffee{[[my_script.coffee]]} 141 | 142 | It will produce `script` tags embedded with the resulting JavaScript. 143 | 144 | ### Analytics 145 | 146 | Provides a template helper, `analytics`, that lets your easily embed the Google 147 | Analytics tracking code. Takes one argument, the account code as a string. 148 | 149 | In some page: 150 | 151 | $analytics{[[UA-000000]]} 152 | 153 | ### Dump 154 | 155 | Provides a template helper, `dump` that dumps the contents of a variable. 156 | Will pretty-print tables. Useful for debugging. 157 | 158 | In some page 159 | 160 | $dump{title} 161 | 162 | -------------------------------------------------------------------------------- /doc/renderers_html.md: -------------------------------------------------------------------------------- 1 | { 2 | id: "renderers.html" 3 | target: "doc/renderers-html" 4 | } 5 | 6 | # HTML renderer 7 | 8 | The HTML renderer takes an HTML file and renders it after interpolating any 9 | variables or expressions with cosmo. The HTML renderer is used when you add a 10 | file that ends in `.html` 11 | 12 | HTML templates also support a *frontmatter* object that lets you add more 13 | variables to the page while it compiles. Place a MoonScript object starting at 14 | the first line of the file to provide additional variables: 15 | 16 | ```html 17 | { 18 | title: "Hello and welcome to the page" 19 | date: "June 5th, 2022" 20 | } 21 | 22 |

$title

23 | 24 | 25 |

Hello!

26 | ``` 27 | 28 | -------------------------------------------------------------------------------- /doc/renderers_markdown.md: -------------------------------------------------------------------------------- 1 | { 2 | id: "renderers.markdown" 3 | target: "doc/renderers-markdown" 4 | } 5 | 6 | # Markdown renderer 7 | 8 | The Markdown renderer renders a page using a Markdown template. It's 9 | automatically acitvated when you add a file ending in `.md` to your sitefile. 10 | 11 | The markdown template is compiled to HTML before any variables or cosmo 12 | expressions are executed and replaced. 13 | 14 | Markdown template support a *frontmatter* object that lets you add more 15 | variables to the page while it compiles. Place a MoonScript object starting at 16 | the first line of the file to provide additional variables: 17 | 18 | ```html 19 | { 20 | title: "Hello and welcome to the page" 21 | date: "June 5th, 2022" 22 | } 23 | 24 | # $title 25 | 26 | ## $date 27 | 28 | Hello world, here are some foots: 29 | 30 | * Apple 31 | * Ice cream 32 | ``` 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /doc/renderers_moonscript.md: -------------------------------------------------------------------------------- 1 | { 2 | id: "renderers.moonscript" 3 | target: "doc/renderers-moonscript" 4 | } 5 | 6 | # MoonScript renderer 7 | 8 | The MoonScript renderer lets you write a page or template as MoonScript code. 9 | You have the full power of the language to do anything you need to do to 10 | generate the final result. This renderer is automatically used when you add a 11 | file ending in `.moon` in your sitefile. 12 | 13 | ```moon 14 | html -> 15 | h1 "Welcome to my page: #{@page_titler}" 16 | ``` 17 | 18 | -------------------------------------------------------------------------------- /homepage/README.md: -------------------------------------------------------------------------------- 1 | # Sitegen 2 | 3 | This is the sitegen homepage in sitegen format. -------------------------------------------------------------------------------- /homepage/index.md: -------------------------------------------------------------------------------- 1 | { 2 | id: "root" 3 | } 4 | 5 | # Sitegen 6 | 7 | Sitegen assembles static webpages through a pipeline consisting of templates and 8 | pages. If you're looking for something dynamic try out [Lapis](http://leafo.net/lapis). 9 | 10 | Pages and templates can be written in html or markdown. The site is defined 11 | through the `site.moon` file, which is written in [MoonScript][2]. It describes 12 | all pages that need to be brought in, it can also specify configuration 13 | variables accessible within pages and templates. 14 | 15 | Pages can be assigned any number of **types**, which lets your aggregate pages 16 | into groups. Enabling you to create blogs, among other things. 17 | 18 | Sitegen has a [plugin system][3] that lets you transform the page as it travels 19 | through the pipeline. Letting you do things like syntax highlighting and 20 | automatically generated headers. 21 | 22 | Sitegen uses the [cosmo templating language][1] to inject variables, run 23 | functions, and trigger actions in the body of the page as it is being created. 24 | 25 | $index{min_depth = 2} 26 | 27 | ## Install 28 | 29 | ```bash 30 | $ luarocks install sitegen 31 | ``` 32 | 33 | [Get source on GitHub](https://github.com/leafo/sitegen). 34 | 35 | ## Quick start 36 | 37 | ### Basic site 38 | 39 | To create a new site we just need to create a `site.moon` file in a directory 40 | of our choosing. We'll call the `create` function on the `sitegen` module to 41 | initialize the site. `create` takes one argument, a function that will be used 42 | to initialize the site. An empty function, `=>`, is perfectly valid. 43 | 44 | ```moonscript 45 | -- site.moon 46 | sitegen = require "sitegen" 47 | 48 | sitegen.create => 49 | ``` 50 | 51 | We can tell our site to build by using the `sitegen` command, run it from the 52 | same directory as `site.moon`. (You can also run it in any child directories, 53 | but we don't have any yet.) 54 | 55 | ```bash 56 | $ sitegen 57 | ``` 58 | 59 | Since our site file is empty it won't do anything except create a cache file. 60 | 61 | Sitegen works great with markdown, lets create a new page in markdown, 62 | `index.md`: 63 | 64 | Hello, and welcome to *my homepage!* 65 | 66 | Update `site.moon` to have that file: 67 | 68 | ```moonscript 69 | -- site.moon 70 | sitegen = require "sitegen" 71 | 72 | sitegen.create => 73 | add "index.md" 74 | ``` 75 | 76 | And now tell it to build: 77 | 78 | ```bash 79 | $ sitegen 80 | ``` 81 | 82 | Every time you edit the markdown file you'll have to tell Sitegen to rebuild. 83 | That can be annoying. Start *watch* mode to have it listen for file changes and 84 | automatically rebuild: 85 | 86 | ```bash 87 | $ sitegen watch 88 | ``` 89 | 90 | Whenever you edit an input file, the corresponding output file will be built. 91 | If you edit `site.moon` you'll have to restart watch mode, sorry! 92 | 93 | ### Variables 94 | 95 | Sometimes you want to share a piece of data across many pages, say a 96 | *version_number* for a open source project's homepage. Just assign the variable 97 | on `self`: 98 | 99 | ```moonscript 100 | sitegen = require "sitegen" 101 | 102 | sitegen.create => 103 | @version = "1.2.3-alpha-gamma" 104 | add "index.md" 105 | ``` 106 | 107 | Then reference it with `$` in your page, here's `index.md`: 108 | 109 | # Welcome 110 | The current version is $version 111 | 112 | 113 | 114 | ### Templates 115 | 116 | If you looked at the compiled output of any of the examples above you may have 117 | noticed that each page got wrapped in an `` tag along with a `` and 118 | ``. The *template* defines what wraps each page's contents, there's a 119 | default one that adds those tags. The default one doesn't add much, so you'll 120 | want to create your own. 121 | 122 | Here's what the default template looks like: 123 | 124 | ```html 125 | 126 | 127 | 128 | 129 | $title 130 | 131 | 132 | $body 133 | 134 | 135 | ``` 136 | 137 | The `$body` variable gets the contents of the page, the `$title` variable lets 138 | you set the title of the page. It's `nil` by default, but you can set it in 139 | your `site.moon` 140 | 141 | Templates live in the `templates/` directory next to `site.moon`. If you name a 142 | template `index` then it will take place of the default one provided by 143 | Sitegen. Here's a custom default template: 144 | 145 | ```html 146 | 147 | 148 | 149 |

GREETINGS

150 | $body 151 | 152 | 153 | ``` 154 | 155 | Make sure to include `$body`, otherwise the contents of your page will not be 156 | visible. 157 | 158 | ### Page options 159 | 160 | You can pass individual pages custom options to control how they are rendered, 161 | like where they are written to and what template they use. You can pass these 162 | options to the `add` function in `site.moon`: 163 | 164 | ```moonscript 165 | sitegen = require "sitegen" 166 | 167 | sitegen.create => 168 | add "home.md", template: "jumbo_layout", target: "index.html" 169 | ``` 170 | 171 | This will cause the page to be written to `www/index.html`, and it will use the 172 | template located in `templates/jumbo_layout.html`. 173 | 174 | ### Page types 175 | 176 | In all the previous examples we've used Markdown files for our pages. You can 177 | also use HTML files. 178 | 179 | All pages are passed through a preprocessor that fills in the variables and 180 | runs any functions, so HTML pages can access the same things as Markdown pages. 181 | 182 | To create an HTML page we just give it the extension `html`: 183 | 184 | ```moonscript 185 | sitegen = require "sitegen" 186 | 187 | sitegen.create => 188 | add "about.html" 189 | ``` 190 | 191 | ```html 192 | 193 |

This page was generated on $generate_date, Go home

194 | ``` 195 | 196 | ## Next 197 | 198 | Now that you know how Sitegen works, you'll want to look over the [plugins][3] 199 | to learn about the additional functionality. All plugins and enabled by default 200 | so no extra steps are required to use them. 201 | 202 | ## License 203 | 204 | MIT License, Copyright (C) 2015 by Leaf Corcoran 205 | 206 | 207 | [1]: http://cosmo.luaforge.net/ 208 | [2]: http://moonscript.org/ 209 | [3]: $root/doc/plugins.html 210 | 211 | -------------------------------------------------------------------------------- /homepage/main.coffee: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leafo/sitegen/ac9b7c199ed7adb32064b056a438cbaed8c71d23/homepage/main.coffee -------------------------------------------------------------------------------- /homepage/site.moon: -------------------------------------------------------------------------------- 1 | sitegen = require "sitegen" 2 | 3 | tools = require "sitegen.tools" 4 | 5 | sassc = tools.system_command "sassc < %s > %s", "css" 6 | coffeescript = tools.system_command "coffee -c -s < %s > %s", "js" 7 | 8 | sitegen.create => 9 | deploy_to "leaf@leafo.net", "www/sitegen" 10 | 11 | build sassc, "style.scss", "style.css" 12 | build coffeescript, "main.coffee", "main.js" 13 | 14 | add "index.md" 15 | add "../doc/plugins.md" 16 | add "../doc/creating_a_plugin.md" 17 | add "../doc/html_helpers.md" 18 | add "../doc/renderers_markdown.md" 19 | add "../doc/renderers_html.md" 20 | 21 | @title = "Sitegen" 22 | @url = "http://leafo.net/sitegen/" 23 | 24 | -------------------------------------------------------------------------------- /homepage/snippets/command.md: -------------------------------------------------------------------------------- 1 | 2 | ```bash 3 | $ sitegen new 4 | -> made directory www 5 | -> made directory templates 6 | -> wrote site.moon 7 | 8 | $ sitegen page "Cool Things" 9 | -> wrote cool_things.md 10 | 11 | $ sitegen 12 | rendered index.html -> www/index.html 13 | rendered cool_things.md -> www/cool_things.html 14 | rendered doc/ref.md -> www/doc/ref.html 15 | 16 | $ sitegen deploy 17 | -> uploading to: leaf@leafo.net www/sitegen 18 | ``` 19 | -------------------------------------------------------------------------------- /homepage/snippets/sitemoon.md: -------------------------------------------------------------------------------- 1 | 2 | ```moon 3 | -- site.moon 4 | require "sitegen" 5 | site = sitegen.create_site => 6 | deploy_to "leaf@leafo.net", "www/sitegen" 7 | 8 | @title = "Sitegen" 9 | @url = "http://leafo.net/sitegen/" 10 | 11 | add "index.html" 12 | add "doc/ref.md" 13 | 14 | site\write! 15 | ``` 16 | -------------------------------------------------------------------------------- /homepage/style.scss: -------------------------------------------------------------------------------- 1 | $site_width: 650px; 2 | 3 | @mixin border_box { 4 | box-sizing: border-box; 5 | -moz-box-sizing: border-box; 6 | } 7 | 8 | body { 9 | background: #333; 10 | color: #eee; 11 | margin: 0px; 12 | padding: 0px; 13 | font-family: 'Open Sans', sans-serif; 14 | font-size: 16px; 15 | } 16 | 17 | .header { 18 | background: white; 19 | text-align: center; 20 | .logo_link { 21 | display: inline-block; 22 | vertical-align: top; 23 | text-decoration: none; 24 | background: #eee; 25 | padding: 8px 15px; 26 | 27 | &:hover { 28 | background: darken(#eee, 5%); 29 | } 30 | 31 | img { 32 | display: block; 33 | height: 40px; 34 | } 35 | } 36 | } 37 | 38 | .navigation { 39 | @include border_box; 40 | line-height: 1.2; 41 | padding: 10px; 42 | border: 1px solid lighten(#333, 10%); 43 | border-radius: 3px; 44 | margin-top: 40px; 45 | 46 | ul, li { 47 | margin: 0; 48 | padding: 0; 49 | list-style: none; 50 | } 51 | 52 | li { 53 | margin-bottom: 10px; 54 | &:last-child { 55 | margin-bottom: 0; 56 | } 57 | } 58 | 59 | strong { 60 | font-size: 13px; 61 | text-transform: uppercase; 62 | } 63 | 64 | a { 65 | opacity: 0.8; 66 | 67 | &.current { 68 | font-weight: bold; 69 | opacity: 1.0; 70 | } 71 | } 72 | 73 | } 74 | 75 | p, li { 76 | line-height: 1.5; 77 | } 78 | 79 | a { 80 | color: #A3FF57; 81 | text-decoration: none; 82 | border-bottom: 1px dotted #A3FF57; 83 | 84 | &:hover { 85 | color: #BDFF87; 86 | border-bottom-color: #BDFF87; 87 | } 88 | } 89 | 90 | pre { 91 | background: rgba(black, 0.5); 92 | border-radius: 8px; 93 | padding: 8px; 94 | color: white; 95 | 96 | font-size: 14px; 97 | } 98 | 99 | p > code, li > code { 100 | font-size: 14px; 101 | background: #444; 102 | border-radius: 4px; 103 | padding: 2px 4px; 104 | } 105 | 106 | .footer { 107 | text-align: center; 108 | font-size: 10px; 109 | color: #aaa; 110 | margin-bottom: 100px; 111 | } 112 | 113 | 114 | .highlight { 115 | /* builtins */ 116 | .nb, .cp { 117 | color: #F69385; // new 118 | } 119 | 120 | /* strings */ 121 | .s, .s1, .s2, .se { 122 | color: #F1BF8E; // new 123 | } 124 | 125 | /* proper names, self */ 126 | .nc, .vc, .bp { 127 | color: #99CBCA; // new 128 | } 129 | 130 | /* true, false, nil */ 131 | .kc { 132 | color: #B3EFE5; // new 133 | } 134 | 135 | /* function lit, braces, parens */ 136 | .nf, .kt, .na { 137 | color: #B0D89C; // new 138 | } 139 | 140 | /* operators */ 141 | .o, .si { 142 | color: #F277A1; // new 143 | } 144 | 145 | .nv { 146 | color: #F277A1; // new 147 | } 148 | 149 | /* keywords */ 150 | .k, .kd, .nt { 151 | color: #BB84B4; // new 152 | } 153 | 154 | .c1, .c2, .c { 155 | color: #929292; 156 | } 157 | 158 | // numbers 159 | .m, .mi, .mf, .mh { 160 | color: #9D8FF2; // new 161 | } 162 | } 163 | 164 | 165 | .page_columns { 166 | margin: 0 20px; 167 | 168 | display: flex; 169 | align-items: flex-start; 170 | justify-content: center; 171 | 172 | article { 173 | max-width: 650px; 174 | min-width: 0; 175 | flex: 1; 176 | 177 | margin-left: 40px; 178 | 179 | @media (max-width: 600px) { 180 | padding: 0 20px; 181 | } 182 | 183 | } 184 | 185 | navigation { 186 | width: 200px; 187 | } 188 | } 189 | 190 | -------------------------------------------------------------------------------- /homepage/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $title 6 | 7 | 8 | $analytics{"UA-136625-1"} 9 | 10 | 11 |
12 | 13 | leafo.net logo 14 | 15 |
16 | 17 |
18 | 19 | 52 | 53 | 54 |
$body 55 |
56 |
57 | 58 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /homepage/www/.gitignore: -------------------------------------------------------------------------------- 1 | doc/creating-a-plugin.html 2 | doc/html-helpers.html 3 | doc/plugins.html 4 | doc/renderers-html.html 5 | doc/renderers-markdown.html 6 | index.html -------------------------------------------------------------------------------- /lint_config.lua: -------------------------------------------------------------------------------- 1 | return { 2 | whitelist_globals = { 3 | ["."] = { }, 4 | ["sitegen/page"] = { 5 | "a" 6 | }, 7 | ["sitegen/plugins/coffee_script"] = { 8 | "script", 9 | "raw" 10 | }, 11 | ["sitegen/plugins/feed"] = { 12 | "raw", 13 | "rss", 14 | "channel", 15 | "title", 16 | "link", 17 | "description", 18 | "item", 19 | "pubDate", 20 | "cdata" 21 | }, 22 | ["sitegen/plugins/pygments"] = { 23 | "pre", 24 | "code", 25 | "raw" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lint_config.moon: -------------------------------------------------------------------------------- 1 | { 2 | whitelist_globals: { 3 | ["."]: { } 4 | 5 | ["sitegen/page"]: { 6 | "a" 7 | } 8 | 9 | ["sitegen/plugins/coffee_script"]: { 10 | "script", "raw" 11 | } 12 | 13 | ["sitegen/plugins/feed"]: { 14 | "raw", "rss", "channel", "title", "link", "description", "item", 15 | "pubDate", "cdata" 16 | } 17 | 18 | ["sitegen/plugins/pygments"]: { 19 | "pre", "code", "raw" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sitegen-0.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "sitegen" 2 | version = "0.1-1" 3 | 4 | source = { 5 | url = "git://github.com/leafo/sitegen.git", 6 | branch = "v0.1" 7 | } 8 | 9 | description = { 10 | summary = "A tool for assembling static webpages with markdown", 11 | homepage = "http://leafo.net/sitegen/", 12 | maintainer = "Leaf Corcoran ", 13 | license = "MIT" 14 | } 15 | 16 | dependencies = { 17 | "lua >= 5.1", 18 | "cosmo", -- doesn't work with lua 5.2+ 19 | "moonscript", 20 | "luasocket", 21 | "lua-discount", 22 | "luafilesystem >= 1.5", 23 | "lua-cjson", 24 | "date", 25 | "ansicolors", 26 | "web_sanitize", 27 | "syntaxhighlight", 28 | } 29 | 30 | build = { 31 | type = "builtin", 32 | modules = { 33 | ["sitegen"] = "sitegen/init.lua", 34 | ["sitegen.cache"] = "sitegen/cache.lua", 35 | ["sitegen.cmd.actions"] = "sitegen/cmd/actions.lua", 36 | ["sitegen.cmd.util"] = "sitegen/cmd/util.lua", 37 | ["sitegen.common"] = "sitegen/common.lua", 38 | ["sitegen.cosmo"] = "sitegen/cosmo.lua", 39 | ["sitegen.default.templates"] = "sitegen/default/templates.lua", 40 | ["sitegen.dispatch"] = "sitegen/dispatch.lua", 41 | ["sitegen.formatters.default"] = "sitegen/formatters/default.lua", 42 | ["sitegen.header"] = "sitegen/header.lua", 43 | ["sitegen.html"] = "sitegen/html.lua", 44 | ["sitegen.output"] = "sitegen/output.lua", 45 | ["sitegen.page"] = "sitegen/page.lua", 46 | ["sitegen.path"] = "sitegen/path.lua", 47 | ["sitegen.plugin"] = "sitegen/plugin.lua", 48 | ["sitegen.plugins.analytics"] = "sitegen/plugins/analytics.lua", 49 | ["sitegen.plugins.blog"] = "sitegen/plugins/blog.lua", 50 | ["sitegen.plugins.coffee_script"] = "sitegen/plugins/coffee_script.lua", 51 | ["sitegen.plugins.deploy"] = "sitegen/plugins/deploy.lua", 52 | ["sitegen.plugins.dump"] = "sitegen/plugins/dump.lua", 53 | ["sitegen.plugins.feed"] = "sitegen/plugins/feed.lua", 54 | ["sitegen.plugins.indexer"] = "sitegen/plugins/indexer.lua", 55 | ["sitegen.plugins.pygments"] = "sitegen/plugins/pygments.lua", 56 | ["sitegen.plugins.syntaxhighlight"] = "sitegen/plugins/syntaxhighlight.lua", 57 | ["sitegen.query"] = "sitegen/query.lua", 58 | ["sitegen.renderer"] = "sitegen/renderer.lua", 59 | ["sitegen.renderers.html"] = "sitegen/renderers/html.lua", 60 | ["sitegen.renderers.lapis"] = "sitegen/renderers/lapis.lua", 61 | ["sitegen.renderers.markdown"] = "sitegen/renderers/markdown.lua", 62 | ["sitegen.renderers.moon"] = "sitegen/renderers/moon.lua", 63 | ["sitegen.site"] = "sitegen/site.lua", 64 | ["sitegen.site_file"] = "sitegen/site_file.lua", 65 | ["sitegen.site_scope"] = "sitegen/site_scope.lua", 66 | ["sitegen.templates"] = "sitegen/templates.lua", 67 | ["sitegen.tools"] = "sitegen/tools.lua", 68 | ["sitegen.watch"] = "sitegen/watch.lua", 69 | }, 70 | install = { 71 | bin = { "bin/sitegen" }, 72 | }, 73 | } 74 | 75 | -------------------------------------------------------------------------------- /sitegen-dev-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "sitegen" 2 | version = "dev-1" 3 | 4 | source = { 5 | url = "git://github.com/leafo/sitegen.git" 6 | } 7 | 8 | description = { 9 | summary = "A tool for assembling static webpages with markdown", 10 | homepage = "http://leafo.net/sitegen/", 11 | maintainer = "Leaf Corcoran ", 12 | license = "MIT" 13 | } 14 | 15 | dependencies = { 16 | "lua >= 5.1", 17 | "cosmo", -- doesn't work with lua 5.2+ 18 | "moonscript", 19 | "luasocket", 20 | "lua-discount", 21 | "luafilesystem >= 1.5", 22 | "lua-cjson", 23 | "date", 24 | "ansicolors", 25 | "web_sanitize", 26 | "syntaxhighlight", 27 | "argparse" 28 | } 29 | 30 | build = { 31 | type = "builtin", 32 | modules = { 33 | ["sitegen"] = "sitegen/init.lua", 34 | ["sitegen.cache"] = "sitegen/cache.lua", 35 | ["sitegen.cmd.actions"] = "sitegen/cmd/actions.lua", 36 | ["sitegen.cmd.argparser"] = "sitegen/cmd/argparser.lua", 37 | ["sitegen.cmd.util"] = "sitegen/cmd/util.lua", 38 | ["sitegen.common"] = "sitegen/common.lua", 39 | ["sitegen.cosmo"] = "sitegen/cosmo.lua", 40 | ["sitegen.default.templates"] = "sitegen/default/templates.lua", 41 | ["sitegen.dispatch"] = "sitegen/dispatch.lua", 42 | ["sitegen.formatters.default"] = "sitegen/formatters/default.lua", 43 | ["sitegen.header"] = "sitegen/header.lua", 44 | ["sitegen.html"] = "sitegen/html.lua", 45 | ["sitegen.output"] = "sitegen/output.lua", 46 | ["sitegen.page"] = "sitegen/page.lua", 47 | ["sitegen.path"] = "sitegen/path.lua", 48 | ["sitegen.plugin"] = "sitegen/plugin.lua", 49 | ["sitegen.plugins.analytics"] = "sitegen/plugins/analytics.lua", 50 | ["sitegen.plugins.blog"] = "sitegen/plugins/blog.lua", 51 | ["sitegen.plugins.coffee_script"] = "sitegen/plugins/coffee_script.lua", 52 | ["sitegen.plugins.deploy"] = "sitegen/plugins/deploy.lua", 53 | ["sitegen.plugins.dump"] = "sitegen/plugins/dump.lua", 54 | ["sitegen.plugins.feed"] = "sitegen/plugins/feed.lua", 55 | ["sitegen.plugins.indexer"] = "sitegen/plugins/indexer.lua", 56 | ["sitegen.plugins.pygments"] = "sitegen/plugins/pygments.lua", 57 | ["sitegen.plugins.syntaxhighlight"] = "sitegen/plugins/syntaxhighlight.lua", 58 | ["sitegen.plugins.tupfile"] = "sitegen/plugins/tupfile.lua", 59 | ["sitegen.query"] = "sitegen/query.lua", 60 | ["sitegen.renderer"] = "sitegen/renderer.lua", 61 | ["sitegen.renderers.html"] = "sitegen/renderers/html.lua", 62 | ["sitegen.renderers.lapis"] = "sitegen/renderers/lapis.lua", 63 | ["sitegen.renderers.markdown"] = "sitegen/renderers/markdown.lua", 64 | ["sitegen.renderers.moon"] = "sitegen/renderers/moon.lua", 65 | ["sitegen.site"] = "sitegen/site.lua", 66 | ["sitegen.site_file"] = "sitegen/site_file.lua", 67 | ["sitegen.site_scope"] = "sitegen/site_scope.lua", 68 | ["sitegen.templates"] = "sitegen/templates.lua", 69 | ["sitegen.tools"] = "sitegen/tools.lua", 70 | ["sitegen.watch"] = "sitegen/watch.lua", 71 | }, 72 | install = { 73 | bin = { "bin/sitegen" }, 74 | }, 75 | } 76 | 77 | -------------------------------------------------------------------------------- /sitegen.moon: -------------------------------------------------------------------------------- 1 | require "sitegen.init" 2 | -------------------------------------------------------------------------------- /sitegen/cache.lua: -------------------------------------------------------------------------------- 1 | local concat 2 | concat = table.concat 3 | local json = require("cjson") 4 | local serialize 5 | serialize = function(obj) 6 | return json.encode(obj) 7 | end 8 | local unserialize 9 | unserialize = function(text) 10 | return json.decode(text) 11 | end 12 | local CacheTable 13 | do 14 | local _class_0 15 | local _base_0 = { 16 | __tostring = function(self) 17 | return "" 18 | end, 19 | get = function(self, name, default) 20 | if default == nil then 21 | default = (function() 22 | return CacheTable() 23 | end) 24 | end 25 | local val = self[name] 26 | if type(val) == "table" and getmetatable(val) ~= self.__class.__base then 27 | self.__class:inject(val) 28 | end 29 | if val == nil then 30 | val = default() 31 | self[name] = val 32 | return val 33 | else 34 | return val 35 | end 36 | end, 37 | set = function(self, name, value) 38 | self[name] = value 39 | end 40 | } 41 | _base_0.__index = _base_0 42 | _class_0 = setmetatable({ 43 | __init = function() end, 44 | __base = _base_0, 45 | __name = "CacheTable" 46 | }, { 47 | __index = _base_0, 48 | __call = function(cls, ...) 49 | local _self_0 = setmetatable({}, _base_0) 50 | cls.__init(_self_0, ...) 51 | return _self_0 52 | end 53 | }) 54 | _base_0.__class = _class_0 55 | local self = _class_0 56 | self.inject = function(self, tbl) 57 | return setmetatable(tbl, self.__base) 58 | end 59 | CacheTable = _class_0 60 | end 61 | local Cache 62 | do 63 | local _class_0 64 | local _base_0 = { 65 | load_cache = function(self) 66 | if self.loaded then 67 | return 68 | end 69 | self.loaded = true 70 | if self.site.io.exists(self.fname) then 71 | local content = self.site.io.read_file(self.fname) 72 | local cache, err = unserialize(content) 73 | if not (cache) then 74 | error("could not load cache `" .. tostring(self.fname) .. "`, delete and try again: " .. tostring(err)) 75 | end 76 | self.cache = cache 77 | else 78 | self.cache = { } 79 | end 80 | return CacheTable:inject(self.cache) 81 | end, 82 | write = function(self) 83 | local _list_0 = self.finalize 84 | for _index_0 = 1, #_list_0 do 85 | local fn = _list_0[_index_0] 86 | fn(self) 87 | end 88 | local text = serialize(self.cache) 89 | if not text then 90 | error("failed to serialize cache") 91 | end 92 | return self.site.io.write_file(self.fname, text) 93 | end, 94 | set = function(self, ...) 95 | self:load_cache() 96 | return self.cache:set(...) 97 | end, 98 | get = function(self, ...) 99 | self:load_cache() 100 | return self.cache:get(...) 101 | end 102 | } 103 | _base_0.__index = _base_0 104 | _class_0 = setmetatable({ 105 | __init = function(self, site, fname) 106 | if fname == nil then 107 | fname = ".sitegen_cache" 108 | end 109 | self.site, self.fname = site, fname 110 | self.finalize = { } 111 | end, 112 | __base = _base_0, 113 | __name = "Cache" 114 | }, { 115 | __index = _base_0, 116 | __call = function(cls, ...) 117 | local _self_0 = setmetatable({}, _base_0) 118 | cls.__init(_self_0, ...) 119 | return _self_0 120 | end 121 | }) 122 | _base_0.__class = _class_0 123 | Cache = _class_0 124 | end 125 | return { 126 | Cache = Cache, 127 | CacheTable = CacheTable 128 | } 129 | -------------------------------------------------------------------------------- /sitegen/cache.moon: -------------------------------------------------------------------------------- 1 | import concat from table 2 | 3 | json = require "cjson" 4 | 5 | serialize = (obj) -> json.encode obj 6 | unserialize = (text) -> json.decode text 7 | 8 | class CacheTable 9 | __tostring: => "" 10 | 11 | @inject = (tbl) => 12 | setmetatable tbl, self.__base 13 | 14 | get: (name, default=(-> CacheTable!)) => 15 | val = self[name] 16 | 17 | if type(val) == "table" and getmetatable(val) != @@__base 18 | @@inject val 19 | 20 | if val == nil 21 | val = default! 22 | self[name] = val 23 | val 24 | else 25 | val 26 | 27 | set: (name, value) => 28 | self[name] = value 29 | 30 | class Cache 31 | new: (@site, @fname=".sitegen_cache") => 32 | @finalize = {} 33 | 34 | load_cache: => 35 | return if @loaded 36 | @loaded = true 37 | 38 | @cache = if @site.io.exists @fname 39 | content = @site.io.read_file @fname 40 | 41 | cache, err = unserialize content 42 | 43 | unless cache 44 | error "could not load cache `#{@fname}`, delete and try again: #{err}" 45 | cache 46 | else 47 | {} 48 | 49 | CacheTable\inject @cache 50 | 51 | write: => 52 | fn self for fn in *@finalize 53 | text = serialize @cache 54 | error "failed to serialize cache" if not text 55 | @site.io.write_file @fname, text 56 | 57 | set: (...) => 58 | @load_cache! 59 | @cache\set ... 60 | 61 | get: (...) => 62 | @load_cache! 63 | @cache\get ... 64 | 65 | { 66 | :Cache 67 | :CacheTable 68 | } 69 | 70 | -------------------------------------------------------------------------------- /sitegen/cmd/actions.lua: -------------------------------------------------------------------------------- 1 | local moonscript = require("moonscript") 2 | local cosmo = require("sitegen.cosmo") 3 | local throw_error, slugify 4 | do 5 | local _obj_0 = require("sitegen.common") 6 | throw_error, slugify = _obj_0.throw_error, _obj_0.slugify 7 | end 8 | local log, get_site, columnize, Path 9 | do 10 | local _obj_0 = require("sitegen.cmd.util") 11 | log, get_site, get_site, columnize, Path = _obj_0.log, _obj_0.get_site, _obj_0.get_site, _obj_0.columnize, _obj_0.Path 12 | end 13 | local extend, dump 14 | do 15 | local _obj_0 = require("moon") 16 | extend, dump = _obj_0.extend, _obj_0.dump 17 | end 18 | local default = { 19 | sitefile = "site.moon", 20 | files = { 21 | page = [==[{ 22 | date: "$eval{"require('date')()"}" 23 | $if{"title"}[[title: "$title"]] 24 | } 25 | 26 | Hello world! 27 | 28 | ]==], 29 | sitefile = [==[sitegen = require "sitegen" 30 | sitegen.create => 31 | @title = $title 32 | ]==] 33 | } 34 | } 35 | local scope 36 | scope = function(t) 37 | if t == nil then 38 | t = { } 39 | end 40 | return extend(t, { 41 | eval = function(arg) 42 | local code = "return -> " .. arg[1] 43 | return moonscript.loadstring(code)()() 44 | end, 45 | ["if"] = function(arg) 46 | local var_name = arg[1] 47 | if t[var_name] then 48 | cosmo.yield(t) 49 | end 50 | return nil 51 | end 52 | }) 53 | end 54 | local actions = { 55 | dump = function() 56 | return print(dump(get_site())) 57 | end, 58 | new = function(args) 59 | local title 60 | title = args.title 61 | if Path.exists(default.sitefile) then 62 | throw_error("sitefile already exists: " .. default.sitefile) 63 | end 64 | title = ("%q"):format(title or "Hello World") 65 | Path.mkdir("www") 66 | Path.mkdir("templates") 67 | local site_moon = cosmo.f(default.files.sitefile)(scope({ 68 | title = title 69 | })) 70 | return Path.write_file(default.sitefile, site_moon) 71 | end, 72 | page = function(args) 73 | local title, path 74 | title, path = args.title, args.path 75 | get_site() 76 | if not title then 77 | title = path 78 | local path_part, title_part = title:match("^(.-)([^/]+)$") 79 | if path_part then 80 | title = title_part 81 | path = path_part 82 | else 83 | path = '.' 84 | end 85 | end 86 | if Path.normalize(path) ~= "" then 87 | Path.mkdir(path) 88 | end 89 | local names 90 | names = function(fname, ext) 91 | if ext == nil then 92 | ext = ".md" 93 | end 94 | local i = 0 95 | return coroutine.wrap(function() 96 | while true do 97 | coroutine.yield((function() 98 | if i == 0 then 99 | return fname .. ext 100 | else 101 | return table.concat({ 102 | fname, 103 | "_", 104 | i, 105 | ext 106 | }) 107 | end 108 | end)()) 109 | i = i + 1 110 | end 111 | end) 112 | end 113 | local full_path = nil 114 | for name in names(slugify(title)) do 115 | full_path = Path.join(path, name) 116 | if not Path.exists(full_path) then 117 | break 118 | end 119 | end 120 | return Path.write_file(full_path, cosmo.f(default.files.page)(scope({ 121 | title = title 122 | }))) 123 | end, 124 | build = function(args) 125 | local files = args.input_files 126 | local site = get_site() 127 | local filter 128 | if files and next(files) then 129 | filter = { } 130 | for i, fname in ipairs(files) do 131 | filter[site.sitefile:relativeize(fname)] = true 132 | end 133 | end 134 | return site:write(filter) 135 | end, 136 | watch = function(args) 137 | local site = get_site() 138 | local w = require("sitegen.watch") 139 | do 140 | local _with_0 = w.Watcher(site) 141 | _with_0:loop() 142 | return _with_0 143 | end 144 | end 145 | } 146 | local find_action 147 | find_action = function(name) 148 | if actions[name] then 149 | return actions[name] 150 | end 151 | for action_obj, call in get_site():plugin_actions() do 152 | local plugin_action_name = action_obj.action or action_obj.method 153 | if plugin_action_name == name then 154 | return call 155 | end 156 | end 157 | end 158 | return { 159 | actions = actions, 160 | find_action = find_action 161 | } 162 | -------------------------------------------------------------------------------- /sitegen/cmd/actions.moon: -------------------------------------------------------------------------------- 1 | moonscript = require "moonscript" 2 | cosmo = require "sitegen.cosmo" 3 | 4 | import throw_error, slugify from require "sitegen.common" 5 | import log, get_site, get_site, columnize, Path from require "sitegen.cmd.util" 6 | 7 | import extend, dump from require "moon" 8 | 9 | default = { 10 | sitefile: "site.moon" 11 | files: 12 | page: [==[{ 13 | date: "$eval{"require('date')()"}" 14 | $if{"title"}[[title: "$title"]] 15 | } 16 | 17 | Hello world! 18 | 19 | ]==] 20 | sitefile: [==[ 21 | sitegen = require "sitegen" 22 | sitegen.create => 23 | @title = $title 24 | ]==] 25 | } 26 | 27 | 28 | scope = (t={}) -> 29 | extend t, { 30 | eval: (arg) -> 31 | code = "return -> " .. arg[1] 32 | moonscript.loadstring(code)!! 33 | if: (arg) -> 34 | var_name = arg[1] 35 | cosmo.yield t if t[var_name] 36 | nil 37 | } 38 | 39 | actions = { 40 | dump: -> print dump get_site! 41 | 42 | new: (args) -> 43 | {:title} = args 44 | 45 | if Path.exists default.sitefile 46 | throw_error "sitefile already exists: " .. default.sitefile 47 | 48 | title = ("%q")\format title or "Hello World" 49 | 50 | Path.mkdir"www" 51 | Path.mkdir"templates" 52 | 53 | site_moon = cosmo.f(default.files.sitefile) scope{:title} 54 | Path.write_file default.sitefile, site_moon 55 | 56 | page: (args) -> 57 | {:title, :path} = args 58 | 59 | get_site! 60 | 61 | if not title 62 | title = path 63 | path_part, title_part = title\match"^(.-)([^/]+)$" 64 | if path_part 65 | title = title_part 66 | path = path_part 67 | else 68 | path = '.' 69 | 70 | Path.mkdir path if Path.normalize(path) != "" 71 | 72 | -- iterater for all potential file names 73 | names = (fname, ext=".md") -> 74 | i = 0 75 | coroutine.wrap -> 76 | while true 77 | coroutine.yield if i == 0 78 | fname .. ext 79 | else 80 | table.concat {fname, "_", i, ext } 81 | i += 1 82 | 83 | full_path = nil 84 | for name in names slugify title 85 | full_path = Path.join path, name 86 | if not Path.exists full_path 87 | break 88 | 89 | Path.write_file full_path, cosmo.f(default.files.page) scope{:title} 90 | 91 | build: (args) -> 92 | files = args.input_files 93 | site = get_site! 94 | 95 | local filter 96 | if files and next files 97 | filter = {} 98 | for i, fname in ipairs files 99 | filter[site.sitefile\relativeize fname] = true 100 | 101 | site\write filter 102 | 103 | watch: (args) -> 104 | site = get_site! 105 | w = require "sitegen.watch" 106 | 107 | with w.Watcher site 108 | \loop! 109 | 110 | } 111 | 112 | -- return function to be called for command line action 113 | -- function should take one argument, the args object returned by argparse 114 | find_action = (name) -> 115 | return actions[name] if actions[name] 116 | 117 | for action_obj, call in get_site!\plugin_actions! 118 | plugin_action_name = action_obj.action or action_obj.method 119 | if plugin_action_name == name 120 | return call 121 | 122 | {:actions, :find_action} 123 | 124 | 125 | -------------------------------------------------------------------------------- /sitegen/cmd/argparser.lua: -------------------------------------------------------------------------------- 1 | local SiteFile 2 | SiteFile = require("sitegen.site_file").SiteFile 3 | local argparse = require("argparse") 4 | local parser = argparse("sitegen", "MoonScript powered static site generator") 5 | parser:command_target("action") 6 | parser:require_command(false) 7 | do 8 | local _with_0 = parser:command("new") 9 | _with_0:summary("Create a new site template in the current directory") 10 | _with_0:argument("title", "Title of site"):args("?") 11 | end 12 | do 13 | local _with_0 = parser:command("build") 14 | _with_0:summary("Build (or rebuild) all pages, or listed inputs") 15 | _with_0:argument("input_files"):args("*") 16 | end 17 | do 18 | local _with_0 = parser:command("page") 19 | _with_0:summary("Create a new markdown page at specified path") 20 | _with_0:argument("path") 21 | _with_0:argument("title", "Title of page"):args("?") 22 | end 23 | do 24 | local _with_0 = parser:command("watch") 25 | _with_0:summary("Compile pages automatically when inputs change (needs inotify)") 26 | end 27 | do 28 | local _with_0 = parser:command("dump") 29 | _with_0:summary("Debug dump of sitefile") 30 | _with_0:hidden(true) 31 | end 32 | local site 33 | pcall(function() 34 | site = SiteFile({ 35 | logger_opts = { 36 | silent = true 37 | } 38 | }):get_site() 39 | end) 40 | if site then 41 | for action_obj in site:plugin_actions() do 42 | local _continue_0 = false 43 | repeat 44 | local action = action_obj.action or action_obj.method 45 | if not (action) then 46 | _continue_0 = true 47 | break 48 | end 49 | local command = parser:command(action) 50 | if type(action_obj.argparser) == "function" then 51 | action_obj.argparser(command) 52 | end 53 | _continue_0 = true 54 | until true 55 | if not _continue_0 then 56 | break 57 | end 58 | end 59 | end 60 | parser:add_help_command() 61 | return parser 62 | -------------------------------------------------------------------------------- /sitegen/cmd/argparser.moon: -------------------------------------------------------------------------------- 1 | 2 | -- needed to load plugin commands 3 | import SiteFile from require "sitegen.site_file" 4 | 5 | argparse = require "argparse" 6 | 7 | parser = argparse "sitegen", 8 | "MoonScript powered static site generator" 9 | 10 | parser\command_target "action" 11 | parser\require_command false 12 | 13 | with parser\command "new" 14 | \summary "Create a new site template in the current directory" 15 | \argument("title", "Title of site")\args "?" 16 | 17 | with parser\command "build" 18 | \summary "Build (or rebuild) all pages, or listed inputs" 19 | \argument("input_files")\args "*" 20 | 21 | with parser\command "page" 22 | \summary "Create a new markdown page at specified path" 23 | 24 | \argument "path" 25 | \argument("title", "Title of page")\args "?" 26 | 27 | with parser\command "watch" 28 | \summary "Compile pages automatically when inputs change (needs inotify)" 29 | 30 | with parser\command "dump" 31 | \summary "Debug dump of sitefile" 32 | \hidden true 33 | 34 | -- attempt to insert plugin actions 35 | local site 36 | pcall -> site = SiteFile(logger_opts: { silent: true })\get_site! 37 | 38 | -- install custom commands from the list of plugins in the current site file 39 | if site 40 | for action_obj in site\plugin_actions! 41 | action = action_obj.action or action_obj.method 42 | continue unless action 43 | 44 | command = parser\command action 45 | if type(action_obj.argparser) == "function" 46 | action_obj.argparser command 47 | 48 | parser\add_help_command! -- should be last 49 | parser 50 | -------------------------------------------------------------------------------- /sitegen/cmd/util.lua: -------------------------------------------------------------------------------- 1 | local split 2 | split = require("sitegen.common").split 3 | local SiteFile 4 | SiteFile = require("sitegen.site_file").SiteFile 5 | local Path = require("sitegen.path") 6 | local log 7 | log = function(...) 8 | return print("->", ...) 9 | end 10 | local get_site 11 | get_site = function() 12 | return SiteFile():get_site() 13 | end 14 | Path = Path:annotate({ 15 | mkdir = "made directory", 16 | write_file = "wrote" 17 | }) 18 | local wrap_text 19 | wrap_text = function(text, indent, max_width) 20 | if indent == nil then 21 | indent = 0 22 | end 23 | if max_width == nil then 24 | max_width = 80 25 | end 26 | local width = max_width - indent 27 | local words = split(text, " ") 28 | local pos = 1 29 | local lines = { } 30 | while pos <= #words do 31 | local line_len = 0 32 | local line = { } 33 | while true do 34 | local word = words[pos] 35 | if word == nil then 36 | break 37 | end 38 | if #word > width then 39 | error("can't wrap text, words too long") 40 | end 41 | if line_len + #word > width then 42 | break 43 | end 44 | pos = pos + 1 45 | table.insert(line, word) 46 | line_len = line_len + #word + 1 47 | end 48 | table.insert(lines, table.concat(line, " ")) 49 | end 50 | return table.concat(lines, "\n" .. (" "):rep(indent)) 51 | end 52 | local columnize 53 | columnize = function(rows, indent, padding) 54 | if indent == nil then 55 | indent = 2 56 | end 57 | if padding == nil then 58 | padding = 4 59 | end 60 | local max = 0 61 | for _index_0 = 1, #rows do 62 | local row = rows[_index_0] 63 | max = math.max(max, #row[1]) 64 | end 65 | local left_width = indent + padding + max 66 | local formatted 67 | do 68 | local _accum_0 = { } 69 | local _len_0 = 1 70 | for _index_0 = 1, #rows do 71 | local row = rows[_index_0] 72 | local padd = (max - #row[1]) + padding 73 | local _value_0 = table.concat({ 74 | (" "):rep(indent), 75 | row[1], 76 | (" "):rep(padd), 77 | wrap_text(row[2], left_width) 78 | }) 79 | _accum_0[_len_0] = _value_0 80 | _len_0 = _len_0 + 1 81 | end 82 | formatted = _accum_0 83 | end 84 | return table.concat(formatted, "\n") 85 | end 86 | return { 87 | log = log, 88 | Path = Path, 89 | get_site = get_site, 90 | columnize = columnize 91 | } 92 | -------------------------------------------------------------------------------- /sitegen/cmd/util.moon: -------------------------------------------------------------------------------- 1 | import split from require "sitegen.common" 2 | import SiteFile from require "sitegen.site_file" 3 | Path = require "sitegen.path" 4 | 5 | log = (...) -> 6 | print "->", ... 7 | 8 | get_site = -> SiteFile!\get_site! 9 | 10 | Path = Path\annotate { 11 | mkdir: "made directory" 12 | write_file: "wrote" 13 | } 14 | 15 | -- wrap test based on tokens 16 | wrap_text = (text, indent=0, max_width=80) -> 17 | width = max_width - indent 18 | words = split text, " " 19 | pos = 1 20 | lines = {} 21 | while pos <= #words 22 | line_len = 0 23 | line = {} 24 | while true 25 | word = words[pos] 26 | break if word == nil 27 | error "can't wrap text, words too long" if #word > width 28 | break if line_len + #word > width 29 | 30 | pos += 1 31 | table.insert line, word 32 | line_len += #word + 1 -- +1 for the space 33 | 34 | table.insert lines, table.concat line, " " 35 | 36 | table.concat lines, "\n" .. (" ")\rep indent 37 | 38 | columnize = (rows, indent=2, padding=4) -> 39 | max = 0 40 | max = math.max max, #row[1] for row in *rows 41 | 42 | left_width = indent + padding + max 43 | 44 | formatted = for row in *rows 45 | padd = (max - #row[1]) + padding 46 | table.concat { 47 | (" ")\rep indent 48 | row[1] 49 | (" ")\rep padd 50 | wrap_text row[2], left_width 51 | } 52 | 53 | table.concat formatted, "\n" 54 | 55 | { :log, :Path, :get_site, :columnize } 56 | -------------------------------------------------------------------------------- /sitegen/cosmo.lua: -------------------------------------------------------------------------------- 1 | _G.unpack = _G.unpack or require("sitegen.common").unpack 2 | _G.loadstring = _G.loadstring or function(str, chunkname) 3 | return load(coroutine.wrap(function() 4 | return coroutine.yield(str) 5 | end), chunkname) 6 | end 7 | _G.getfenv = _G.getfenv or require("moonscript.util").getfenv 8 | _G.setfenv = _G.setfenv or require("moonscript.util").setfenv 9 | return require("cosmo") 10 | -------------------------------------------------------------------------------- /sitegen/cosmo.moon: -------------------------------------------------------------------------------- 1 | _G.unpack or= require("sitegen.common").unpack 2 | 3 | _G.loadstring or= (str, chunkname) -> 4 | load coroutine.wrap(-> coroutine.yield str), chunkname 5 | 6 | _G.getfenv or= require("moonscript.util").getfenv 7 | _G.setfenv or= require("moonscript.util").setfenv 8 | 9 | require "cosmo" 10 | 11 | -------------------------------------------------------------------------------- /sitegen/default/templates.lua: -------------------------------------------------------------------------------- 1 | local index = [==[ 2 | 3 | 4 | 5 | $title 6 | $each{stylesheets, "url"}[[ 7 | ]] 8 | $each{javascripts, "url"}[[ 9 | ]] 10 | 11 | 12 | $body 13 | 14 | 15 | ]==] 16 | return { 17 | index = index 18 | } 19 | -------------------------------------------------------------------------------- /sitegen/default/templates.moon: -------------------------------------------------------------------------------- 1 | 2 | index = [==[ 3 | 4 | 5 | 6 | $title 7 | $each{stylesheets, "url"}[[ 8 | ]] 9 | $each{javascripts, "url"}[[ 10 | ]] 11 | 12 | 13 | $body 14 | 15 | 16 | ]==] 17 | 18 | { 19 | :index 20 | } 21 | -------------------------------------------------------------------------------- /sitegen/dispatch.lua: -------------------------------------------------------------------------------- 1 | local Dispatch 2 | do 3 | local _class_0 4 | local _base_0 = { 5 | _parse_name = function(self, name) 6 | assert(type(name) == "string", "event name must be string") 7 | local parts 8 | do 9 | local _accum_0 = { } 10 | local _len_0 = 1 11 | for p in name:gmatch("[^.]+") do 12 | _accum_0[_len_0] = p 13 | _len_0 = _len_0 + 1 14 | end 15 | parts = _accum_0 16 | end 17 | assert(next(parts), "invalid name") 18 | return parts 19 | end, 20 | on = function(self, name, callback) 21 | local parts = self:_parse_name(name) 22 | local callbacks = self.callbacks 23 | for _index_0 = 1, #parts do 24 | local p = parts[_index_0] 25 | local _update_0 = p 26 | callbacks[_update_0] = callbacks[_update_0] or { } 27 | callbacks = callbacks[p] 28 | end 29 | return table.insert(callbacks, callback) 30 | end, 31 | off = function(self, name) 32 | local parts = self:_parse_name(name) 33 | local last = parts[#parts] 34 | table.remove(parts) 35 | local callbacks = self.callbacks 36 | for _index_0 = 1, #parts do 37 | local p = parts[_index_0] 38 | callbacks = callbacks[p] 39 | end 40 | callbacks[last] = nil 41 | end, 42 | callbacks_for = function(self, name) 43 | local matched = { } 44 | local callbacks = self.callbacks 45 | local _list_0 = self:_parse_name(name) 46 | for _index_0 = 1, #_list_0 do 47 | local p = _list_0[_index_0] 48 | callbacks = callbacks[p] 49 | if not (callbacks) then 50 | break 51 | end 52 | for _index_1 = 1, #callbacks do 53 | local c = callbacks[_index_1] 54 | table.insert(matched, c) 55 | end 56 | end 57 | return matched 58 | end, 59 | pipe_callbacks = function(self, callbacks, i, event, ...) 60 | local cb = callbacks[i] 61 | if cb and not event.cancel then 62 | return self:pipe_callbacks(callbacks, i + 1, event, cb(event, ...)) 63 | else 64 | return ... 65 | end 66 | end, 67 | pipe = function(self, name, ...) 68 | local callbacks = self:callbacks_for(name) 69 | local event = { 70 | name = name, 71 | cancel = false, 72 | dispatch = self 73 | } 74 | return self:pipe_callbacks(callbacks, 1, event, ...) 75 | end, 76 | trigger = function(self, name, ...) 77 | local count = 0 78 | local e = { 79 | name = name, 80 | cancel = false, 81 | dispatch = self 82 | } 83 | local _list_0 = self:callbacks_for(name) 84 | for _index_0 = 1, #_list_0 do 85 | local c = _list_0[_index_0] 86 | c(e, ...) 87 | count = count + 1 88 | if e.cancel then 89 | break 90 | end 91 | end 92 | return count > 0, e 93 | end 94 | } 95 | _base_0.__index = _base_0 96 | _class_0 = setmetatable({ 97 | __init = function(self) 98 | self.callbacks = { } 99 | end, 100 | __base = _base_0, 101 | __name = "Dispatch" 102 | }, { 103 | __index = _base_0, 104 | __call = function(cls, ...) 105 | local _self_0 = setmetatable({}, _base_0) 106 | cls.__init(_self_0, ...) 107 | return _self_0 108 | end 109 | }) 110 | _base_0.__class = _class_0 111 | Dispatch = _class_0 112 | end 113 | return { 114 | Dispatch = Dispatch 115 | } 116 | -------------------------------------------------------------------------------- /sitegen/dispatch.moon: -------------------------------------------------------------------------------- 1 | -- originally from saltw-bot 2 | class Dispatch 3 | new: => 4 | @callbacks = {} 5 | 6 | _parse_name: (name) => 7 | assert type(name) == "string", "event name must be string" 8 | parts = [p for p in name\gmatch "[^.]+"] 9 | assert next(parts), "invalid name" 10 | parts 11 | 12 | on: (name, callback) => 13 | parts = @_parse_name name 14 | 15 | callbacks = @callbacks 16 | for p in *parts 17 | callbacks[p] or= {} 18 | callbacks = callbacks[p] 19 | 20 | table.insert callbacks, callback 21 | 22 | off: (name) => 23 | parts = @_parse_name name 24 | last = parts[#parts] 25 | table.remove parts 26 | 27 | callbacks = @callbacks 28 | for p in *parts 29 | callbacks = callbacks[p] 30 | 31 | callbacks[last] = nil 32 | 33 | callbacks_for: (name) => 34 | -- find all the matching callbacks 35 | matched = {} 36 | 37 | callbacks = @callbacks 38 | for p in *@_parse_name name 39 | callbacks = callbacks[p] 40 | break unless callbacks 41 | for c in *callbacks 42 | table.insert matched, c 43 | 44 | matched 45 | 46 | pipe_callbacks: (callbacks, i, event, ...) => 47 | cb = callbacks[i] 48 | if cb and not event.cancel 49 | @pipe_callbacks callbacks, i + 1, event, cb event, ... 50 | else 51 | ... 52 | 53 | pipe: (name, ...) => 54 | callbacks = @callbacks_for name 55 | 56 | event = { 57 | :name 58 | cancel: false 59 | dispatch: @ 60 | } 61 | 62 | @pipe_callbacks callbacks, 1, event, ... 63 | 64 | trigger: (name, ...) => 65 | count = 0 66 | e = { 67 | :name 68 | cancel: false 69 | dispatch: @ 70 | } 71 | 72 | for c in *@callbacks_for name 73 | c e, ... 74 | count += 1 75 | break if e.cancel 76 | 77 | count > 0, e 78 | 79 | { :Dispatch } 80 | -------------------------------------------------------------------------------- /sitegen/formatters/default.lua: -------------------------------------------------------------------------------- 1 | local html = require("sitegen.html") 2 | local extend, bind_methods 3 | do 4 | local _obj_0 = require("moon") 5 | extend, bind_methods = _obj_0.extend, _obj_0.bind_methods 6 | end 7 | local scope = { 8 | write = function(self, ...) 9 | local _list_0 = { 10 | ... 11 | } 12 | for _index_0 = 1, #_list_0 do 13 | local thing = _list_0[_index_0] 14 | table.insert(self.buffer, tostring(thing)) 15 | end 16 | end, 17 | html = function(self, ...) 18 | return self:write(html.build(...)) 19 | end, 20 | render = function(self) 21 | return table.concat(self.buffer, "\n") 22 | end 23 | } 24 | return { 25 | make_context = function(page) 26 | return bind_methods(extend({ 27 | buffer = { } 28 | }, scope)) 29 | end 30 | } 31 | -------------------------------------------------------------------------------- /sitegen/formatters/default.moon: -------------------------------------------------------------------------------- 1 | -- provides a basic scope that lets your write to a buffer 2 | -- and return it 3 | 4 | html = require "sitegen.html" 5 | import extend, bind_methods from require "moon" 6 | 7 | scope = { 8 | write: (...) => 9 | for thing in *{...} 10 | table.insert @buffer, tostring(thing) 11 | 12 | html: (...) => 13 | @write html.build ... 14 | 15 | render: => 16 | table.concat @buffer, "\n" 17 | } 18 | 19 | { 20 | make_context: (page) -> 21 | bind_methods extend { buffer: {} }, scope 22 | } 23 | 24 | -------------------------------------------------------------------------------- /sitegen/header.lua: -------------------------------------------------------------------------------- 1 | local trim_leading_white 2 | trim_leading_white = require("sitegen.common").trim_leading_white 3 | local parse_moonscript_header 4 | parse_moonscript_header = function(text) 5 | if text:match("^%s*{") then 6 | local build_grammar 7 | build_grammar = require("moonscript.parse").build_grammar 8 | local V, Cp, Ct 9 | do 10 | local _obj_0 = require("lpeg") 11 | V, Cp, Ct = _obj_0.V, _obj_0.Cp, _obj_0.Ct 12 | end 13 | local g = assert(build_grammar(V("TableLit") * Cp())) 14 | local _, pos = assert(g:match(text)) 15 | if type(pos) == "number" then 16 | local loadstring 17 | loadstring = require("moonscript.base").loadstring 18 | local fn = assert(loadstring(text:sub(1, pos - 1))) 19 | return text:sub(pos), fn() 20 | end 21 | end 22 | end 23 | local extract_header 24 | extract_header = function(text) 25 | local remaining, header = parse_moonscript_header(text) 26 | if remaining then 27 | return remaining, header 28 | end 29 | return text, { } 30 | end 31 | return { 32 | extract_header = extract_header 33 | } 34 | -------------------------------------------------------------------------------- /sitegen/header.moon: -------------------------------------------------------------------------------- 1 | import trim_leading_white from require "sitegen.common" 2 | 3 | parse_moonscript_header = (text) -> 4 | if text\match "^%s*{" 5 | import build_grammar from require "moonscript.parse" 6 | import V, Cp, Ct from require "lpeg" 7 | g = assert build_grammar V"TableLit" * Cp! 8 | _, pos = assert g\match(text) 9 | 10 | if type(pos) == "number" 11 | import loadstring from require "moonscript.base" 12 | fn = assert loadstring text\sub 1, pos - 1 13 | return text\sub(pos), fn! 14 | 15 | extract_header = (text) -> 16 | remaining, header = parse_moonscript_header text 17 | if remaining 18 | return remaining, header 19 | 20 | text, {} 21 | 22 | { :extract_header } 23 | -------------------------------------------------------------------------------- /sitegen/html.moon: -------------------------------------------------------------------------------- 1 | 2 | import concat from table 3 | import run_with_scope, defaultbl from require "moon" 4 | 5 | import escape_patt, getfenv from require "sitegen.common" 6 | 7 | local sort_attributes, build 8 | 9 | set_sort_attributes = (v) -> sort_attributes = v 10 | 11 | html_encode_entities = { 12 | ['&']: '&' 13 | ['<']: '<' 14 | ['>']: '>' 15 | ['"']: '"' 16 | ["'"]: ''' 17 | } 18 | 19 | html_decode_entities = {} 20 | for key,value in pairs html_encode_entities 21 | html_decode_entities[value] = key 22 | 23 | html_encode_pattern = "[" .. concat([escape_patt char for char in pairs html_encode_entities]) .. "]" 24 | 25 | encode = (text) -> 26 | (text\gsub html_encode_pattern, html_encode_entities) 27 | 28 | escape = encode 29 | 30 | decode = (text) -> 31 | (text\gsub "(&[^&]-;)", (enc) -> 32 | decoded = html_decode_entities[enc] 33 | decoded if decoded else enc) 34 | 35 | unescape = decode 36 | 37 | strip_tags = (html) -> 38 | html\gsub "<[^>]+>", "" 39 | 40 | is_list = (t) -> 41 | type(t) == "table" and t.type != "tag" 42 | 43 | render_list = (list, delim) -> 44 | escaped = for item in *list 45 | if type(item) == "string" 46 | encode item 47 | elseif is_list item 48 | render_list item, delim 49 | elseif type(item) == "function" 50 | build item 51 | elseif item != nil 52 | tostring item 53 | else 54 | error "unknown item" 55 | 56 | table.concat escaped, delim 57 | 58 | render_tag = (name, inner="", attributes={}) -> 59 | formatted_attributes = {} 60 | for attr_name, attr_value in pairs attributes 61 | if not attr_name\match"^__" 62 | table.insert formatted_attributes, 63 | ('%s="%s"')\format attr_name, encode attr_value 64 | 65 | if sort_attributes 66 | table.sort formatted_attributes 67 | 68 | if is_list inner 69 | inner = render_list inner, "\n" 70 | else 71 | inner = tostring inner 72 | 73 | open = table.concat { 74 | "<", name 75 | 76 | if #formatted_attributes > 0 77 | " " .. table.concat formatted_attributes, " " 78 | else "" 79 | 80 | ">" 81 | } 82 | 83 | close = table.concat { ""} 84 | close = "\n" .. close if attributes.__breakclose 85 | open .. inner .. close 86 | 87 | class Text 88 | new: (@text) => @type = "tag" 89 | __tostring: => @text 90 | 91 | class CData 92 | new: (@text) => @type = "tag" 93 | __tostring: => 94 | "" 95 | 96 | class Tag 97 | new: (@name, @inner, @attributes) => @type = "tag" 98 | __tostring: => render_tag @name, @inner, @attributes 99 | __call: (arg) => 100 | arg = {arg} unless is_list arg 101 | 102 | attributes = {} 103 | inner = {} 104 | 105 | if is_list arg 106 | for k,v in pairs arg 107 | if type(k) == "number" 108 | table.insert inner, v 109 | else 110 | attributes[k] = v 111 | 112 | Tag @name, inner, attributes 113 | 114 | tag = nil 115 | builders = defaultbl { 116 | text: -> (str) -> Text str 117 | cdata: -> (str) -> CData str 118 | tag: -> tag 119 | }, -> Tag 120 | 121 | builders.raw = builders.text 122 | 123 | tag = setmetatable {}, { 124 | __index: (name) => builders[name] name 125 | } 126 | 127 | build = (fn, delim="\n") -> 128 | source_env = getfenv fn 129 | result = run_with_scope fn, setmetatable {}, { 130 | __index: (name) => 131 | return tag if name == "tag" 132 | return source_env[name] if source_env[name] != nil 133 | builders[name] name 134 | } 135 | 136 | result = render_list result, delim if is_list result 137 | tostring result 138 | 139 | { 140 | :encode, :decode, :strip_tags, :build, :builders, :escape, :unescape, :tag, 141 | sort_attributes: set_sort_attributes 142 | } 143 | -------------------------------------------------------------------------------- /sitegen/init.lua: -------------------------------------------------------------------------------- 1 | local Site = require("sitegen.site") 2 | local colors = require("ansicolors") 3 | local create_site 4 | create_site = function(init_fn, site) 5 | if site == nil then 6 | site = Site() 7 | end 8 | io.stderr:write(colors("%{bright}%{red}WARNING: %{reset}sitegen.create_site is deprecated, use create and add markdown files manually.\n")) 9 | do 10 | local _with_0 = site 11 | _with_0:init_from_fn(init_fn) 12 | if not (_with_0.autoadd_disabled) then 13 | _with_0.scope:search("*md") 14 | end 15 | return _with_0 16 | end 17 | end 18 | local create 19 | create = function(init_fn, site) 20 | if site == nil then 21 | site = Site() 22 | end 23 | assert(init_fn, "Attempted to create site without initialization function") 24 | do 25 | local _with_0 = site 26 | _with_0:init_from_fn(init_fn) 27 | return _with_0 28 | end 29 | end 30 | return { 31 | create_site = create_site, 32 | create = create 33 | } 34 | -------------------------------------------------------------------------------- /sitegen/init.moon: -------------------------------------------------------------------------------- 1 | Site = require "sitegen.site" 2 | colors = require "ansicolors" 3 | 4 | -- legacy create that adds all md files 5 | create_site = (init_fn, site=Site!) -> 6 | io.stderr\write colors "%{bright}%{red}WARNING: %{reset}sitegen.create_site is deprecated, use create and add markdown files manually.\n" 7 | with site 8 | \init_from_fn init_fn 9 | .scope\search "*md" unless .autoadd_disabled 10 | 11 | create = (init_fn, site=Site!) -> 12 | assert init_fn, "Attempted to create site without initialization function" 13 | with site 14 | \init_from_fn init_fn 15 | 16 | { 17 | :create_site 18 | :create 19 | } 20 | -------------------------------------------------------------------------------- /sitegen/output.lua: -------------------------------------------------------------------------------- 1 | local colors = require("ansicolors") 2 | local Logger 3 | do 4 | local _class_0 5 | local _base_0 = { 6 | _flatten = function(self, ...) 7 | return table.concat((function(...) 8 | local _accum_0 = { } 9 | local _len_0 = 1 10 | local _list_0 = { 11 | ... 12 | } 13 | for _index_0 = 1, #_list_0 do 14 | local p = _list_0[_index_0] 15 | _accum_0[_len_0] = tostring(p) 16 | _len_0 = _len_0 + 1 17 | end 18 | return _accum_0 19 | end)(...), " ") 20 | end, 21 | plain = function(self, ...) 22 | return self:print(self:_flatten(...)) 23 | end, 24 | notice = function(self, prefix, ...) 25 | return self:print(colors("%{bright}%{yellow}" .. tostring(prefix) .. ":%{reset} ") .. self:_flatten(...)) 26 | end, 27 | positive = function(self, prefix, ...) 28 | return self:print(colors("%{bright}%{green}" .. tostring(prefix) .. ":%{reset} ") .. self:_flatten(...)) 29 | end, 30 | negative = function(self, prefix, ...) 31 | return self:print(colors("%{bright}%{red}" .. tostring(prefix) .. ":%{reset} ") .. self:_flatten(...)) 32 | end, 33 | warn = function(self, ...) 34 | return self:notice("Warning", ...) 35 | end, 36 | error = function(self, ...) 37 | return self:negative("Error", ...) 38 | end, 39 | render = function(self, source, dest) 40 | return self:positive("rendered", tostring(source) .. " -> " .. tostring(dest)) 41 | end, 42 | build = function(self, ...) 43 | return self:positive("built", ...) 44 | end, 45 | print = function(self, ...) 46 | if self.opts.silent then 47 | return 48 | end 49 | io.stderr:write(tostring(table.concat({ 50 | ... 51 | }, "\t")) .. "\n") 52 | return io.stderr:flush() 53 | end 54 | } 55 | _base_0.__index = _base_0 56 | _class_0 = setmetatable({ 57 | __init = function(self, opts) 58 | if opts == nil then 59 | opts = { } 60 | end 61 | self.opts = opts 62 | end, 63 | __base = _base_0, 64 | __name = "Logger" 65 | }, { 66 | __index = _base_0, 67 | __call = function(cls, ...) 68 | local _self_0 = setmetatable({}, _base_0) 69 | cls.__init(_self_0, ...) 70 | return _self_0 71 | end 72 | }) 73 | _base_0.__class = _class_0 74 | Logger = _class_0 75 | end 76 | return { 77 | Logger = Logger 78 | } 79 | -------------------------------------------------------------------------------- /sitegen/output.moon: -------------------------------------------------------------------------------- 1 | 2 | colors = require "ansicolors" 3 | 4 | class Logger 5 | new: (@opts={}) => 6 | 7 | _flatten: (...) => 8 | table.concat [tostring p for p in *{...}], " " 9 | 10 | plain: (...) => 11 | @print @_flatten ... 12 | 13 | notice: (prefix, ...) => 14 | @print colors("%{bright}%{yellow}#{prefix}:%{reset} ") .. @_flatten ... 15 | 16 | positive: (prefix, ...) => 17 | @print colors("%{bright}%{green}#{prefix}:%{reset} ") .. @_flatten ... 18 | 19 | negative: (prefix, ...) => 20 | @print colors("%{bright}%{red}#{prefix}:%{reset} ") .. @_flatten ... 21 | 22 | -- 23 | 24 | warn: (...) => 25 | @notice "Warning", ... 26 | 27 | error: (...) => 28 | @negative "Error", ... 29 | 30 | render: (source, dest) => 31 | @positive "rendered", "#{source} -> #{dest}" 32 | 33 | build: (...) => 34 | @positive "built", ... 35 | 36 | print: (...) => 37 | return if @opts.silent 38 | io.stderr\write "#{table.concat {...}, "\t"}\n" 39 | io.stderr\flush! 40 | 41 | { 42 | :Logger 43 | } 44 | 45 | -------------------------------------------------------------------------------- /sitegen/page.moon: -------------------------------------------------------------------------------- 1 | html = require "sitegen.html" 2 | 3 | Path = require "sitegen.path" 4 | 5 | import 6 | Stack 7 | split 8 | throw_error 9 | error_context 10 | escape_patt 11 | extend 12 | from require "sitegen.common" 13 | 14 | 15 | -- an individual page 16 | -- source: the subpath for the page's source 17 | -- target: where the output of the page is written 18 | -- meta: the parsed header merged with any additional page options 19 | class Page 20 | __tostring: => table.concat { "" } 21 | 22 | new: (@site, @source) => 23 | @renderer = @site\renderer_for @source 24 | 25 | source_text = @read! 26 | filter = @site\filter_for @source 27 | filter_opts = {} 28 | 29 | if filter 30 | source_text = filter(filter_opts, source_text) or source_text 31 | 32 | -- extract metadata 33 | @render_fn, @meta = @renderer\load source_text, @source 34 | @meta = @meta or {} 35 | 36 | @merge_meta filter_opts 37 | 38 | if override_meta = @site.scope.meta[@source] 39 | @merge_meta override_meta 40 | 41 | @target = if @meta.target_fname 42 | Path.join @site.config.out_dir, @meta.target_fname 43 | elseif @meta.target 44 | Path.join @site.config.out_dir, @meta.target .. "." .. @renderer.ext 45 | else 46 | @site\output_path_for @source, @renderer.ext 47 | 48 | @trigger "page.new" 49 | 50 | trigger: (event, ...) => 51 | @site.events\trigger event, @, ... 52 | 53 | pipe: (event, ...) => 54 | select 2, @site.events\pipe event, @, ... 55 | 56 | merge_meta: (tbl) => 57 | for k,v in pairs tbl 58 | @meta[k] = v 59 | 60 | url_for: (absolute=false) => 61 | front = "^"..escape_patt @site.config.out_dir 62 | path = @target\gsub front, "" 63 | 64 | if absolute 65 | base = @site.user_vars.base_url or @site.user_vars.url or "/" 66 | path = Path.join base, path 67 | 68 | path 69 | 70 | link_to: => 71 | html.build -> a { @title, href: @url_for! } 72 | 73 | -- write the file, return path to written file 74 | write: => 75 | error_context "#{@source} -> #{@target}", -> 76 | content = @render! 77 | assert @site.io.write_file_safe @target, content 78 | 79 | source = @site.io.full_path @source 80 | target = @site.io.full_path @target 81 | 82 | @site.logger\render source, target 83 | @target 84 | 85 | -- read the source 86 | read: => 87 | with out = @site.io.read_file @source 88 | unless out 89 | throw_error "failed to read input file: " .. @source 90 | 91 | plugin_template_helpers: => 92 | helpers = {} 93 | 94 | for plugin in *@site.plugins 95 | continue unless plugin.tpl_helpers 96 | for helper_name in *plugin.tpl_helpers 97 | helpers[helper_name] = (...) -> 98 | plugin[helper_name] plugin, @, ... 99 | 100 | helpers 101 | 102 | get_root: => 103 | base = Path.basepath @target 104 | parts = for i = 1, #split(base, "/") - 1 do ".." 105 | root = table.concat parts, "/" 106 | root = "." if root == "" 107 | root 108 | 109 | get_tpl_scope: => 110 | user_vars_scope = {} 111 | if @site.user_vars 112 | -- bind the functions to the page 113 | for k,v in pairs @site.user_vars 114 | user_vars_scope[k] = if type(v) == "function" 115 | (...) -> v @, ... 116 | else 117 | v 118 | 119 | extend { 120 | generate_date: os.date! 121 | root: @get_root! 122 | }, @plugin_template_helpers!, @meta, user_vars_scope 123 | 124 | set_content: (@_content) => 125 | 126 | render: => 127 | return @_content if @_content 128 | @trigger "page.before_render" 129 | 130 | @template_stack = Stack! 131 | 132 | @tpl_scope = @get_tpl_scope! 133 | 134 | @_content = assert @render_fn(@), "failed to get content from renderer" 135 | @trigger "page.content_rendered", @_content 136 | 137 | @_inner_content = @_content 138 | 139 | -- wrap the page in template 140 | if @tpl_scope.template != false 141 | @template_stack\push @tpl_scope.template or @site.config.default_template 142 | 143 | while #@template_stack > 0 144 | tpl_name = @template_stack\pop! 145 | if template = @site.templates\find_by_name tpl_name 146 | @tpl_scope.body = @_content 147 | @_content = template @ 148 | 149 | @trigger "page.rendered" 150 | 151 | @_content 152 | 153 | { 154 | :Page 155 | } 156 | 157 | -------------------------------------------------------------------------------- /sitegen/path.lua: -------------------------------------------------------------------------------- 1 | local io = io 2 | local needs_shell_escape 3 | needs_shell_escape = function(str) 4 | return not not str:match("[^%w_-]") 5 | end 6 | local shell_escape 7 | shell_escape = function(str) 8 | return str:gsub("'", "'\\''") 9 | end 10 | local up, exists, normalize, basepath, filename, write_file_safe, write_file, read_file, mkdir, rmdir, copy, join, _prepare_command, exec, read_exec, relative_to, annotate 11 | up = function(path) 12 | path = path:gsub("/$", "") 13 | path = path:gsub("[^/]*$", "") 14 | if path ~= "" then 15 | return path 16 | end 17 | end 18 | exists = function(path) 19 | local file = io.open(path) 20 | if file then 21 | return file:close() and true 22 | end 23 | end 24 | normalize = function(path) 25 | return (path:gsub("^%./", "")) 26 | end 27 | basepath = function(path) 28 | return (path:match("^(.*)/[^/]*$") or ".") 29 | end 30 | filename = function(path) 31 | return (path:match("([^/]*)$")) 32 | end 33 | write_file_safe = function(path, content, check_exists) 34 | if check_exists == nil then 35 | check_exists = false 36 | end 37 | if check_exists and exists(path) then 38 | return nil, "file already exists `" .. tostring(path) .. "`" 39 | end 40 | do 41 | local prefix = path:match("^(.+)/[^/]+$") 42 | if prefix then 43 | if not (exists(prefix)) then 44 | mkdir(prefix) 45 | end 46 | end 47 | end 48 | write_file(path, content) 49 | return true 50 | end 51 | write_file = function(path, content) 52 | assert(content, "trying to write `" .. tostring(path) .. "` with no content") 53 | do 54 | local _with_0 = io.open(path, "w") 55 | _with_0:write(content) 56 | _with_0:close() 57 | return _with_0 58 | end 59 | end 60 | read_file = function(path) 61 | local file = io.open(path) 62 | if not (file) then 63 | error("file doesn't exist `" .. tostring(path) .. "'") 64 | end 65 | do 66 | local _with_0 = file:read("*a") 67 | file:close() 68 | return _with_0 69 | end 70 | end 71 | mkdir = function(path) 72 | return os.execute("mkdir -p '" .. tostring(shell_escape(path)) .. "'") 73 | end 74 | rmdir = function(path) 75 | return os.execute("rm -r '" .. tostring(shell_escape(path)) .. "'") 76 | end 77 | copy = function(src, dest) 78 | return os.execute("cp '" .. tostring(shell_escape(src)) .. "' '" .. tostring(shell_escape(dest)) .. "'") 79 | end 80 | join = function(a, b) 81 | assert(a, "missing left argument to Path.join") 82 | assert(b, "missing right argument to Path.join") 83 | if a ~= "/" then 84 | a = a:match("^(.*)/$") or a 85 | end 86 | b = b:match("^/(.*)$") or b 87 | if a == "" then 88 | return b 89 | end 90 | if b == "" then 91 | return a 92 | end 93 | return a .. "/" .. b 94 | end 95 | _prepare_command = function(cmd, ...) 96 | local args 97 | do 98 | local _accum_0 = { } 99 | local _len_0 = 1 100 | local _list_0 = { 101 | ... 102 | } 103 | for _index_0 = 1, #_list_0 do 104 | local x = _list_0[_index_0] 105 | if needs_shell_escape(x) then 106 | _accum_0[_len_0] = "'" .. tostring(shell_escape(x)) .. "'" 107 | else 108 | _accum_0[_len_0] = x 109 | end 110 | _len_0 = _len_0 + 1 111 | end 112 | args = _accum_0 113 | end 114 | args = table.concat(args, " ") 115 | return tostring(cmd) .. " " .. tostring(args) 116 | end 117 | exec = function(cmd, ...) 118 | return os.execute(_prepare_command(cmd, ...)) 119 | end 120 | read_exec = function(cmd, ...) 121 | local f = assert(io.popen(_prepare_command(cmd, ...), "r")) 122 | do 123 | local _with_0 = f:read("*a") 124 | f:close() 125 | return _with_0 126 | end 127 | end 128 | relative_to = function(self, prefix) 129 | local methods = { 130 | "mkdir", 131 | "read_file", 132 | "write_file", 133 | "write_file_safe", 134 | "exists" 135 | } 136 | local prefixed 137 | prefixed = function(fn) 138 | return function(path, ...) 139 | return self[fn](self.join(prefix, path), ...) 140 | end 141 | end 142 | local m = setmetatable((function() 143 | local _tbl_0 = { } 144 | for _index_0 = 1, #methods do 145 | local meth = methods[_index_0] 146 | _tbl_0[meth] = prefixed(meth) 147 | end 148 | return _tbl_0 149 | end)(), { 150 | __index = self 151 | }) 152 | m.full_path = function(path) 153 | return self.join(prefix, path) 154 | end 155 | m.strip_prefix = function(path) 156 | local escape_patt 157 | escape_patt = require("sitegen.common").escape_patt 158 | return path:gsub("^" .. tostring(escape_patt(prefix)) .. "/?", "") 159 | end 160 | m.get_prefix = function() 161 | return prefix 162 | end 163 | m.set_prefix = function(p) 164 | prefix = p 165 | end 166 | return m 167 | end 168 | annotate = function(self) 169 | local wrap_module 170 | wrap_module = function(obj, verbs) 171 | return setmetatable({ }, { 172 | __newindex = function(self, name, value) 173 | obj[name] = value 174 | end, 175 | __index = function(self, name) 176 | local fn = obj[name] 177 | if not type(fn) == "function" then 178 | return fn 179 | end 180 | if verbs[name] then 181 | return function(...) 182 | print(verbs[name], (...)) 183 | return fn(...) 184 | end 185 | else 186 | return fn 187 | end 188 | end 189 | }) 190 | end 191 | local colors = require("ansicolors") 192 | return wrap_module(self, { 193 | mkdir = colors("%{bright}%{magenta}made directory%{reset}"), 194 | write_file = colors("%{bright}%{yellow}wrote%{reset}"), 195 | write_file_safe = colors("%{bright}%{yellow}wrote%{reset}"), 196 | read_file = colors("%{bright}%{green}read%{reset}"), 197 | exists = colors("%{bright}%{cyan}exists?%{reset}"), 198 | exec = colors("%{bright}%{red}exec%{reset}"), 199 | read_exec = colors("%{bright}%{red}exec%{reset}") 200 | }) 201 | end 202 | return { 203 | up = up, 204 | exists = exists, 205 | normalize = normalize, 206 | basepath = basepath, 207 | filename = filename, 208 | write_file = write_file, 209 | write_file_safe = write_file_safe, 210 | mkdir = mkdir, 211 | rmdir = rmdir, 212 | copy = copy, 213 | join = join, 214 | read_file = read_file, 215 | shell_escape = shell_escape, 216 | exec = exec, 217 | read_exec = read_exec, 218 | relative_to = relative_to, 219 | annotate = annotate 220 | } 221 | -------------------------------------------------------------------------------- /sitegen/path.moon: -------------------------------------------------------------------------------- 1 | io = io 2 | 3 | needs_shell_escape = (str) -> 4 | not not str\match "[^%w_-]" 5 | 6 | shell_escape = (str) -> 7 | str\gsub "'", "'\\''" 8 | 9 | local * 10 | 11 | -- move up a directory 12 | -- /hello/world -> /hello 13 | up = (path) -> 14 | path = path\gsub "/$", "" 15 | path = path\gsub "[^/]*$", "" 16 | path if path != "" 17 | 18 | exists = (path) -> 19 | file = io.open path 20 | file\close! and true if file 21 | 22 | normalize = (path) -> 23 | (path\gsub "^%./", "") 24 | 25 | basepath = (path) -> 26 | (path\match"^(.*)/[^/]*$" or ".") 27 | 28 | filename = (path) -> 29 | (path\match"([^/]*)$") 30 | 31 | -- write a file, making sure directory exists and file isn't already written 32 | write_file_safe = (path, content, check_exists=false) -> 33 | if check_exists and exists path 34 | return nil, "file already exists `#{path}`" 35 | 36 | if prefix = path\match "^(.+)/[^/]+$" 37 | mkdir prefix unless exists prefix 38 | 39 | write_file path, content 40 | true 41 | 42 | write_file = (path, content) -> 43 | assert content, "trying to write `#{path}` with no content" 44 | with io.open path, "w" 45 | \write content 46 | \close! 47 | 48 | read_file = (path) -> 49 | file = io.open path 50 | error "file doesn't exist `#{path}'" unless file 51 | with file\read "*a" 52 | file\close! 53 | 54 | mkdir = (path) -> 55 | os.execute "mkdir -p '#{shell_escape path}'" 56 | 57 | rmdir = (path) -> 58 | os.execute "rm -r '#{shell_escape path}'" 59 | 60 | copy = (src, dest) -> 61 | os.execute "cp '#{shell_escape src}' '#{shell_escape dest}'" 62 | 63 | join = (a, b) -> 64 | assert a, "missing left argument to Path.join" 65 | assert b, "missing right argument to Path.join" 66 | 67 | a = a\match"^(.*)/$" or a if a != "/" 68 | b = b\match"^/(.*)$" or b 69 | return b if a == "" 70 | return a if b == "" 71 | a .. "/" .. b 72 | 73 | _prepare_command = (cmd, ...) -> 74 | args = for x in *{...} 75 | if needs_shell_escape x 76 | "'#{shell_escape x}'" 77 | else 78 | x 79 | 80 | args = table.concat args, " " 81 | "#{cmd} #{args}" 82 | 83 | exec = (cmd, ...) -> 84 | os.execute _prepare_command cmd, ... 85 | 86 | read_exec = (cmd, ...) -> 87 | f = assert io.popen _prepare_command(cmd, ...), "r" 88 | with f\read "*a" 89 | f\close! 90 | 91 | relative_to = (prefix) => 92 | methods = {"mkdir", "read_file", "write_file", "write_file_safe", "exists"} 93 | 94 | prefixed = (fn) -> 95 | (path, ...) -> 96 | @[fn] @.join(prefix, path), ... 97 | 98 | m = setmetatable {meth, prefixed(meth) for meth in *methods}, { 99 | __index: @ 100 | } 101 | 102 | m.full_path = (path) -> @.join prefix, path 103 | m.strip_prefix = (path) -> 104 | import escape_patt from require "sitegen.common" 105 | path\gsub "^#{escape_patt prefix}/?", "" 106 | 107 | m.get_prefix = -> prefix 108 | m.set_prefix = (p) -> prefix = p 109 | 110 | m 111 | 112 | annotate = => 113 | wrap_module = (obj, verbs) -> 114 | setmetatable {}, { 115 | __newindex: (name, value) => 116 | obj[name] = value 117 | 118 | __index: (name) => 119 | fn = obj[name] 120 | return fn if not type(fn) == "function" 121 | if verbs[name] 122 | (...) -> 123 | print verbs[name], (...) 124 | fn ... 125 | else 126 | fn 127 | } 128 | 129 | colors = require "ansicolors" 130 | wrap_module @, { 131 | mkdir: colors "%{bright}%{magenta}made directory%{reset}" 132 | write_file: colors "%{bright}%{yellow}wrote%{reset}" 133 | write_file_safe: colors "%{bright}%{yellow}wrote%{reset}" 134 | read_file: colors "%{bright}%{green}read%{reset}" 135 | exists: colors "%{bright}%{cyan}exists?%{reset}" 136 | exec: colors "%{bright}%{red}exec%{reset}" 137 | read_exec: colors "%{bright}%{red}exec%{reset}" 138 | } 139 | 140 | { 141 | :up, :exists, :normalize, :basepath, :filename, :write_file, 142 | :write_file_safe, :mkdir, :rmdir, :copy, :join, :read_file, :shell_escape, 143 | :exec, :read_exec 144 | 145 | :relative_to, :annotate 146 | } 147 | -------------------------------------------------------------------------------- /sitegen/plugin.lua: -------------------------------------------------------------------------------- 1 | local Plugin 2 | do 3 | local _class_0 4 | local _base_0 = { } 5 | _base_0.__index = _base_0 6 | _class_0 = setmetatable({ 7 | __init = function(self, site) 8 | self.site = site 9 | if self.events then 10 | for event_name, func in pairs(self.events) do 11 | self.site.events:on(event_name, function(...) 12 | return func(self, ...) 13 | end) 14 | end 15 | end 16 | end, 17 | __base = _base_0, 18 | __name = "Plugin" 19 | }, { 20 | __index = _base_0, 21 | __call = function(cls, ...) 22 | local _self_0 = setmetatable({}, _base_0) 23 | cls.__init(_self_0, ...) 24 | return _self_0 25 | end 26 | }) 27 | _base_0.__class = _class_0 28 | Plugin = _class_0 29 | end 30 | return { 31 | Plugin = Plugin 32 | } 33 | -------------------------------------------------------------------------------- /sitegen/plugin.moon: -------------------------------------------------------------------------------- 1 | 2 | class Plugin 3 | -- events: {} 4 | -- tpl_helpers: { "some_method" } 5 | -- mixin_funcs: { "some_method" } 6 | -- write: => 7 | 8 | new: (@site) => 9 | if @events 10 | for event_name, func in pairs @events 11 | @site.events\on event_name, (...) -> func @, ... 12 | 13 | { 14 | :Plugin 15 | } 16 | -------------------------------------------------------------------------------- /sitegen/plugins/analytics.lua: -------------------------------------------------------------------------------- 1 | local Plugin 2 | Plugin = require("sitegen.plugin").Plugin 3 | local AnalyticsPlugin 4 | do 5 | local _class_0 6 | local _parent_0 = Plugin 7 | local _base_0 = { 8 | tpl_helpers = { 9 | "analytics" 10 | }, 11 | analytics = function(self, page, arg) 12 | local code = arg[1] 13 | return [[]] 26 | end 27 | } 28 | _base_0.__index = _base_0 29 | setmetatable(_base_0, _parent_0.__base) 30 | _class_0 = setmetatable({ 31 | __init = function(self, ...) 32 | return _class_0.__parent.__init(self, ...) 33 | end, 34 | __base = _base_0, 35 | __name = "AnalyticsPlugin", 36 | __parent = _parent_0 37 | }, { 38 | __index = function(cls, name) 39 | local val = rawget(_base_0, name) 40 | if val == nil then 41 | local parent = rawget(cls, "__parent") 42 | if parent then 43 | return parent[name] 44 | end 45 | else 46 | return val 47 | end 48 | end, 49 | __call = function(cls, ...) 50 | local _self_0 = setmetatable({}, _base_0) 51 | cls.__init(_self_0, ...) 52 | return _self_0 53 | end 54 | }) 55 | _base_0.__class = _class_0 56 | if _parent_0.__inherited then 57 | _parent_0.__inherited(_parent_0, _class_0) 58 | end 59 | AnalyticsPlugin = _class_0 60 | return _class_0 61 | end 62 | -------------------------------------------------------------------------------- /sitegen/plugins/analytics.moon: -------------------------------------------------------------------------------- 1 | 2 | import Plugin from require "sitegen.plugin" 3 | 4 | class AnalyticsPlugin extends Plugin 5 | tpl_helpers: { "analytics" } 6 | 7 | analytics: (page, arg) => 8 | code = arg[1] 9 | [[]] 22 | 23 | 24 | -------------------------------------------------------------------------------- /sitegen/plugins/blog.lua: -------------------------------------------------------------------------------- 1 | local Plugin 2 | Plugin = require("sitegen.plugin").Plugin 3 | local query = require("sitegen.query") 4 | local copy, bind_methods 5 | do 6 | local _obj_0 = require("moon") 7 | copy, bind_methods = _obj_0.copy, _obj_0.bind_methods 8 | end 9 | local insert 10 | insert = table.insert 11 | local unpack 12 | unpack = require("sitegen.common").unpack 13 | local render_feed 14 | render_feed = require("sitegen.plugins.feed").render_feed 15 | local BlogPlugin 16 | do 17 | local _class_0 18 | local _parent_0 = Plugin 19 | local _base_0 = { 20 | mixin_funcs = { 21 | "blog_feed" 22 | }, 23 | blog_feed = function(self, opts) 24 | if opts == nil then 25 | opts = { } 26 | end 27 | self.create_feed = true 28 | for k, v in pairs(opts) do 29 | self.opts[k] = v 30 | end 31 | end, 32 | write = function(self) 33 | if not (self.create_feed) then 34 | return 35 | end 36 | self.posts = self.site:query_pages(self.opts.filter, { 37 | sort = query.sort.date() 38 | }) 39 | if not (self.posts[1]) then 40 | return 41 | end 42 | self.site.logger:plain("blog posts:", #self.posts) 43 | local title, url, description 44 | do 45 | local _obj_0 = self.site.user_vars 46 | title, url, description = _obj_0.title, _obj_0.url, _obj_0.description 47 | end 48 | local feed_posts 49 | do 50 | local _accum_0 = { } 51 | local _len_0 = 1 52 | local _list_0 = self.posts 53 | for _index_0 = 1, #_list_0 do 54 | local page = _list_0[_index_0] 55 | local meta = page.meta 56 | local _value_0 = self.opts.prepare(page, { 57 | title = meta.title, 58 | date = meta.date, 59 | link = page:url_for(true), 60 | description = rawget(meta, "description") 61 | }) 62 | _accum_0[_len_0] = _value_0 63 | _len_0 = _len_0 + 1 64 | end 65 | feed_posts = _accum_0 66 | end 67 | local rss_text = render_feed({ 68 | title = self.opts.title or title, 69 | description = self.opts.description or description, 70 | link = self.opts.url or url, 71 | unpack(feed_posts) 72 | }) 73 | return self.site:write_file(self.opts.out_file, rss_text) 74 | end 75 | } 76 | _base_0.__index = _base_0 77 | setmetatable(_base_0, _parent_0.__base) 78 | _class_0 = setmetatable({ 79 | __init = function(self, site) 80 | self.site = site 81 | self.opts = { 82 | out_file = "feed.xml", 83 | filter = { 84 | is_a = query.filter.contains("blog_post") 85 | }, 86 | prepare = function(page, ...) 87 | return ... 88 | end 89 | } 90 | end, 91 | __base = _base_0, 92 | __name = "BlogPlugin", 93 | __parent = _parent_0 94 | }, { 95 | __index = function(cls, name) 96 | local val = rawget(_base_0, name) 97 | if val == nil then 98 | local parent = rawget(cls, "__parent") 99 | if parent then 100 | return parent[name] 101 | end 102 | else 103 | return val 104 | end 105 | end, 106 | __call = function(cls, ...) 107 | local _self_0 = setmetatable({}, _base_0) 108 | cls.__init(_self_0, ...) 109 | return _self_0 110 | end 111 | }) 112 | _base_0.__class = _class_0 113 | if _parent_0.__inherited then 114 | _parent_0.__inherited(_parent_0, _class_0) 115 | end 116 | BlogPlugin = _class_0 117 | return _class_0 118 | end 119 | -------------------------------------------------------------------------------- /sitegen/plugins/blog.moon: -------------------------------------------------------------------------------- 1 | 2 | import Plugin from require "sitegen.plugin" 3 | 4 | query = require "sitegen.query" 5 | 6 | import copy, bind_methods from require "moon" 7 | import insert from table 8 | import unpack from require "sitegen.common" 9 | 10 | import render_feed from require "sitegen.plugins.feed" 11 | 12 | class BlogPlugin extends Plugin 13 | new: (@site) => 14 | @opts = { 15 | out_file: "feed.xml" 16 | filter: { is_a: query.filter.contains "blog_post" } 17 | prepare: (page, ...) -> ... 18 | } 19 | 20 | 21 | mixin_funcs: { "blog_feed" } 22 | 23 | blog_feed: (opts={}) => 24 | @create_feed = true 25 | 26 | for k,v in pairs opts 27 | @opts[k] = v 28 | 29 | 30 | write: => 31 | return unless @create_feed 32 | @posts = @site\query_pages @opts.filter, sort: query.sort.date! 33 | 34 | return unless @posts[1] 35 | 36 | @site.logger\plain "blog posts:", #@posts 37 | 38 | import title, url, description from @site.user_vars 39 | 40 | feed_posts = for page in *@posts 41 | meta = page.meta 42 | 43 | @opts.prepare page, { 44 | title: meta.title 45 | date: meta.date 46 | link: page\url_for true 47 | 48 | -- to avoid getting description of page from chained meta 49 | description: rawget meta, "description" 50 | } 51 | 52 | rss_text = render_feed { 53 | title: @opts.title or title 54 | description: @opts.description or description 55 | link: @opts.url or url 56 | 57 | unpack feed_posts 58 | } 59 | 60 | @site\write_file @opts.out_file, rss_text 61 | -------------------------------------------------------------------------------- /sitegen/plugins/coffee_script.lua: -------------------------------------------------------------------------------- 1 | local Plugin 2 | Plugin = require("sitegen.plugin").Plugin 3 | local html = require("sitegen.html") 4 | local unpack 5 | unpack = require("sitegen.common").unpack 6 | local CoffeeScriptPlugin 7 | do 8 | local _class_0 9 | local _parent_0 = Plugin 10 | local _base_0 = { 11 | tpl_helpers = { 12 | "render_coffee" 13 | }, 14 | compile_coffee = function(self, fname) 15 | local p = io.popen(("coffee -c -p %s"):format(fname)) 16 | return p:read("*a") 17 | end, 18 | render_coffee = function(self, page, arg) 19 | local fname = unpack(arg) 20 | return html.build(function() 21 | return script({ 22 | type = "text/javascript", 23 | raw(self:compile_coffee(fname)) 24 | }) 25 | end) 26 | end 27 | } 28 | _base_0.__index = _base_0 29 | setmetatable(_base_0, _parent_0.__base) 30 | _class_0 = setmetatable({ 31 | __init = function(self, ...) 32 | return _class_0.__parent.__init(self, ...) 33 | end, 34 | __base = _base_0, 35 | __name = "CoffeeScriptPlugin", 36 | __parent = _parent_0 37 | }, { 38 | __index = function(cls, name) 39 | local val = rawget(_base_0, name) 40 | if val == nil then 41 | local parent = rawget(cls, "__parent") 42 | if parent then 43 | return parent[name] 44 | end 45 | else 46 | return val 47 | end 48 | end, 49 | __call = function(cls, ...) 50 | local _self_0 = setmetatable({}, _base_0) 51 | cls.__init(_self_0, ...) 52 | return _self_0 53 | end 54 | }) 55 | _base_0.__class = _class_0 56 | if _parent_0.__inherited then 57 | _parent_0.__inherited(_parent_0, _class_0) 58 | end 59 | CoffeeScriptPlugin = _class_0 60 | return _class_0 61 | end 62 | -------------------------------------------------------------------------------- /sitegen/plugins/coffee_script.moon: -------------------------------------------------------------------------------- 1 | import Plugin from require "sitegen.plugin" 2 | 3 | html = require "sitegen.html" 4 | 5 | import unpack from require "sitegen.common" 6 | 7 | -- embed compiled coffeescript directly into the page 8 | class CoffeeScriptPlugin extends Plugin 9 | tpl_helpers: { "render_coffee" } 10 | 11 | compile_coffee: (fname) => 12 | p = io.popen ("coffee -c -p %s")\format fname 13 | p\read "*a" 14 | 15 | render_coffee: (page, arg) => 16 | fname = unpack arg 17 | html.build -> 18 | script { 19 | type: "text/javascript" 20 | raw @compile_coffee fname 21 | } 22 | 23 | -------------------------------------------------------------------------------- /sitegen/plugins/deploy.lua: -------------------------------------------------------------------------------- 1 | local Plugin 2 | Plugin = require("sitegen.plugin").Plugin 3 | local DeployPlugin 4 | do 5 | local _class_0 6 | local _parent_0 = Plugin 7 | local _base_0 = { 8 | mixin_funcs = { 9 | "deploy_to" 10 | }, 11 | command_actions = { 12 | { 13 | method = "deploy", 14 | argparser = function(command) 15 | do 16 | local _with_0 = command 17 | _with_0:summary("Deploy previously generated site over ssh using rsync") 18 | _with_0:argument("host", "Sever hostname"):args("?") 19 | _with_0:argument("path", "Path on server to deploy to"):args("?") 20 | return _with_0 21 | end 22 | end 23 | } 24 | }, 25 | deploy_to = function(self, host, path) 26 | if host == nil then 27 | host = error("need host") 28 | end 29 | if path == nil then 30 | path = error("need path") 31 | end 32 | self.host, self.path = host, path 33 | end, 34 | deploy = function(self, args) 35 | local throw_error 36 | throw_error = require("sitegen.common").throw_error 37 | local log 38 | log = require("sitegen.cmd.util").log 39 | local host = args.host or self.host 40 | local path = args.path or self.path 41 | if not (host) then 42 | throw_error("need host") 43 | end 44 | if not (path) then 45 | throw_error("need path") 46 | end 47 | log("uploading to:", host, path) 48 | return self:sync() 49 | end, 50 | sync = function(self) 51 | assert(self.host, "missing host for deploy") 52 | assert(self.path, "missing path for deploy") 53 | return os.execute(table.concat({ 54 | 'rsync -rvuzL www/ ', 55 | self.host, 56 | ':', 57 | self.path 58 | })) 59 | end 60 | } 61 | _base_0.__index = _base_0 62 | setmetatable(_base_0, _parent_0.__base) 63 | _class_0 = setmetatable({ 64 | __init = function(self, ...) 65 | return _class_0.__parent.__init(self, ...) 66 | end, 67 | __base = _base_0, 68 | __name = "DeployPlugin", 69 | __parent = _parent_0 70 | }, { 71 | __index = function(cls, name) 72 | local val = rawget(_base_0, name) 73 | if val == nil then 74 | local parent = rawget(cls, "__parent") 75 | if parent then 76 | return parent[name] 77 | end 78 | else 79 | return val 80 | end 81 | end, 82 | __call = function(cls, ...) 83 | local _self_0 = setmetatable({}, _base_0) 84 | cls.__init(_self_0, ...) 85 | return _self_0 86 | end 87 | }) 88 | _base_0.__class = _class_0 89 | if _parent_0.__inherited then 90 | _parent_0.__inherited(_parent_0, _class_0) 91 | end 92 | DeployPlugin = _class_0 93 | return _class_0 94 | end 95 | -------------------------------------------------------------------------------- /sitegen/plugins/deploy.moon: -------------------------------------------------------------------------------- 1 | import Plugin from require "sitegen.plugin" 2 | 3 | class DeployPlugin extends Plugin 4 | mixin_funcs: { "deploy_to" } 5 | command_actions: { 6 | { 7 | method: "deploy" 8 | argparser: (command) -> 9 | with command 10 | \summary "Deploy previously generated site over ssh using rsync" 11 | 12 | \argument("host", "Sever hostname")\args "?" 13 | \argument("path", "Path on server to deploy to")\args "?" 14 | } 15 | } 16 | 17 | deploy_to: (@host=error"need host", @path=error"need path") => 18 | 19 | deploy: (args) => 20 | import throw_error from require "sitegen.common" 21 | import log from require "sitegen.cmd.util" 22 | 23 | host = args.host or @host 24 | path = args.path or @path 25 | 26 | throw_error "need host" unless host 27 | throw_error "need path" unless path 28 | 29 | log "uploading to:", host, path 30 | 31 | @sync! 32 | 33 | -- 'rsync -arvuz www/ leaf@leafo.net:www/test' 34 | sync: => 35 | assert @host, "missing host for deploy" 36 | assert @path, "missing path for deploy" 37 | 38 | os.execute table.concat { 39 | 'rsync -rvuzL www/ ', @host ,':', @path 40 | } 41 | 42 | -------------------------------------------------------------------------------- /sitegen/plugins/dump.lua: -------------------------------------------------------------------------------- 1 | local dump 2 | dump = require("moon").dump 3 | local Plugin 4 | Plugin = require("sitegen.plugin").Plugin 5 | local DumpPlugin 6 | do 7 | local _class_0 8 | local _parent_0 = Plugin 9 | local _base_0 = { 10 | tpl_helpers = { 11 | "dump" 12 | }, 13 | dump = function(self, page, args) 14 | return dump(args) 15 | end 16 | } 17 | _base_0.__index = _base_0 18 | setmetatable(_base_0, _parent_0.__base) 19 | _class_0 = setmetatable({ 20 | __init = function(self, ...) 21 | return _class_0.__parent.__init(self, ...) 22 | end, 23 | __base = _base_0, 24 | __name = "DumpPlugin", 25 | __parent = _parent_0 26 | }, { 27 | __index = function(cls, name) 28 | local val = rawget(_base_0, name) 29 | if val == nil then 30 | local parent = rawget(cls, "__parent") 31 | if parent then 32 | return parent[name] 33 | end 34 | else 35 | return val 36 | end 37 | end, 38 | __call = function(cls, ...) 39 | local _self_0 = setmetatable({}, _base_0) 40 | cls.__init(_self_0, ...) 41 | return _self_0 42 | end 43 | }) 44 | _base_0.__class = _class_0 45 | if _parent_0.__inherited then 46 | _parent_0.__inherited(_parent_0, _class_0) 47 | end 48 | DumpPlugin = _class_0 49 | return _class_0 50 | end 51 | -------------------------------------------------------------------------------- /sitegen/plugins/dump.moon: -------------------------------------------------------------------------------- 1 | 2 | import dump from require "moon" 3 | import Plugin from require "sitegen.plugin" 4 | 5 | class DumpPlugin extends Plugin 6 | tpl_helpers: { "dump" } 7 | dump: (page, args) => 8 | dump args 9 | -------------------------------------------------------------------------------- /sitegen/plugins/feed.lua: -------------------------------------------------------------------------------- 1 | local Plugin 2 | Plugin = require("sitegen.plugin").Plugin 3 | local trim_leading_white, unpack 4 | do 5 | local _obj_0 = require("sitegen.common") 6 | trim_leading_white, unpack = _obj_0.trim_leading_white, _obj_0.unpack 7 | end 8 | local html = require("sitegen.html") 9 | local discount = require("discount") 10 | local date = require("date") 11 | local extend 12 | extend = require("moon").extend 13 | local insert 14 | insert = table.insert 15 | local render_feed 16 | render_feed = function(root) 17 | local concat 18 | concat = function(list) 19 | return html.builders.raw()(html.build(function() 20 | return list 21 | end)) 22 | end 23 | local format_date 24 | format_date = function(date) 25 | if date.fmt then 26 | return date:fmt("${http}") 27 | else 28 | return tostring(date) 29 | end 30 | end 31 | return html.build(function() 32 | return { 33 | raw([[]]), 34 | rss({ 35 | version = "2.0", 36 | channel({ 37 | title(root.title), 38 | link(root.link), 39 | description(root.description), 40 | concat((function() 41 | local _accum_0 = { } 42 | local _len_0 = 1 43 | for _index_0 = 1, #root do 44 | local entry = root[_index_0] 45 | local parts = { } 46 | if entry.title then 47 | insert(parts, title(entry.title)) 48 | end 49 | if entry.link then 50 | insert(parts, link(entry.link)) 51 | end 52 | if entry.date then 53 | insert(parts, pubDate(format_date(entry.date))) 54 | end 55 | if entry.description then 56 | insert(parts, description(cdata(entry.description))) 57 | end 58 | local _value_0 = item(parts) 59 | _accum_0[_len_0] = _value_0 60 | _len_0 = _len_0 + 1 61 | end 62 | return _accum_0 63 | end)()) 64 | }) 65 | }) 66 | } 67 | end) 68 | end 69 | local FeedPlugin 70 | do 71 | local _class_0 72 | local _parent_0 = Plugin 73 | local _base_0 = { 74 | mixin_funcs = { 75 | "feed" 76 | }, 77 | feed = function(self, source, dest) 78 | local moonscript = require("moonscript.base") 79 | local fn = assert(moonscript.loadfile(source)) 80 | return table.insert(self.feeds, { 81 | dest, 82 | fn() 83 | }) 84 | end, 85 | write = function(self) 86 | if not (self.feeds[1]) then 87 | return 88 | end 89 | self.site.logger:plain("feeds:", #self.feeds) 90 | local _list_0 = self.feeds 91 | for _index_0 = 1, #_list_0 do 92 | local feed = _list_0[_index_0] 93 | local dest, root = unpack(feed) 94 | root.description = root.description or "" 95 | for _index_1 = 1, #root do 96 | local entry = root[_index_1] 97 | entry.description = trim_leading_white(entry.description) 98 | extend(entry, root) 99 | local _exp_0 = entry.format 100 | if "markdown" == _exp_0 then 101 | entry.description = discount(entry.description) 102 | else 103 | entry.description = entry.description 104 | end 105 | end 106 | self.site:write_file(dest, render_feed(root)) 107 | end 108 | end 109 | } 110 | _base_0.__index = _base_0 111 | setmetatable(_base_0, _parent_0.__base) 112 | _class_0 = setmetatable({ 113 | __init = function(self, site) 114 | self.site = site 115 | self.feeds = { } 116 | end, 117 | __base = _base_0, 118 | __name = "FeedPlugin", 119 | __parent = _parent_0 120 | }, { 121 | __index = function(cls, name) 122 | local val = rawget(_base_0, name) 123 | if val == nil then 124 | local parent = rawget(cls, "__parent") 125 | if parent then 126 | return parent[name] 127 | end 128 | else 129 | return val 130 | end 131 | end, 132 | __call = function(cls, ...) 133 | local _self_0 = setmetatable({}, _base_0) 134 | cls.__init(_self_0, ...) 135 | return _self_0 136 | end 137 | }) 138 | _base_0.__class = _class_0 139 | local self = _class_0 140 | self.render_feed = render_feed 141 | if _parent_0.__inherited then 142 | _parent_0.__inherited(_parent_0, _class_0) 143 | end 144 | FeedPlugin = _class_0 145 | return _class_0 146 | end 147 | -------------------------------------------------------------------------------- /sitegen/plugins/feed.moon: -------------------------------------------------------------------------------- 1 | 2 | import Plugin from require "sitegen.plugin" 3 | 4 | import 5 | trim_leading_white 6 | unpack 7 | from require "sitegen.common" 8 | 9 | html = require "sitegen.html" 10 | discount = require "discount" 11 | 12 | date = require "date" 13 | 14 | import extend from require "moon" 15 | import insert from table 16 | 17 | render_feed = (root) -> 18 | concat = (list) -> 19 | html.builders.raw! html.build -> list 20 | 21 | format_date = (date) -> 22 | if date.fmt 23 | date\fmt "${http}" 24 | else 25 | tostring date 26 | 27 | html.build -> { 28 | raw [[]] 29 | rss { 30 | version: "2.0" 31 | channel { 32 | title root.title 33 | link root.link 34 | description root.description 35 | concat for entry in *root 36 | parts = { } 37 | insert parts, title entry.title if entry.title 38 | insert parts, link entry.link if entry.link 39 | insert parts, pubDate format_date entry.date if entry.date 40 | insert parts, description cdata entry.description if entry.description 41 | item parts 42 | } 43 | } 44 | } 45 | 46 | class FeedPlugin extends Plugin 47 | @render_feed: render_feed 48 | 49 | mixin_funcs: { "feed" } 50 | 51 | new: (@site) => 52 | @feeds = {} 53 | 54 | feed: (source, dest) => 55 | moonscript = require "moonscript.base" 56 | fn = assert moonscript.loadfile source 57 | table.insert @feeds, { dest, fn! } 58 | 59 | write: => 60 | return unless @feeds[1] 61 | @site.logger\plain "feeds:", #@feeds 62 | 63 | for feed in *@feeds 64 | dest, root = unpack feed 65 | 66 | root.description = root.description or "" 67 | 68 | -- format entries 69 | for entry in *root 70 | entry.description = trim_leading_white entry.description 71 | extend entry, root 72 | 73 | entry.description = switch entry.format 74 | when "markdown" 75 | discount entry.description 76 | else 77 | entry.description 78 | 79 | @site\write_file dest, render_feed root 80 | 81 | -------------------------------------------------------------------------------- /sitegen/plugins/indexer.lua: -------------------------------------------------------------------------------- 1 | local Plugin 2 | Plugin = require("sitegen.plugin").Plugin 3 | local slugify 4 | slugify = require("sitegen.common").slugify 5 | local insert 6 | insert = table.insert 7 | local IndexerPlugin 8 | do 9 | local _class_0 10 | local _parent_0 = Plugin 11 | local _base_0 = { 12 | tpl_helpers = { 13 | "index" 14 | }, 15 | events = { 16 | ["page.content_rendered"] = function(self, e, page, content) 17 | if self.current_index[page] then 18 | return 19 | end 20 | if not (page.meta.index) then 21 | return 22 | end 23 | local body 24 | body, self.current_index[page] = self:parse_headers(content, page.meta.index) 25 | return page:set_content(body) 26 | end 27 | }, 28 | index_for_page = function(self, page) 29 | page:render() 30 | return self.current_index[page] 31 | end, 32 | index = function(self, page, arg) 33 | if page.meta.index == false then 34 | return "" 35 | end 36 | if not (self.current_index[page]) then 37 | assert(page.tpl_scope.render_source, "attempting to render index with no body available (are you in cosmo?)") 38 | arg = arg or { } 39 | setmetatable(arg, { 40 | __index = page.meta.index 41 | }) 42 | local body 43 | body, self.current_index[page] = self:parse_headers(page.tpl_scope.render_source, arg) 44 | coroutine.yield(body) 45 | end 46 | return self:render_index(self.current_index[page]) 47 | end, 48 | parse_headers = function(self, content, opts) 49 | if not (type(opts) == "table") then 50 | opts = { } 51 | end 52 | local min_depth = opts.min_depth or 1 53 | local max_depth = opts.max_depth or 9 54 | local link_headers = opts.link_headers 55 | local _slugify = opts.slugify or function(h) 56 | return slugify(h.title) 57 | end 58 | local headers = { } 59 | local current = headers 60 | local push_header 61 | push_header = function(i, header) 62 | i = tonumber(i) 63 | if not current.depth then 64 | current.depth = i 65 | else 66 | if i > current.depth then 67 | current = { 68 | parent = current, 69 | depth = i 70 | } 71 | else 72 | while i < current.depth and current.parent do 73 | insert(current.parent, current) 74 | current = current.parent 75 | end 76 | if i < current.depth then 77 | current.depth = i 78 | end 79 | end 80 | end 81 | return insert(current, header) 82 | end 83 | local replace_html 84 | replace_html = require("web_sanitize.query.scan_html").replace_html 85 | local out = replace_html(content, function(stack) 86 | local el = stack:current() 87 | local depth = el.tag:match("h(%d+)") 88 | if not (depth) then 89 | return 90 | end 91 | depth = tonumber(depth) 92 | if not (depth >= min_depth and depth <= max_depth) then 93 | return 94 | end 95 | local header = { 96 | title = el:inner_text(), 97 | html_content = el:inner_html() 98 | } 99 | header.slug = _slugify(header) 100 | push_header(depth, header) 101 | if current.parent then 102 | local last_parent = current.parent[#current.parent] 103 | header.slug = tostring(last_parent.slug) .. "/" .. tostring(header.slug) 104 | end 105 | if link_headers then 106 | local html = require("sitegen.html") 107 | return el:replace_inner_html(html.build(function() 108 | return a({ 109 | name = header.slug, 110 | href = "#" .. tostring(header.slug), 111 | raw(header.html_content) 112 | }) 113 | end)) 114 | else 115 | return el:replace_attributes({ 116 | id = header.slug 117 | }) 118 | end 119 | end) 120 | while current.parent do 121 | insert(current.parent, current) 122 | current = current.parent 123 | end 124 | return out, headers 125 | end, 126 | render_index = function(self, headers) 127 | local html = require("sitegen.html") 128 | return html.build(function() 129 | local render 130 | render = function(headers) 131 | return ul((function() 132 | local _accum_0 = { } 133 | local _len_0 = 1 134 | for _index_0 = 1, #headers do 135 | local item = headers[_index_0] 136 | if item.depth then 137 | _accum_0[_len_0] = render(item) 138 | else 139 | local title, slug, html_content 140 | title, slug, html_content = item.title, item.slug, item.html_content 141 | _accum_0[_len_0] = li({ 142 | a({ 143 | href = "#" .. tostring(slug), 144 | raw(html_content) 145 | }) 146 | }) 147 | end 148 | _len_0 = _len_0 + 1 149 | end 150 | return _accum_0 151 | end)()) 152 | end 153 | return render(headers) 154 | end) 155 | end 156 | } 157 | _base_0.__index = _base_0 158 | setmetatable(_base_0, _parent_0.__base) 159 | _class_0 = setmetatable({ 160 | __init = function(self, site) 161 | self.site = site 162 | _class_0.__parent.__init(self, self.site) 163 | self.current_index = { } 164 | end, 165 | __base = _base_0, 166 | __name = "IndexerPlugin", 167 | __parent = _parent_0 168 | }, { 169 | __index = function(cls, name) 170 | local val = rawget(_base_0, name) 171 | if val == nil then 172 | local parent = rawget(cls, "__parent") 173 | if parent then 174 | return parent[name] 175 | end 176 | else 177 | return val 178 | end 179 | end, 180 | __call = function(cls, ...) 181 | local _self_0 = setmetatable({}, _base_0) 182 | cls.__init(_self_0, ...) 183 | return _self_0 184 | end 185 | }) 186 | _base_0.__class = _class_0 187 | if _parent_0.__inherited then 188 | _parent_0.__inherited(_parent_0, _class_0) 189 | end 190 | IndexerPlugin = _class_0 191 | return _class_0 192 | end 193 | -------------------------------------------------------------------------------- /sitegen/plugins/indexer.moon: -------------------------------------------------------------------------------- 1 | import Plugin from require "sitegen.plugin" 2 | 3 | import slugify from require "sitegen.common" 4 | import insert from table 5 | 6 | class IndexerPlugin extends Plugin 7 | tpl_helpers: { "index" } 8 | 9 | events: { 10 | "page.content_rendered": (e, page, content) => 11 | return if @current_index[page] -- already added index 12 | return unless page.meta.index 13 | body, @current_index[page] = @parse_headers content, page.meta.index 14 | page\set_content body 15 | } 16 | 17 | new: (@site) => 18 | super @site 19 | @current_index = {} 20 | 21 | index_for_page: (page) => 22 | page\render! 23 | @current_index[page] 24 | 25 | -- renders index from within template 26 | index: (page, arg) => 27 | 28 | return "" if page.meta.index == false 29 | 30 | unless @current_index[page] 31 | assert page.tpl_scope.render_source, 32 | "attempting to render index with no body available (are you in cosmo?)" 33 | 34 | 35 | arg or= {} 36 | setmetatable arg, { 37 | __index: page.meta.index 38 | } 39 | 40 | body, @current_index[page] = @parse_headers page.tpl_scope.render_source, arg 41 | 42 | coroutine.yield body 43 | 44 | @render_index @current_index[page] 45 | 46 | parse_headers: (content, opts) => 47 | opts = {} unless type(opts) == "table" 48 | 49 | min_depth = opts.min_depth or 1 50 | max_depth = opts.max_depth or 9 51 | link_headers = opts.link_headers 52 | _slugify = opts.slugify or (h) -> slugify h.title 53 | 54 | headers = {} 55 | current = headers 56 | 57 | push_header = (i, header) -> 58 | i = tonumber i 59 | 60 | if not current.depth 61 | current.depth = i 62 | else 63 | if i > current.depth 64 | current = parent: current, depth: i 65 | else 66 | while i < current.depth and current.parent 67 | insert current.parent, current 68 | current = current.parent 69 | 70 | current.depth = i if i < current.depth 71 | 72 | insert current, header 73 | 74 | import replace_html from require "web_sanitize.query.scan_html" 75 | 76 | out = replace_html content, (stack) -> 77 | el = stack\current! 78 | depth = el.tag\match "h(%d+)" 79 | return unless depth 80 | depth = tonumber depth 81 | return unless depth >= min_depth and depth <= max_depth 82 | 83 | header = { 84 | title: el\inner_text! 85 | html_content: el\inner_html! 86 | } 87 | 88 | header.slug = _slugify header 89 | push_header depth, header 90 | 91 | -- add hierarchy to slug now that tree is built 92 | if current.parent 93 | last_parent = current.parent[#current.parent] 94 | header.slug = "#{last_parent.slug}/#{header.slug}" 95 | 96 | if link_headers 97 | html = require "sitegen.html" 98 | el\replace_inner_html html.build -> 99 | a { 100 | name: header.slug 101 | href: "##{header.slug}" 102 | raw header.html_content 103 | } 104 | else 105 | el\replace_attributes { 106 | id: header.slug 107 | } 108 | 109 | -- clean up 110 | while current.parent 111 | insert current.parent, current 112 | current = current.parent 113 | 114 | out, headers 115 | 116 | render_index: (headers) => 117 | html = require "sitegen.html" 118 | html.build -> 119 | render = (headers) -> 120 | ul for item in *headers 121 | if item.depth 122 | render item 123 | else 124 | {:title, :slug, :html_content} = item 125 | 126 | li { 127 | a { 128 | href: "##{slug}" 129 | raw html_content 130 | } 131 | } 132 | 133 | 134 | render headers 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /sitegen/plugins/pygments.lua: -------------------------------------------------------------------------------- 1 | local html = require("sitegen.html") 2 | local CacheTable 3 | CacheTable = require("sitegen.cache").CacheTable 4 | local trim_leading_white 5 | trim_leading_white = require("sitegen.common").trim_leading_white 6 | local Plugin 7 | Plugin = require("sitegen.plugin").Plugin 8 | local PygmentsPlugin 9 | do 10 | local _class_0 11 | local _parent_0 = Plugin 12 | local _base_0 = { 13 | custom_highlighters = { }, 14 | disable_indent_detect = false, 15 | highlight = function(self, lang, code) 16 | local fname = os.tmpname() 17 | do 18 | local _with_0 = io.open(fname, "w") 19 | _with_0:write(code) 20 | _with_0:close() 21 | end 22 | local p = io.popen(("pygmentize -f html -l %s %s"):format(lang, fname)) 23 | local out = p:read("*a") 24 | return assert(out:match('^
(.-)\n?
'), "Failed to parse pygmentize result, is pygments installed?") 25 | end, 26 | _highlight = function(self, lang, code, page) 27 | if page == nil then 28 | page = nil 29 | end 30 | local lang_cache = self.lang_cache:get(lang) 31 | local cached = lang_cache[code] 32 | local highlighted 33 | if cached then 34 | highlighted = cached 35 | else 36 | local out 37 | do 38 | local custom = self.custom_highlighters[lang] 39 | if custom then 40 | out = assert(custom(self, code, page), "custom highlighter " .. tostring(lang) .. " failed to return result") 41 | else 42 | out = self:pre_tag(self:highlight(lang, code), lang) 43 | end 44 | end 45 | lang_cache[code] = out 46 | highlighted = out 47 | end 48 | self.keep_cache:get(lang):set(code, highlighted) 49 | return highlighted 50 | end, 51 | pre_tag = function(self, html_code, lang) 52 | if lang == nil then 53 | lang = "text" 54 | end 55 | return html.build(function() 56 | return pre({ 57 | __breakclose = true, 58 | class = "highlight lang_" .. lang, 59 | code({ 60 | raw(html_code) 61 | }) 62 | }) 63 | end) 64 | end, 65 | filter = function(self, text, page) 66 | local lpeg = require("lpeg") 67 | local P, R, S, Cs, Cmt, C, Cg, Cb 68 | P, R, S, Cs, Cmt, C, Cg, Cb = lpeg.P, lpeg.R, lpeg.S, lpeg.Cs, lpeg.Cmt, lpeg.C, lpeg.Cg, lpeg.Cb 69 | local delim = P("```") 70 | local white = S(" \t") ^ 0 71 | local nl = P("\n") 72 | local check_indent = Cmt(C(white) * Cb("indent"), function(body, pos, white, prev) 73 | if prev ~= "" and self.disable_indent_detect then 74 | return false 75 | end 76 | return white == prev 77 | end) 78 | local start_line = Cg(white, "indent") * delim * C(R("az", "AZ") ^ 1) * nl 79 | local end_line = check_indent * delim * (#nl + -1) 80 | local code_block = start_line * C((1 - end_line) ^ 0) * end_line 81 | code_block = code_block * Cb("indent") / function(lang, body, indent) 82 | if indent ~= "" then 83 | body = trim_leading_white(body, indent) 84 | end 85 | return assert(self:_highlight(lang, body, page), "failed to highlight " .. tostring(lang) .. " code\n\n" .. tostring(body)) 86 | end 87 | local parse_cosmo 88 | parse_cosmo = require("sitegen.renderers.markdown").parse_cosmo 89 | local cosmo_pattern = parse_cosmo() 90 | local document = Cs(code_block ^ -1 * (nl * code_block + cosmo_pattern + 1) ^ 0 * -1) 91 | return assert(document:match(text)) 92 | end 93 | } 94 | _base_0.__index = _base_0 95 | setmetatable(_base_0, _parent_0.__base) 96 | _class_0 = setmetatable({ 97 | __init = function(self, site) 98 | self.site = site 99 | self.lang_cache = self.site.cache:get("highlight") 100 | self.keep_cache = CacheTable() 101 | table.insert(self.site.cache.finalize, function() 102 | return self.site.cache:set("highlight", self.keep_cache) 103 | end) 104 | return self.site.events:on("renderer.markdown.pre_render", function(event, page, md_source) 105 | return page, self:filter(md_source, page) 106 | end) 107 | end, 108 | __base = _base_0, 109 | __name = "PygmentsPlugin", 110 | __parent = _parent_0 111 | }, { 112 | __index = function(cls, name) 113 | local val = rawget(_base_0, name) 114 | if val == nil then 115 | local parent = rawget(cls, "__parent") 116 | if parent then 117 | return parent[name] 118 | end 119 | else 120 | return val 121 | end 122 | end, 123 | __call = function(cls, ...) 124 | local _self_0 = setmetatable({}, _base_0) 125 | cls.__init(_self_0, ...) 126 | return _self_0 127 | end 128 | }) 129 | _base_0.__class = _class_0 130 | if _parent_0.__inherited then 131 | _parent_0.__inherited(_parent_0, _class_0) 132 | end 133 | PygmentsPlugin = _class_0 134 | return _class_0 135 | end 136 | -------------------------------------------------------------------------------- /sitegen/plugins/pygments.moon: -------------------------------------------------------------------------------- 1 | 2 | html = require "sitegen.html" 3 | import CacheTable from require "sitegen.cache" 4 | import trim_leading_white from require "sitegen.common" 5 | 6 | import Plugin from require "sitegen.plugin" 7 | 8 | -- 9 | -- Specify code to highlight using ````lang, eg. 10 | -- 11 | -- 12 | -- ```lua 13 | -- print "hello world" 14 | -- ``` 15 | -- 16 | class PygmentsPlugin extends Plugin 17 | custom_highlighters: {} 18 | disable_indent_detect: false 19 | 20 | -- highlight code with pygments 21 | highlight: (lang, code) => 22 | fname = os.tmpname! 23 | with io.open fname, "w" 24 | \write code 25 | \close! 26 | 27 | p = io.popen ("pygmentize -f html -l %s %s")\format lang, fname 28 | out = p\read"*a" 29 | 30 | -- get rid of the div and pre inserted by pygments 31 | assert out\match('^
(.-)\n?
'), 32 | "Failed to parse pygmentize result, is pygments installed?" 33 | 34 | -- checks cache and custom highlighters 35 | _highlight: (lang, code, page=nil) => 36 | lang_cache = @lang_cache\get lang 37 | cached = lang_cache[code] 38 | highlighted = if cached 39 | cached 40 | else 41 | out = if custom = @custom_highlighters[lang] 42 | assert custom(@, code, page), 43 | "custom highlighter #{lang} failed to return result" 44 | else 45 | @pre_tag @highlight(lang, code), lang 46 | 47 | lang_cache[code] = out 48 | out 49 | 50 | @keep_cache\get(lang)\set code, highlighted 51 | highlighted 52 | 53 | pre_tag: (html_code, lang="text") => 54 | html.build -> pre { 55 | __breakclose: true 56 | class: "highlight lang_"..lang 57 | code { raw html_code } 58 | } 59 | 60 | 61 | filter: (text, page) => 62 | lpeg = require "lpeg" 63 | import P, R, S, Cs, Cmt, C, Cg, Cb from lpeg 64 | 65 | delim = P"```" 66 | white = S" \t"^0 67 | nl = P"\n" 68 | 69 | check_indent = Cmt C(white) * Cb"indent", (body, pos, white, prev) -> 70 | return false if prev != "" and @disable_indent_detect 71 | white == prev 72 | 73 | start_line = Cg(white, "indent") * delim * C(R("az", "AZ")^1) * nl 74 | end_line = check_indent * delim * (#nl + -1) 75 | 76 | code_block = start_line * C((1 - end_line)^0) * end_line 77 | code_block = code_block * Cb"indent" / (lang, body, indent) -> 78 | if indent != "" 79 | body = trim_leading_white body, indent 80 | assert @_highlight(lang, body, page), 81 | "failed to highlight #{lang} code\n\n#{body}" 82 | 83 | import parse_cosmo from require "sitegen.renderers.markdown" 84 | cosmo_pattern = parse_cosmo! 85 | 86 | -- normally a code block must start at the beginning of a new line, but 87 | -- there is no new line at the beginning of the file so we have a special 88 | -- case for that 89 | document = Cs code_block^-1 * (nl * code_block + cosmo_pattern + 1)^0 * -1 90 | 91 | assert document\match text 92 | 93 | new: (@site) => 94 | @lang_cache = @site.cache\get"highlight" 95 | @keep_cache = CacheTable! 96 | 97 | table.insert @site.cache.finalize, -> 98 | @site.cache\set "highlight", @keep_cache 99 | 100 | @site.events\on "renderer.markdown.pre_render", (event, page, md_source) -> 101 | page, @filter md_source, page 102 | 103 | -------------------------------------------------------------------------------- /sitegen/plugins/syntaxhighlight.lua: -------------------------------------------------------------------------------- 1 | local html = require("sitegen.html") 2 | local Plugin 3 | Plugin = require("sitegen.plugin").Plugin 4 | local trim_leading_white 5 | trim_leading_white = require("sitegen.common").trim_leading_white 6 | local SyntaxhighlightPlugin 7 | do 8 | local _class_0 9 | local _parent_0 = Plugin 10 | local _base_0 = { 11 | before_highlight = { }, 12 | disable_indent_detect = false, 13 | ignore_missing_lexer = true, 14 | language_aliases = { 15 | moon = "moonscript", 16 | erb = "rhtml" 17 | }, 18 | highlight = function(self, lang, code) 19 | local syntaxhighlight = require("syntaxhighlight") 20 | lang = self.language_aliases[lang] or lang 21 | if self.ignore_missing_lexer and not syntaxhighlight.lexers[lang] then 22 | if self.site then 23 | self.site.logger:warn("Failed to find syntax highlighter for: " .. tostring(lang)) 24 | end 25 | return html.escape(code) 26 | end 27 | local out = assert(syntaxhighlight.highlight_to_html(lang, code, { 28 | bare = true 29 | })) 30 | return (out:gsub("\n$", "")) 31 | end, 32 | _highlight = function(self, lang, code, page) 33 | if page == nil then 34 | page = nil 35 | end 36 | code = code:gsub("\r?\n$", ""):gsub("^\r?\n", "") 37 | do 38 | local fn = self.before_highlight[lang] 39 | if fn then 40 | assert(fn(self, code, page)) 41 | end 42 | end 43 | return self:pre_tag(self:highlight(lang, code), lang) 44 | end, 45 | pre_tag = function(self, html_code, lang) 46 | if lang == nil then 47 | lang = "text" 48 | end 49 | return html.build(function() 50 | return pre({ 51 | __breakclose = true, 52 | class = "highlight lang_" .. lang, 53 | code({ 54 | raw(html_code) 55 | }) 56 | }) 57 | end) 58 | end, 59 | filter = function(self, text, page) 60 | local lpeg = require("lpeg") 61 | local P, R, S, Cs, Cmt, C, Cg, Cb 62 | P, R, S, Cs, Cmt, C, Cg, Cb = lpeg.P, lpeg.R, lpeg.S, lpeg.Cs, lpeg.Cmt, lpeg.C, lpeg.Cg, lpeg.Cb 63 | local delim = P("```") 64 | local white = S(" \t") ^ 0 65 | local nl = P("\n") 66 | local check_indent = Cmt(C(white) * Cb("indent"), function(body, pos, white, prev) 67 | if prev ~= "" and self.disable_indent_detect then 68 | return false 69 | end 70 | return white == prev 71 | end) 72 | local start_line = Cg(white, "indent") * delim * C(R("az", "AZ") ^ 1) * nl 73 | local end_line = check_indent * delim * (#nl + -1) 74 | local code_block = start_line * C((1 - end_line) ^ 0) * end_line 75 | code_block = code_block * Cb("indent") / function(lang, body, indent) 76 | if indent ~= "" then 77 | body = trim_leading_white(body, indent) 78 | end 79 | return assert(self:_highlight(lang, body, page), "failed to highlight " .. tostring(lang) .. " code\n\n" .. tostring(body)) 80 | end 81 | local parse_cosmo 82 | parse_cosmo = require("sitegen.renderers.markdown").parse_cosmo 83 | local cosmo_pattern = parse_cosmo() 84 | local document = Cs(code_block ^ -1 * (nl * code_block + cosmo_pattern + 1) ^ 0) * -1 85 | return assert(document:match(text), "failed to parse string for syntax highlight") 86 | end 87 | } 88 | _base_0.__index = _base_0 89 | setmetatable(_base_0, _parent_0.__base) 90 | _class_0 = setmetatable({ 91 | __init = function(self, site) 92 | self.site = site 93 | return self.site.events:on("renderer.markdown.pre_render", function(event, page, md_source) 94 | return page, self:filter(md_source, page) 95 | end) 96 | end, 97 | __base = _base_0, 98 | __name = "SyntaxhighlightPlugin", 99 | __parent = _parent_0 100 | }, { 101 | __index = function(cls, name) 102 | local val = rawget(_base_0, name) 103 | if val == nil then 104 | local parent = rawget(cls, "__parent") 105 | if parent then 106 | return parent[name] 107 | end 108 | else 109 | return val 110 | end 111 | end, 112 | __call = function(cls, ...) 113 | local _self_0 = setmetatable({}, _base_0) 114 | cls.__init(_self_0, ...) 115 | return _self_0 116 | end 117 | }) 118 | _base_0.__class = _class_0 119 | if _parent_0.__inherited then 120 | _parent_0.__inherited(_parent_0, _class_0) 121 | end 122 | SyntaxhighlightPlugin = _class_0 123 | return _class_0 124 | end 125 | -------------------------------------------------------------------------------- /sitegen/plugins/syntaxhighlight.moon: -------------------------------------------------------------------------------- 1 | html = require "sitegen.html" 2 | import Plugin from require "sitegen.plugin" 3 | 4 | import trim_leading_white from require "sitegen.common" 5 | 6 | -- 7 | -- Specify code to highlight using ````lang, eg. 8 | -- 9 | -- 10 | -- ```lua 11 | -- print "hello world" 12 | -- ``` 13 | -- 14 | class SyntaxhighlightPlugin extends Plugin 15 | before_highlight: {} 16 | disable_indent_detect: false 17 | ignore_missing_lexer: true 18 | 19 | -- allow remapping language names 20 | language_aliases: { 21 | moon: "moonscript" 22 | erb: "rhtml" 23 | } 24 | 25 | -- highlight code with syntaxhighlight library https://github.com/leafo/lua-syntaxhighlight 26 | highlight: (lang, code) => 27 | syntaxhighlight = require "syntaxhighlight" 28 | 29 | lang = @language_aliases[lang] or lang 30 | 31 | -- pass through must be html escaped 32 | if @ignore_missing_lexer and not syntaxhighlight.lexers[lang] 33 | if @site 34 | @site.logger\warn "Failed to find syntax highlighter for: #{lang}" 35 | 36 | return html.escape code 37 | 38 | out = assert syntaxhighlight.highlight_to_html lang, code, { 39 | bare: true 40 | } 41 | 42 | (out\gsub "\n$", "") 43 | 44 | -- checks custom highlighters 45 | _highlight: (lang, code, page=nil) => 46 | -- strip extra newlines 47 | code = code\gsub("\r?\n$", "")\gsub("^\r?\n", "") 48 | 49 | if fn = @before_highlight[lang] 50 | assert fn @, code, page 51 | 52 | @pre_tag @highlight(lang, code), lang 53 | 54 | pre_tag: (html_code, lang="text") => 55 | html.build -> pre { 56 | __breakclose: true 57 | class: "highlight lang_"..lang 58 | code { raw html_code } 59 | } 60 | 61 | filter: (text, page) => 62 | lpeg = require "lpeg" 63 | import P, R, S, Cs, Cmt, C, Cg, Cb from lpeg 64 | 65 | delim = P"```" 66 | white = S" \t"^0 67 | nl = P"\n" 68 | 69 | check_indent = Cmt C(white) * Cb"indent", (body, pos, white, prev) -> 70 | return false if prev != "" and @disable_indent_detect 71 | white == prev 72 | 73 | start_line = Cg(white, "indent") * delim * C(R("az", "AZ")^1) * nl 74 | end_line = check_indent * delim * (#nl + -1) 75 | 76 | code_block = start_line * C((1 - end_line)^0) * end_line 77 | code_block = code_block * Cb"indent" / (lang, body, indent) -> 78 | if indent != "" 79 | body = trim_leading_white body, indent 80 | assert @_highlight(lang, body, page), 81 | "failed to highlight #{lang} code\n\n#{body}" 82 | 83 | import parse_cosmo from require "sitegen.renderers.markdown" 84 | cosmo_pattern = parse_cosmo! 85 | 86 | -- normally a code block must start at the beginning of a new line, but 87 | -- there is no new line at the beginning of the file so we have a special 88 | -- case for that 89 | document = Cs(code_block^-1 * (nl * code_block + cosmo_pattern + 1)^0) * -1 90 | 91 | assert document\match(text), "failed to parse string for syntax highlight" 92 | 93 | new: (@site) => 94 | @site.events\on "renderer.markdown.pre_render", 95 | (event, page, md_source) -> 96 | page, @filter md_source, page 97 | 98 | -------------------------------------------------------------------------------- /sitegen/plugins/tupfile.lua: -------------------------------------------------------------------------------- 1 | local Plugin 2 | Plugin = require("sitegen.plugin").Plugin 3 | local Path = require("sitegen.path") 4 | local TupfilePlugin 5 | do 6 | local _class_0 7 | local _parent_0 = Plugin 8 | local _base_0 = { 9 | command_actions = { 10 | { 11 | method = "generate_tupfile", 12 | argparser = function(command) 13 | do 14 | local _with_0 = command 15 | _with_0:summary("Generate a tupfile for building the site") 16 | return _with_0 17 | end 18 | end 19 | } 20 | }, 21 | generate_tupfile = function(self, args) 22 | local output_lines = { } 23 | local _list_0 = self.site:load_pages() 24 | for _index_0 = 1, #_list_0 do 25 | local page = _list_0[_index_0] 26 | local source = self.site.io.full_path(page.source) 27 | local target = self.site.io.full_path(page.target) 28 | table.insert(output_lines, ": " .. tostring(source) .. " |> sitegen build %f |> " .. tostring(target)) 29 | end 30 | local _list_1 = self.site.scope.builds 31 | for _index_0 = 1, #_list_1 do 32 | local buildset = _list_1[_index_0] 33 | require("moon").p(buildset) 34 | end 35 | for path in self.site.scope.copy_files:each() do 36 | local target = Path.join(self.site.config.out_dir, path) 37 | table.insert(output_lines, ": " .. tostring(path) .. " |> cp %f %o |> " .. tostring(target)) 38 | end 39 | return print(table.concat(output_lines, "\n")) 40 | end 41 | } 42 | _base_0.__index = _base_0 43 | setmetatable(_base_0, _parent_0.__base) 44 | _class_0 = setmetatable({ 45 | __init = function(self, site) 46 | self.site = site 47 | end, 48 | __base = _base_0, 49 | __name = "TupfilePlugin", 50 | __parent = _parent_0 51 | }, { 52 | __index = function(cls, name) 53 | local val = rawget(_base_0, name) 54 | if val == nil then 55 | local parent = rawget(cls, "__parent") 56 | if parent then 57 | return parent[name] 58 | end 59 | else 60 | return val 61 | end 62 | end, 63 | __call = function(cls, ...) 64 | local _self_0 = setmetatable({}, _base_0) 65 | cls.__init(_self_0, ...) 66 | return _self_0 67 | end 68 | }) 69 | _base_0.__class = _class_0 70 | if _parent_0.__inherited then 71 | _parent_0.__inherited(_parent_0, _class_0) 72 | end 73 | TupfilePlugin = _class_0 74 | return _class_0 75 | end 76 | -------------------------------------------------------------------------------- /sitegen/plugins/tupfile.moon: -------------------------------------------------------------------------------- 1 | -- This is an experimental plugin that is able to generate a tupfile for 2 | -- building the site incrementally in a more reliable way than watch mode 3 | 4 | -- TODO: also need to add a clean command to clean up generated files so we can easily migrate to tup where possible 5 | 6 | import Plugin from require "sitegen.plugin" 7 | Path = require "sitegen.path" 8 | 9 | class TupfilePlugin extends Plugin 10 | command_actions: { 11 | { 12 | method: "generate_tupfile" 13 | argparser: (command) -> 14 | with command 15 | \summary "Generate a tupfile for building the site" 16 | } 17 | } 18 | 19 | new: (@site) => 20 | 21 | generate_tupfile: (args) => 22 | output_lines = {} 23 | 24 | -- this needs to implement everything from Site:write 25 | -- [ ] render pages 26 | -- [ ] run build commands 27 | -- [ ] run copy commands 28 | -- [ ] execute plugin writes 29 | 30 | for page in *@site\load_pages! 31 | source = @site.io.full_path page.source 32 | target = @site.io.full_path page.target 33 | 34 | table.insert output_lines, ": #{source} |> sitegen build %f |> #{target}" 35 | 36 | for buildset in *@site.scope.builds 37 | -- TODO: the build function might be destructive, so we need to have a 38 | -- way to detect the common pattern of using a system tool to convert it 39 | -- into a command within the tupfile 40 | require("moon").p buildset 41 | 42 | for path in @site.scope.copy_files\each! 43 | target = Path.join @site.config.out_dir, path 44 | table.insert output_lines, ": #{path} |> cp %f %o |> #{target}" 45 | 46 | print table.concat output_lines, "\n" 47 | 48 | 49 | -------------------------------------------------------------------------------- /sitegen/query.lua: -------------------------------------------------------------------------------- 1 | local array_includes 2 | array_includes = function(array, val) 3 | if array == val then 4 | return true 5 | end 6 | if not (type(array) == "table") then 7 | return false 8 | end 9 | for _index_0 = 1, #array do 10 | local array_val = array[_index_0] 11 | if array_val == val then 12 | return true 13 | end 14 | end 15 | return false 16 | end 17 | local query_page_match 18 | query_page_match = function(page, query) 19 | if not query or not next(query) then 20 | return true 21 | end 22 | for k, query_val in pairs(query) do 23 | local page_val = page.meta[k] 24 | if type(query_val) == "function" then 25 | if not (query_val(page_val)) then 26 | return false 27 | end 28 | else 29 | if not (page_val == query_val) then 30 | return false 31 | end 32 | end 33 | end 34 | return true 35 | end 36 | local query_pages 37 | query_pages = function(pages, query, opts) 38 | if query == nil then 39 | query = { } 40 | end 41 | if opts == nil then 42 | opts = { } 43 | end 44 | local out 45 | do 46 | local _accum_0 = { } 47 | local _len_0 = 1 48 | for _index_0 = 1, #pages do 49 | local _continue_0 = false 50 | repeat 51 | local page = pages[_index_0] 52 | if not (query_page_match(page, query)) then 53 | _continue_0 = true 54 | break 55 | end 56 | local _value_0 = page 57 | _accum_0[_len_0] = _value_0 58 | _len_0 = _len_0 + 1 59 | _continue_0 = true 60 | until true 61 | if not _continue_0 then 62 | break 63 | end 64 | end 65 | out = _accum_0 66 | end 67 | if opts.sort then 68 | table.sort(out, opts.sort) 69 | end 70 | return out 71 | end 72 | local cmp = { 73 | date = function(dir) 74 | if dir == nil then 75 | dir = "desc" 76 | end 77 | local date = require("date") 78 | return function(a, b) 79 | if dir == "asc" then 80 | return date(a) < date(b) 81 | else 82 | return date(a) > date(b) 83 | end 84 | end 85 | end 86 | } 87 | local filter = { 88 | contains = function(val) 89 | return function(page_val) 90 | return array_includes(page_val, val) 91 | end 92 | end, 93 | is_set = function() 94 | return function(page_val) 95 | return page_val ~= nil 96 | end 97 | end 98 | } 99 | local sort = { 100 | date = function(key, dir) 101 | if key == nil then 102 | key = "date" 103 | end 104 | return function(p1, p2) 105 | return cmp.date(dir)(p1.meta[key], p2.meta[key]) 106 | end 107 | end 108 | } 109 | return { 110 | query_pages = query_pages, 111 | cmp = cmp, 112 | filter = filter, 113 | sort = sort 114 | } 115 | -------------------------------------------------------------------------------- /sitegen/query.moon: -------------------------------------------------------------------------------- 1 | 2 | array_includes = (array, val) -> 3 | return true if array == val 4 | return false unless type(array) == "table" 5 | 6 | for array_val in *array 7 | return true if array_val == val 8 | 9 | false 10 | 11 | query_page_match = (page, query) -> 12 | -- empty query matches all 13 | return true if not query or not next query 14 | 15 | for k, query_val in pairs query 16 | page_val = page.meta[k] 17 | if type(query_val) == "function" 18 | return false unless query_val page_val 19 | else 20 | return false unless page_val == query_val 21 | 22 | true 23 | 24 | query_pages = (pages, query={}, opts={}) -> 25 | out = for page in *pages 26 | continue unless query_page_match page, query 27 | page 28 | 29 | table.sort out, opts.sort if opts.sort 30 | out 31 | 32 | cmp = { 33 | date: (dir="desc") -> 34 | date = require "date" 35 | (a, b) -> 36 | if dir == "asc" 37 | date(a) < date(b) 38 | else 39 | date(a) > date(b) 40 | } 41 | 42 | filter = { 43 | -- sees if value is argument, or value contains argument 44 | -- { tag: contains "hello" } --> tag == "hello", tag = {"hello", ...} 45 | contains: (val) -> 46 | (page_val) -> array_includes page_val, val 47 | 48 | -- sees if key is set 49 | is_set: -> 50 | (page_val) -> page_val != nil 51 | } 52 | 53 | sort = { 54 | date: (key="date", dir) -> 55 | (p1, p2) -> 56 | cmp.date(dir) p1.meta[key], p2.meta[key] 57 | } 58 | 59 | { :query_pages, :cmp, :filter, :sort } 60 | -------------------------------------------------------------------------------- /sitegen/renderer.lua: -------------------------------------------------------------------------------- 1 | local Renderer 2 | do 3 | local _class_0 4 | local _base_0 = { 5 | extract_header = function(self, text) 6 | local extract_header 7 | extract_header = require("sitegen.header").extract_header 8 | return extract_header(text) 9 | end, 10 | can_load = function(self, fname) 11 | if not (self.source_ext) then 12 | return nil 13 | end 14 | local convert_pattern 15 | convert_pattern = require("sitegen.common").convert_pattern 16 | local pattern = convert_pattern("*." .. tostring(self.source_ext) .. "$") 17 | return not not fname:match(pattern) 18 | end, 19 | load = function(self, source) 20 | local content, meta = self:extract_header(source) 21 | return (function() 22 | return content 23 | end), meta 24 | end 25 | } 26 | _base_0.__index = _base_0 27 | _class_0 = setmetatable({ 28 | __init = function(self, site) 29 | self.site = site 30 | end, 31 | __base = _base_0, 32 | __name = "Renderer" 33 | }, { 34 | __index = _base_0, 35 | __call = function(cls, ...) 36 | local _self_0 = setmetatable({}, _base_0) 37 | cls.__init(_self_0, ...) 38 | return _self_0 39 | end 40 | }) 41 | _base_0.__class = _class_0 42 | Renderer = _class_0 43 | end 44 | return { 45 | Renderer = Renderer 46 | } 47 | -------------------------------------------------------------------------------- /sitegen/renderer.moon: -------------------------------------------------------------------------------- 1 | 2 | -- A renderer is reponsible for loading a template source file, extracting any 3 | -- metadata, and providing a function to render the template to string 4 | 5 | class Renderer 6 | new: (@site) => 7 | 8 | extract_header: (text) => 9 | import extract_header from require "sitegen.header" 10 | extract_header text 11 | 12 | can_load: (fname) => 13 | return nil unless @source_ext 14 | import convert_pattern from require "sitegen.common" 15 | pattern = convert_pattern "*.#{@source_ext}$" 16 | not not fname\match pattern 17 | 18 | -- if the source can be loaded, returns a function that takes render context 19 | -- to render, and the parsed header 20 | load: (source) => 21 | content, meta = @extract_header source 22 | (-> content), meta 23 | 24 | { 25 | :Renderer 26 | } 27 | -------------------------------------------------------------------------------- /sitegen/renderers/html.moon: -------------------------------------------------------------------------------- 1 | import Renderer from require "sitegen.renderer" 2 | 3 | cosmo = require "sitegen.cosmo" 4 | 5 | import extend from require "moon" 6 | 7 | import 8 | fill_ignoring_pre 9 | throw_error 10 | flatten_args 11 | pass_error 12 | unpack 13 | from require "sitegen.common" 14 | 15 | -- template_helpers can yield if they decide to change the 16 | -- entire source. This triggers the render to happen again 17 | -- with the updated tpl_scope. 18 | -- see indexer 19 | render_until_complete = (tpl_scope, render_fn, reset_fn) -> 20 | out = nil 21 | while true 22 | reset_fn! 23 | co = coroutine.create -> 24 | out = render_fn! 25 | nil 26 | 27 | _, altered_source = assert coroutine.resume co 28 | pass_error altered_source 29 | 30 | if altered_source 31 | tpl_scope.render_source = altered_source 32 | else 33 | break 34 | out 35 | 36 | -- reads the raw content, runs cosmo on the result 37 | class HTMLRenderer extends Renderer 38 | source_ext: "html" 39 | ext: "html" 40 | 41 | -- all of these receive the page as the first argument, not the renderer instance 42 | cosmo_helpers: { 43 | render: (args) => -- render another page in current scope 44 | name = assert unpack(args), "missing template name for render" 45 | 46 | templates = @site\Templates! 47 | templates.search_dir = "." 48 | templates.defaults = {} 49 | 50 | assert(templates\find_by_name(args[1]), "failed to find template: #{name}") @ 51 | 52 | markdown: (args) => 53 | md = @site\get_renderer "sitegen.renderers.markdown" 54 | md\render @, assert args and args[1], "missing markdown string" 55 | 56 | wrap: (args) => 57 | tpl_name = unpack args 58 | throw_error "missing template name" if not tpl_name 59 | @template_stack\push tpl_name 60 | "" 61 | 62 | neq: (args) => 63 | if args[1] != args[2] 64 | cosmo.yield {} 65 | else 66 | cosmo.yield _template: 2 67 | nil 68 | 69 | eq: (args) => 70 | if args[1] == args[2] 71 | cosmo.yield {} 72 | else 73 | cosmo.yield _template: 2 74 | nil 75 | 76 | if: (args) => 77 | if @tpl_scope[args[1]] 78 | cosmo.yield {} 79 | nil 80 | 81 | unless: (args) => 82 | unless @tpl_scope[args[1]] 83 | cosmo.yield {} 84 | nil 85 | 86 | each: (args) => 87 | list, name = unpack args 88 | if list 89 | list = flatten_args list 90 | for item in *list 91 | cosmo.yield { [(name)]: item } 92 | nil 93 | 94 | is_page: (args) => 95 | page_pattern = unpack args 96 | 97 | if type(page_pattern) == "string" 98 | cosmo.yield {} if @source\match page_pattern 99 | else 100 | import query_pages from require "sitegen.query" 101 | if unpack query_pages {@}, args 102 | cosmo.yield {} 103 | 104 | nil 105 | 106 | query_pages: (query) => 107 | import query_pages from require "sitegen.query" 108 | for page in *query_pages @site.pages, query 109 | -- cosmo helpers should already be available from parent scope 110 | cosmo.yield page\get_tpl_scope! 111 | nil 112 | 113 | query_page: (query) => 114 | import query_pages from require "sitegen.query" 115 | res = query_pages @site.pages, query 116 | assert #res == 1, "expected to find one page for `query_page`, found #{#res}" 117 | cosmo.yield res[1]\get_tpl_scope! 118 | nil 119 | 120 | url_for: (query) => 121 | import query_pages from require "sitegen.query" 122 | res = query_pages @site.pages, query 123 | if #res == 0 124 | error "failed to find any pages matching: #{require("moon").dump query}" 125 | elseif #res > 1 126 | error "found more than 1 page matching: #{require("moon").dump query}" 127 | else 128 | "#{@tpl_scope.root}/#{res[1]\url_for!}" 129 | } 130 | 131 | helpers: (page) => 132 | extend {}, 133 | { k, ((...) -> v page, ...) for k,v in pairs @cosmo_helpers}, 134 | page.tpl_scope 135 | 136 | render: (page, html_source) => 137 | cosmo_scope = @helpers page 138 | old_render_source = page.tpl_scope.render_source 139 | page.tpl_scope.render_source = html_source 140 | 141 | -- stack size is remembered so re-renders don't continue to grow the 142 | -- template stack 143 | init_stack = #page.template_stack 144 | 145 | out = render_until_complete page.tpl_scope, 146 | (-> fill_ignoring_pre page.tpl_scope.render_source, cosmo_scope), 147 | (-> while #page.template_stack > init_stack do page.template_stack\pop!) 148 | 149 | page.tpl_scope.render_source = old_render_source 150 | out 151 | 152 | load: (source) => 153 | content_fn, meta = super source 154 | ((page) -> @render page, content_fn!), meta 155 | 156 | -------------------------------------------------------------------------------- /sitegen/renderers/lapis.lua: -------------------------------------------------------------------------------- 1 | local Renderer 2 | Renderer = require("sitegen.renderer").Renderer 3 | local moonscript = require("moonscript.base") 4 | local LapisRenderer 5 | do 6 | local _class_0 7 | local _parent_0 = Renderer 8 | local _base_0 = { 9 | source_ext = "moon", 10 | ext = "html", 11 | load = function(self, source, fname) 12 | local chunk_name 13 | if fname then 14 | chunk_name = "@" .. tostring(fname) 15 | end 16 | local fn = assert(moonscript.loadstring(source, chunk_name)) 17 | local widget = fn() 18 | return (function(page) 19 | local w = widget({ 20 | page = page, 21 | site = page.site 22 | }) 23 | w:include_helper(page.tpl_scope) 24 | return w:render_to_string() 25 | end), widget.options 26 | end 27 | } 28 | _base_0.__index = _base_0 29 | setmetatable(_base_0, _parent_0.__base) 30 | _class_0 = setmetatable({ 31 | __init = function(self, ...) 32 | return _class_0.__parent.__init(self, ...) 33 | end, 34 | __base = _base_0, 35 | __name = "LapisRenderer", 36 | __parent = _parent_0 37 | }, { 38 | __index = function(cls, name) 39 | local val = rawget(_base_0, name) 40 | if val == nil then 41 | local parent = rawget(cls, "__parent") 42 | if parent then 43 | return parent[name] 44 | end 45 | else 46 | return val 47 | end 48 | end, 49 | __call = function(cls, ...) 50 | local _self_0 = setmetatable({}, _base_0) 51 | cls.__init(_self_0, ...) 52 | return _self_0 53 | end 54 | }) 55 | _base_0.__class = _class_0 56 | if _parent_0.__inherited then 57 | _parent_0.__inherited(_parent_0, _class_0) 58 | end 59 | LapisRenderer = _class_0 60 | return _class_0 61 | end 62 | -------------------------------------------------------------------------------- /sitegen/renderers/lapis.moon: -------------------------------------------------------------------------------- 1 | import Renderer from require "sitegen.renderer" 2 | 3 | moonscript = require "moonscript.base" 4 | 5 | class LapisRenderer extends Renderer 6 | source_ext: "moon" 7 | ext: "html" 8 | 9 | load: (source, fname) => 10 | chunk_name = if fname 11 | "@#{fname}" 12 | 13 | fn = assert moonscript.loadstring source, chunk_name 14 | widget = fn! 15 | ((page) -> 16 | w = widget(:page, site: page.site) 17 | w\include_helper page.tpl_scope 18 | w\render_to_string!), widget.options 19 | -------------------------------------------------------------------------------- /sitegen/renderers/markdown.lua: -------------------------------------------------------------------------------- 1 | local Renderer 2 | Renderer = require("sitegen.renderer").Renderer 3 | local dollar_temp = "0000sitegen_markdown00dollar0000" 4 | local simple_string 5 | simple_string = function(delim) 6 | local P 7 | P = require("lpeg").P 8 | local inner = P("\\" .. tostring(delim)) + "\\\\" + (1 - P(delim)) 9 | inner = inner ^ 0 10 | return P(delim) * inner * P(delim) 11 | end 12 | local lua_string 13 | lua_string = function() 14 | local P, C, Cmt, Cb, Cg 15 | do 16 | local _obj_0 = require("lpeg") 17 | P, C, Cmt, Cb, Cg = _obj_0.P, _obj_0.C, _obj_0.Cmt, _obj_0.Cb, _obj_0.Cg 18 | end 19 | local check_lua_string 20 | check_lua_string = function(str, pos, right, left) 21 | return #left == #right 22 | end 23 | local string_open = P("[") * P("=") ^ 0 * "[" 24 | local string_close = P("]") * P("=") ^ 0 * "]" 25 | local valid_close = Cmt(C(string_close) * Cb("string_open"), check_lua_string) 26 | return Cg(string_open, "string_open") * (1 - valid_close) ^ 0 * string_close 27 | end 28 | local parse_cosmo 29 | parse_cosmo = function() 30 | local P, R, Cmt, Cs, V 31 | do 32 | local _obj_0 = require("lpeg") 33 | P, R, Cmt, Cs, V = _obj_0.P, _obj_0.R, _obj_0.Cmt, _obj_0.Cs, _obj_0.V 34 | end 35 | local curly = P({ 36 | P("{") * (simple_string("'") + simple_string('"') + lua_string() + V(1) + (P(1) - "}")) ^ 0 * P("}") 37 | }) 38 | local alphanum = R("az", "AZ", "09", "__") 39 | return P("$") * alphanum ^ 1 * (curly) ^ -1 40 | end 41 | local escape_cosmo 42 | escape_cosmo = function(str) 43 | local escapes = { } 44 | local P, R, Cmt, Cs, V 45 | do 46 | local _obj_0 = require("lpeg") 47 | P, R, Cmt, Cs, V = _obj_0.P, _obj_0.R, _obj_0.Cmt, _obj_0.Cs, _obj_0.V 48 | end 49 | local counter = 0 50 | local cosmo = parse_cosmo() / function(tpl) 51 | counter = counter + 1 52 | local key = tostring(dollar_temp) .. "." .. tostring(counter) 53 | escapes[key] = tpl 54 | return key 55 | end 56 | local patt = Cs((cosmo + P(1)) ^ 0 * P(-1)) 57 | str = patt:match(str) or str, escapes 58 | return str, escapes 59 | end 60 | local unescape_cosmo 61 | unescape_cosmo = function(str, escapes) 62 | local P, R, Cmt, Cs 63 | do 64 | local _obj_0 = require("lpeg") 65 | P, R, Cmt, Cs = _obj_0.P, _obj_0.R, _obj_0.Cmt, _obj_0.Cs 66 | end 67 | local escape_patt = P(dollar_temp) * P(".") * R("09") ^ 1 / function(key) 68 | return escapes[key] or error("bad key for unescape_cosmo") 69 | end 70 | local patt = Cs((escape_patt + P(1)) ^ 0 * P(-1)) 71 | return assert(patt:match(str)) 72 | end 73 | local MarkdownRenderer 74 | do 75 | local _class_0 76 | local _parent_0 = require("sitegen.renderers.html") 77 | local _base_0 = { 78 | source_ext = "md", 79 | ext = "html", 80 | render = function(self, page, md_source) 81 | local discount = require("discount") 82 | md_source = page:pipe("renderer.markdown.pre_render", md_source) 83 | local escapes 84 | md_source, escapes = escape_cosmo(md_source) 85 | local html_source = assert(discount(md_source)) 86 | html_source = unescape_cosmo(html_source, escapes) 87 | return _class_0.__parent.__base.render(self, page, html_source) 88 | end 89 | } 90 | _base_0.__index = _base_0 91 | setmetatable(_base_0, _parent_0.__base) 92 | _class_0 = setmetatable({ 93 | __init = function(self, ...) 94 | return _class_0.__parent.__init(self, ...) 95 | end, 96 | __base = _base_0, 97 | __name = "MarkdownRenderer", 98 | __parent = _parent_0 99 | }, { 100 | __index = function(cls, name) 101 | local val = rawget(_base_0, name) 102 | if val == nil then 103 | local parent = rawget(cls, "__parent") 104 | if parent then 105 | return parent[name] 106 | end 107 | else 108 | return val 109 | end 110 | end, 111 | __call = function(cls, ...) 112 | local _self_0 = setmetatable({}, _base_0) 113 | cls.__init(_self_0, ...) 114 | return _self_0 115 | end 116 | }) 117 | _base_0.__class = _class_0 118 | local self = _class_0 119 | self.escape_cosmo = escape_cosmo 120 | self.unescape_cosmo = unescape_cosmo 121 | self.parse_cosmo = parse_cosmo 122 | if _parent_0.__inherited then 123 | _parent_0.__inherited(_parent_0, _class_0) 124 | end 125 | MarkdownRenderer = _class_0 126 | return _class_0 127 | end 128 | -------------------------------------------------------------------------------- /sitegen/renderers/markdown.moon: -------------------------------------------------------------------------------- 1 | import Renderer from require "sitegen.renderer" 2 | 3 | dollar_temp = "0000sitegen_markdown00dollar0000" 4 | 5 | -- a constructor for quote delimited strings 6 | simple_string = (delim) -> 7 | import P from require "lpeg" 8 | 9 | inner = P("\\#{delim}") + "\\\\" + (1 - P delim) 10 | inner = inner^0 11 | P(delim) * inner * P(delim) 12 | 13 | lua_string = -> 14 | import P, C, Cmt, Cb, Cg from require "lpeg" 15 | check_lua_string = (str, pos, right, left) -> 16 | #left == #right 17 | 18 | string_open = P"[" * P"="^0 * "[" 19 | string_close = P"]" * P"="^0 * "]" 20 | 21 | valid_close = Cmt C(string_close) * Cb"string_open", check_lua_string 22 | 23 | Cg(string_open, "string_open") * 24 | (1 - valid_close)^0 * string_close 25 | 26 | -- returns a pattern that parses a cosmo template. Can be used to have 27 | -- pre-processors ignore text that would be handled by cosmo 28 | parse_cosmo = -> 29 | import P, R, Cmt, Cs, V from require "lpeg" 30 | curly = P { 31 | P"{" * ( 32 | simple_string("'") + 33 | simple_string('"') + 34 | lua_string! + 35 | V(1) + 36 | (P(1) - "}") 37 | )^0 * P"}" 38 | } 39 | 40 | alphanum = R "az", "AZ", "09", "__" 41 | P"$" * alphanum^1 * (curly)^-1 42 | 43 | escape_cosmo = (str) -> 44 | escapes = {} 45 | import P, R, Cmt, Cs, V from require "lpeg" 46 | 47 | counter = 0 48 | 49 | cosmo = parse_cosmo! / (tpl) -> 50 | counter += 1 51 | key = "#{dollar_temp}.#{counter}" 52 | escapes[key] = tpl 53 | key 54 | 55 | patt = Cs (cosmo + P(1))^0 * P(-1) 56 | str = patt\match(str) or str, escapes 57 | str, escapes 58 | 59 | unescape_cosmo = (str, escapes) -> 60 | import P, R, Cmt, Cs from require "lpeg" 61 | 62 | escape_patt = P(dollar_temp) * P(".") * R("09")^1 / (key) -> 63 | escapes[key] or error "bad key for unescape_cosmo" 64 | 65 | patt = Cs (escape_patt + P(1))^0 * P(-1) 66 | assert patt\match(str) 67 | 68 | -- Converts input from markdown, then passes through cosmo filter from HTML 69 | -- renderer 70 | class MarkdownRenderer extends require "sitegen.renderers.html" 71 | @escape_cosmo: escape_cosmo 72 | @unescape_cosmo: unescape_cosmo 73 | @parse_cosmo: parse_cosmo 74 | 75 | source_ext: "md" 76 | ext: "html" 77 | 78 | render: (page, md_source) => 79 | discount = require "discount" 80 | 81 | md_source = page\pipe "renderer.markdown.pre_render", md_source 82 | md_source, escapes = escape_cosmo md_source 83 | 84 | html_source = assert discount md_source 85 | html_source = unescape_cosmo html_source, escapes 86 | 87 | super page, html_source 88 | 89 | -------------------------------------------------------------------------------- /sitegen/renderers/moon.lua: -------------------------------------------------------------------------------- 1 | local Renderer 2 | Renderer = require("sitegen.renderer").Renderer 3 | local moonscript = require("moonscript.base") 4 | local insert 5 | insert = table.insert 6 | local setfenv 7 | setfenv = require("sitegen.common").setfenv 8 | local MoonRenderer 9 | do 10 | local _class_0 11 | local _parent_0 = Renderer 12 | local _base_0 = { 13 | source_ext = "moon", 14 | ext = "html", 15 | load = function(self, source, fname) 16 | local content_fn, meta = _class_0.__parent.__base.load(self, source) 17 | local chunk_name 18 | if fname then 19 | chunk_name = "@" .. tostring(fname) 20 | end 21 | local render 22 | render = function(page) 23 | local scopes = { } 24 | local fn = assert(moonscript.loadstring(content_fn(), chunk_name)) 25 | local context = setmetatable({ }, { 26 | __index = function(self, key) 27 | for i = #scopes, 1, -1 do 28 | local val = scopes[i][key] 29 | if val then 30 | return val 31 | end 32 | end 33 | end 34 | }) 35 | local base_scope = setmetatable({ 36 | _context = function() 37 | return context 38 | end, 39 | self = page.tpl_scope, 40 | page = page, 41 | site = page.site, 42 | format = function(formatter) 43 | if type(formatter) == "string" then 44 | formatter = require(formatter) 45 | end 46 | return insert(scopes, formatter.make_context(page, context)) 47 | end 48 | }, { 49 | __index = _G 50 | }) 51 | insert(scopes, base_scope) 52 | context.format("sitegen.formatters.default") 53 | setfenv(fn, context) 54 | fn() 55 | return context.render() 56 | end 57 | return render, meta 58 | end 59 | } 60 | _base_0.__index = _base_0 61 | setmetatable(_base_0, _parent_0.__base) 62 | _class_0 = setmetatable({ 63 | __init = function(self, ...) 64 | return _class_0.__parent.__init(self, ...) 65 | end, 66 | __base = _base_0, 67 | __name = "MoonRenderer", 68 | __parent = _parent_0 69 | }, { 70 | __index = function(cls, name) 71 | local val = rawget(_base_0, name) 72 | if val == nil then 73 | local parent = rawget(cls, "__parent") 74 | if parent then 75 | return parent[name] 76 | end 77 | else 78 | return val 79 | end 80 | end, 81 | __call = function(cls, ...) 82 | local _self_0 = setmetatable({}, _base_0) 83 | cls.__init(_self_0, ...) 84 | return _self_0 85 | end 86 | }) 87 | _base_0.__class = _class_0 88 | if _parent_0.__inherited then 89 | _parent_0.__inherited(_parent_0, _class_0) 90 | end 91 | MoonRenderer = _class_0 92 | return _class_0 93 | end 94 | -------------------------------------------------------------------------------- /sitegen/renderers/moon.moon: -------------------------------------------------------------------------------- 1 | import Renderer from require "sitegen.renderer" 2 | 3 | moonscript = require "moonscript.base" 4 | 5 | import insert from table 6 | 7 | import setfenv from require "sitegen.common" 8 | 9 | class MoonRenderer extends Renderer 10 | source_ext: "moon" 11 | ext: "html" 12 | 13 | load: (source, fname) => 14 | content_fn, meta = super source 15 | 16 | chunk_name = if fname 17 | "@#{fname}" 18 | 19 | render = (page) -> 20 | scopes = {} 21 | fn = assert moonscript.loadstring content_fn!, chunk_name 22 | 23 | context = setmetatable {}, { 24 | __index: (key) => 25 | for i=#scopes,1,-1 26 | val = scopes[i][key] 27 | return val if val 28 | } 29 | 30 | base_scope = setmetatable { 31 | _context: -> context 32 | self: page.tpl_scope 33 | 34 | page: page 35 | site: page.site 36 | 37 | -- appends a scope to __index of the context 38 | format: (formatter) -> 39 | if type(formatter) == "string" 40 | formatter = require formatter 41 | 42 | insert scopes, formatter.make_context page, context 43 | }, __index: _G 44 | 45 | insert scopes, base_scope 46 | context.format "sitegen.formatters.default" 47 | 48 | setfenv fn, context 49 | fn! 50 | context.render! 51 | 52 | render, meta 53 | 54 | -------------------------------------------------------------------------------- /sitegen/site_file.lua: -------------------------------------------------------------------------------- 1 | local lfs = require("lfs") 2 | local extend, run_with_scope 3 | do 4 | local _obj_0 = require("moon") 5 | extend, run_with_scope = _obj_0.extend, _obj_0.run_with_scope 6 | end 7 | local moonscript = require("moonscript.base") 8 | local Path = require("sitegen.path") 9 | local Site = require("sitegen.site") 10 | local throw_error, trim, escape_patt 11 | do 12 | local _obj_0 = require("sitegen.common") 13 | throw_error, trim, escape_patt = _obj_0.throw_error, _obj_0.trim, _obj_0.escape_patt 14 | end 15 | local Logger 16 | Logger = require("sitegen.output").Logger 17 | local SiteFile 18 | do 19 | local _class_0 20 | local _base_0 = { 21 | find_root = function(self) 22 | local dir = lfs.currentdir() 23 | local depth = 0 24 | while dir do 25 | local path = Path.join(dir, self.name) 26 | if Path.exists(path) then 27 | self.file_path = path 28 | self:set_rel_path(depth) 29 | return 30 | end 31 | dir = Path.up(dir) 32 | depth = depth + 1 33 | end 34 | return throw_error("failed to find sitefile `" .. tostring(self.name) .. "`") 35 | end, 36 | relativeize = function(self, path) 37 | local exec 38 | exec = function(cmd) 39 | local p = io.popen(cmd) 40 | do 41 | local _with_0 = trim(p:read("*a")) 42 | p:close() 43 | return _with_0 44 | end 45 | end 46 | local rel_path 47 | if self.rel_path == "" then 48 | rel_path = "." 49 | else 50 | rel_path = self.rel_path 51 | end 52 | self.prefix = self.prefix or exec("realpath " .. rel_path) .. "/" 53 | local realpath = exec("realpath " .. path) 54 | return realpath:gsub("^" .. escape_patt(self.prefix), "") 55 | end, 56 | set_rel_path = function(self, depth) 57 | self.rel_path = ("../"):rep(depth) 58 | self:make_io() 59 | package.path = self.rel_path .. "?.lua;" .. package.path 60 | package.moonpath = self.rel_path .. "?.moon;" .. package.moonpath 61 | end, 62 | make_io = function(self) 63 | self.io = Path:relative_to(self.rel_path) 64 | end, 65 | get_site = function(self) 66 | self.logger:notice("using", Path.join(self.rel_path, self.name)) 67 | local fn = assert(moonscript.loadfile(self.file_path)) 68 | local sitegen = require("sitegen") 69 | local old_write = Site.write 70 | local site_ref, site 71 | Site.__base.write = function(site) 72 | site_ref = site 73 | return site 74 | end 75 | do 76 | local old_master = self.__class.master 77 | self.__class.master = self 78 | site = run_with_scope(fn, { 79 | sitegen = sitegen 80 | }) 81 | self.__class.master = old_master 82 | end 83 | Site.__base.write = old_write 84 | return assert(site, "Failed to load site from sitefile, make sure site is returned") 85 | end 86 | } 87 | _base_0.__index = _base_0 88 | _class_0 = setmetatable({ 89 | __init = function(self, opts) 90 | if opts == nil then 91 | opts = { } 92 | end 93 | self.name = opts.name or "site.moon" 94 | self.logger = Logger(opts.logger_opts) 95 | if opts.rel_path then 96 | self.rel_path = opts.rel_path 97 | self.file_path = Path.join(self.rel_path, self.name) 98 | return self:make_io() 99 | else 100 | return self:find_root() 101 | end 102 | end, 103 | __base = _base_0, 104 | __name = "SiteFile" 105 | }, { 106 | __index = _base_0, 107 | __call = function(cls, ...) 108 | local _self_0 = setmetatable({}, _base_0) 109 | cls.__init(_self_0, ...) 110 | return _self_0 111 | end 112 | }) 113 | _base_0.__class = _class_0 114 | local self = _class_0 115 | self.master = nil 116 | SiteFile = _class_0 117 | end 118 | return { 119 | SiteFile = SiteFile 120 | } 121 | -------------------------------------------------------------------------------- /sitegen/site_file.moon: -------------------------------------------------------------------------------- 1 | lfs = require "lfs" 2 | import extend, run_with_scope from require "moon" 3 | moonscript = require "moonscript.base" 4 | 5 | Path = require "sitegen.path" 6 | Site = require "sitegen.site" 7 | 8 | import 9 | throw_error 10 | trim 11 | escape_patt 12 | from require "sitegen.common" 13 | 14 | import Logger from require "sitegen.output" 15 | 16 | -- does two things: 17 | -- 1) finds the sitefile, looking up starting from the current dir 18 | -- 2) lets us open files relative to where the sitefile is 19 | class SiteFile 20 | @master: nil -- the global sitefile of the current process 21 | 22 | new: (opts={}) => 23 | @name = opts.name or "site.moon" 24 | @logger = Logger opts.logger_opts 25 | 26 | if opts.rel_path 27 | @rel_path = opts.rel_path 28 | @file_path = Path.join @rel_path, @name 29 | @make_io! 30 | else 31 | @find_root! 32 | 33 | find_root: => 34 | dir = lfs.currentdir! 35 | depth = 0 36 | while dir 37 | path = Path.join dir, @name 38 | if Path.exists path 39 | @file_path = path 40 | @set_rel_path depth 41 | return 42 | dir = Path.up dir 43 | depth += 1 44 | 45 | throw_error "failed to find sitefile `#{@name}`" 46 | 47 | -- convert from shell relative to sitefile relative 48 | relativeize: (path) => 49 | exec = (cmd) -> 50 | p = io.popen cmd 51 | with trim p\read "*a" 52 | p\close! 53 | 54 | rel_path = if @rel_path == "" then "." else @rel_path 55 | 56 | @prefix = @prefix or exec("realpath " .. rel_path) .. "/" 57 | realpath = exec "realpath " .. path 58 | realpath\gsub "^" .. escape_patt(@prefix), "" 59 | 60 | -- set relative path to depth folders above current 61 | -- add it to package.path 62 | set_rel_path: (depth) => 63 | @rel_path = ("../")\rep depth 64 | @make_io! 65 | package.path = @rel_path .. "?.lua;" .. package.path 66 | -- TODO: regenerate moonpath? 67 | package.moonpath = @rel_path .. "?.moon;" .. package.moonpath 68 | 69 | make_io: => 70 | @io = Path\relative_to @rel_path 71 | 72 | get_site: => 73 | @logger\notice "using", Path.join @rel_path, @name 74 | 75 | fn = assert moonscript.loadfile @file_path 76 | sitegen = require "sitegen" 77 | 78 | -- stub out write to not run during load for legacy 79 | old_write = Site.write 80 | 81 | local site_ref, site 82 | 83 | Site.__base.write = (site) -> 84 | site_ref = site 85 | site 86 | 87 | with old_master = @@master 88 | @@master = @ 89 | site = run_with_scope fn, { 90 | -- for legacy pages that doesn't reference module 91 | :sitegen 92 | } 93 | @@master = old_master 94 | 95 | Site.__base.write = old_write 96 | assert site, "Failed to load site from sitefile, make sure site is returned" 97 | 98 | { 99 | :SiteFile 100 | } 101 | -------------------------------------------------------------------------------- /sitegen/site_scope.lua: -------------------------------------------------------------------------------- 1 | local lfs = require("lfs") 2 | local Path = require("sitegen.path") 3 | local OrderSet, flatten_args, convert_pattern 4 | do 5 | local _obj_0 = require("sitegen.common") 6 | OrderSet, flatten_args, convert_pattern = _obj_0.OrderSet, _obj_0.flatten_args, _obj_0.convert_pattern 7 | end 8 | local SiteScope 9 | do 10 | local _class_0 11 | local _base_0 = { 12 | set = function(self, name, value) 13 | self[name] = value 14 | end, 15 | get = function(self, name) 16 | return self[name] 17 | end, 18 | disable = function(self, thing) 19 | self.site[thing .. "_disabled"] = true 20 | end, 21 | add_renderer = function(self, renderer) 22 | if type(renderer) == "string" then 23 | renderer = require(renderer) 24 | end 25 | return table.insert(self.site.renderers, 1, (assert(renderer, "nil renderer"))) 26 | end, 27 | add = function(self, ...) 28 | local files, options = flatten_args(...) 29 | for _index_0 = 1, #files do 30 | local _continue_0 = false 31 | repeat 32 | local fname = files[_index_0] 33 | if self.files:has(fname) then 34 | _continue_0 = true 35 | break 36 | end 37 | self.files:add(fname) 38 | if next(options) then 39 | self.meta[fname] = options 40 | end 41 | _continue_0 = true 42 | until true 43 | if not _continue_0 then 44 | break 45 | end 46 | end 47 | end, 48 | build = function(self, tool, input, ...) 49 | return table.insert(self.builds, { 50 | tool, 51 | input, 52 | { 53 | ... 54 | } 55 | }) 56 | end, 57 | copy = function(self, ...) 58 | local files = flatten_args(...) 59 | local _list_0 = files 60 | for _index_0 = 1, #_list_0 do 61 | local fname = _list_0[_index_0] 62 | self.copy_files:add(fname) 63 | end 64 | end, 65 | filter = function(self, pattern, fn) 66 | return table.insert(self.filters, { 67 | pattern, 68 | fn 69 | }) 70 | end, 71 | search = function(self, pattern, dir, enter_dirs) 72 | if dir == nil then 73 | dir = "." 74 | end 75 | if enter_dirs == nil then 76 | enter_dirs = false 77 | end 78 | pattern = convert_pattern(pattern) 79 | local root_dir = self.site.io.full_path(dir) 80 | local search 81 | search = function(dir) 82 | for fname in lfs.dir(dir) do 83 | local _continue_0 = false 84 | repeat 85 | if fname:match("^%.") then 86 | _continue_0 = true 87 | break 88 | end 89 | local full_path = Path.join(dir, fname) 90 | if enter_dirs and "directory" == lfs.attributes(full_path, "mode") then 91 | search(full_path) 92 | _continue_0 = true 93 | break 94 | end 95 | if fname:match(pattern) then 96 | if full_path:match("^%./") then 97 | full_path = full_path:sub(3) 98 | end 99 | local relative = self.site.io.strip_prefix(full_path) 100 | if not (self.files:has(relative)) then 101 | self.files:add(relative) 102 | end 103 | end 104 | _continue_0 = true 105 | until true 106 | if not _continue_0 then 107 | break 108 | end 109 | end 110 | end 111 | return search(root_dir) 112 | end, 113 | dump_files = function(self) 114 | print("added files:") 115 | for path in self.files:each() do 116 | print(" * " .. path) 117 | end 118 | print() 119 | print("copy files:") 120 | for path in self.copy_files:each() do 121 | print(" * " .. path) 122 | end 123 | end 124 | } 125 | _base_0.__index = _base_0 126 | _class_0 = setmetatable({ 127 | __init = function(self, site) 128 | self.site = site 129 | self.files = OrderSet() 130 | self.meta = { } 131 | self.copy_files = OrderSet() 132 | self.builds = { } 133 | self.filters = { } 134 | end, 135 | __base = _base_0, 136 | __name = "SiteScope" 137 | }, { 138 | __index = _base_0, 139 | __call = function(cls, ...) 140 | local _self_0 = setmetatable({}, _base_0) 141 | cls.__init(_self_0, ...) 142 | return _self_0 143 | end 144 | }) 145 | _base_0.__class = _class_0 146 | SiteScope = _class_0 147 | end 148 | return { 149 | SiteScope = SiteScope 150 | } 151 | -------------------------------------------------------------------------------- /sitegen/site_scope.moon: -------------------------------------------------------------------------------- 1 | lfs = require "lfs" 2 | 3 | Path = require "sitegen.path" 4 | 5 | import 6 | OrderSet 7 | flatten_args 8 | convert_pattern 9 | from require "sitegen.common" 10 | 11 | -- visible from init 12 | class SiteScope 13 | new: (@site) => 14 | @files = OrderSet! 15 | @meta = {} 16 | @copy_files = OrderSet! 17 | 18 | @builds = {} 19 | @filters = {} 20 | 21 | set: (name, value) => self[name] = value 22 | get: (name) => self[name] 23 | 24 | disable: (thing) => 25 | @site[thing .. "_disabled"] = true 26 | 27 | add_renderer: (renderer) => 28 | if type(renderer) == "string" 29 | renderer = require renderer 30 | 31 | table.insert @site.renderers, 1, (assert renderer, "nil renderer") 32 | 33 | add: (...) => 34 | files, options = flatten_args ... 35 | for fname in *files 36 | continue if @files\has fname 37 | @files\add fname 38 | @meta[fname] = options if next(options) 39 | 40 | build: (tool, input, ...) => 41 | table.insert @builds, {tool, input, {...}} 42 | 43 | copy: (...) => 44 | files = flatten_args ... 45 | @copy_files\add fname for fname in *files 46 | 47 | filter: (pattern, fn) => 48 | table.insert @filters, {pattern, fn} 49 | 50 | search: (pattern, dir=".", enter_dirs=false) => 51 | pattern = convert_pattern pattern 52 | root_dir = @site.io.full_path dir 53 | 54 | search = (dir) -> 55 | for fname in lfs.dir dir 56 | continue if fname\match "^%." -- no hidden files 57 | 58 | full_path = Path.join dir, fname 59 | 60 | if enter_dirs and "directory" == lfs.attributes full_path, "mode" 61 | search full_path 62 | continue 63 | 64 | if fname\match pattern 65 | if full_path\match"^%./" 66 | full_path = full_path\sub 3 67 | 68 | relative = @site.io.strip_prefix full_path 69 | 70 | unless @files\has relative 71 | @files\add relative 72 | 73 | search root_dir 74 | 75 | dump_files: => 76 | print "added files:" 77 | for path in @files\each! 78 | print " * " .. path 79 | print! 80 | print "copy files:" 81 | for path in @copy_files\each! 82 | print " * " .. path 83 | 84 | { 85 | :SiteScope 86 | } 87 | -------------------------------------------------------------------------------- /sitegen/templates.lua: -------------------------------------------------------------------------------- 1 | local extend 2 | extend = require("moon").extend 3 | local Path = require("sitegen.path") 4 | local Templates 5 | do 6 | local _class_0 7 | local _base_0 = { 8 | defaults = require("sitegen.default.templates"), 9 | templates_path = function(self, subpath) 10 | return Path.join(self.search_dir, subpath) 11 | end, 12 | find_by_name = function(self, name) 13 | if self.template_cache[name] then 14 | return self.template_cache[name] 15 | end 16 | local _list_0 = self.site.renderers 17 | for _index_0 = 1, #_list_0 do 18 | local _continue_0 = false 19 | repeat 20 | local renderer = _list_0[_index_0] 21 | if not (renderer.source_ext) then 22 | _continue_0 = true 23 | break 24 | end 25 | local fname = self:templates_path(tostring(name) .. "." .. tostring(renderer.source_ext)) 26 | if self.io.exists(fname) then 27 | self.template_cache[name] = renderer:load(self.io.read_file(fname), fname) 28 | break 29 | end 30 | _continue_0 = true 31 | until true 32 | if not _continue_0 then 33 | break 34 | end 35 | end 36 | do 37 | local default = not self.template_cache[name] and self.defaults[name] 38 | if default then 39 | local HTMLRenderer = require("sitegen.renderers.html") 40 | self.template_cache[name] = HTMLRenderer:load(default, "default.template(" .. tostring(name) .. ")") 41 | end 42 | end 43 | return self.template_cache[name] 44 | end 45 | } 46 | _base_0.__index = _base_0 47 | _class_0 = setmetatable({ 48 | __init = function(self, site) 49 | self.site = site 50 | self.io = assert(self.site.io, "site missing io") 51 | self.template_cache = { } 52 | self.search_dir = self.site.config.template_dir 53 | end, 54 | __base = _base_0, 55 | __name = "Templates" 56 | }, { 57 | __index = _base_0, 58 | __call = function(cls, ...) 59 | local _self_0 = setmetatable({}, _base_0) 60 | cls.__init(_self_0, ...) 61 | return _self_0 62 | end 63 | }) 64 | _base_0.__class = _class_0 65 | Templates = _class_0 66 | end 67 | return { 68 | Templates = Templates 69 | } 70 | -------------------------------------------------------------------------------- /sitegen/templates.moon: -------------------------------------------------------------------------------- 1 | import extend from require "moon" 2 | 3 | Path = require "sitegen.path" 4 | 5 | class Templates 6 | defaults: require "sitegen.default.templates" 7 | 8 | new: (@site) => 9 | @io = assert @site.io, "site missing io" 10 | @template_cache = {} 11 | @search_dir = @site.config.template_dir 12 | 13 | templates_path: (subpath) => 14 | Path.join @search_dir, subpath 15 | 16 | find_by_name: (name) => 17 | if @template_cache[name] 18 | return @template_cache[name] 19 | 20 | for renderer in *@site.renderers 21 | continue unless renderer.source_ext 22 | fname = @templates_path "#{name}.#{renderer.source_ext}" 23 | if @io.exists fname 24 | @template_cache[name] = renderer\load @io.read_file(fname), fname 25 | break 26 | 27 | if default = not @template_cache[name] and @defaults[name] 28 | HTMLRenderer = require "sitegen.renderers.html" 29 | @template_cache[name] = HTMLRenderer\load(default, "default.template(#{name})") 30 | 31 | @template_cache[name] 32 | 33 | { 34 | :Templates 35 | } 36 | -------------------------------------------------------------------------------- /sitegen/tools.lua: -------------------------------------------------------------------------------- 1 | local shell_escape 2 | shell_escape = require("sitegen.path").shell_escape 3 | local system_command 4 | system_command = function(cmd, ext) 5 | return function(self, input, output) 6 | output = output and self:real_output_path_for(output) or self:real_output_path_for(input, ext) 7 | local real_cmd = cmd:format("'" .. tostring(shell_escape(input)) .. "'", "'" .. tostring(shell_escape(output)) .. "'") 8 | return real_cmd:match("^%w+"), real_cmd, os.execute(real_cmd) 9 | end 10 | end 11 | return { 12 | system_command = system_command 13 | } 14 | -------------------------------------------------------------------------------- /sitegen/tools.moon: -------------------------------------------------------------------------------- 1 | 2 | import shell_escape from require "sitegen.path" 3 | 4 | system_command = (cmd, ext) -> 5 | (input, output) => 6 | output = output and @real_output_path_for(output) or @real_output_path_for(input, ext) 7 | real_cmd = cmd\format "'#{shell_escape(input)}'", "'#{shell_escape(output)}'" 8 | real_cmd\match"^%w+", real_cmd, os.execute real_cmd 9 | 10 | { 11 | :system_command 12 | } 13 | -------------------------------------------------------------------------------- /sitegen/watch.lua: -------------------------------------------------------------------------------- 1 | local Path = require("sitegen.path") 2 | local inotify 3 | local Watcher 4 | do 5 | local _class_0 6 | local _base_0 = { 7 | page_handler = function(self, fname) 8 | return function() 9 | self.site.pages = nil 10 | self.site:load_pages() 11 | return self.site:Page(fname):write() 12 | end 13 | end, 14 | build_handler = function(self, buildset) 15 | return function() 16 | return self.site:run_build(buildset) 17 | end 18 | end, 19 | watch_file_with = function(self, file, handler) 20 | local path = Path.basepath(self.site.io.full_path(file)) 21 | self.dirs[path] = self.dirs[path] or { } 22 | self.dirs[path][Path.filename(file)] = handler 23 | end, 24 | setup_dirs = function(self) 25 | for file in self.site.scope.files:each() do 26 | self:watch_file_with(file, self:page_handler(file)) 27 | end 28 | local _list_0 = self.site.scope.builds 29 | for _index_0 = 1, #_list_0 do 30 | local buildset = _list_0[_index_0] 31 | self:watch_file_with(buildset[2], self:build_handler(buildset)) 32 | end 33 | end, 34 | loop = function(self) 35 | self.dirs = { } 36 | self:setup_dirs() 37 | local wd_table = { } 38 | for dir, set in pairs(self.dirs) do 39 | wd_table[self.handle:addwatch(dir, inotify.IN_CLOSE_WRITE)] = set 40 | end 41 | local filter_name 42 | filter_name = function(name) 43 | return name:match("^(.*)%~$") or name 44 | end 45 | print("Watching " .. #wd_table .. " dirs, Ctrl+C to quit") 46 | while true do 47 | local events = self.handle:read() 48 | if not events then 49 | break 50 | end 51 | for _index_0 = 1, #events do 52 | local ev = events[_index_0] 53 | local set = wd_table[ev.wd] 54 | local name = filter_name(ev.name) 55 | if set and set[name] then 56 | set[name]() 57 | end 58 | end 59 | end 60 | end 61 | } 62 | _base_0.__index = _base_0 63 | _class_0 = setmetatable({ 64 | __init = function(self, site) 65 | self.site = site 66 | inotify = require("inotify") 67 | if not inotify then 68 | error("missing inotify") 69 | end 70 | self.handle = inotify.init() 71 | end, 72 | __base = _base_0, 73 | __name = "Watcher" 74 | }, { 75 | __index = _base_0, 76 | __call = function(cls, ...) 77 | local _self_0 = setmetatable({}, _base_0) 78 | cls.__init(_self_0, ...) 79 | return _self_0 80 | end 81 | }) 82 | _base_0.__class = _class_0 83 | Watcher = _class_0 84 | end 85 | return { 86 | Watcher = Watcher 87 | } 88 | -------------------------------------------------------------------------------- /sitegen/watch.moon: -------------------------------------------------------------------------------- 1 | Path = require "sitegen.path" 2 | 3 | local inotify 4 | 5 | -- this is pretty basic, it just watches the page inputs, not any of the 6 | -- dependencies like templates or inline renders 7 | class Watcher 8 | new: (@site) => 9 | inotify = require "inotify" 10 | error "missing inotify" if not inotify 11 | 12 | @handle = inotify.init! 13 | 14 | page_handler: (fname) => 15 | -> 16 | -- refresh all the pages 17 | @site.pages = nil 18 | @site\load_pages! 19 | 20 | @site\Page(fname)\write! 21 | 22 | build_handler: (buildset) => 23 | -> @site\run_build buildset 24 | 25 | watch_file_with: (file, handler) => 26 | path = Path.basepath @site.io.full_path file 27 | 28 | @dirs[path] = @dirs[path] or { } 29 | @dirs[path][Path.filename file] = handler 30 | 31 | setup_dirs: => 32 | for file in @site.scope.files\each! 33 | @watch_file_with file, @page_handler file 34 | 35 | for buildset in *@site.scope.builds 36 | @watch_file_with buildset[2], @build_handler buildset 37 | 38 | loop: => 39 | @dirs = {} 40 | @setup_dirs! 41 | 42 | wd_table = {} 43 | 44 | for dir, set in pairs @dirs 45 | wd_table[@handle\addwatch dir, inotify.IN_CLOSE_WRITE] = set 46 | 47 | -- sym links have ~ appended to end of name? 48 | filter_name = (name) -> 49 | name\match"^(.*)%~$" or name 50 | 51 | print "Watching " .. #wd_table .. " dirs, Ctrl+C to quit" 52 | while true 53 | events = @handle\read! 54 | break if not events 55 | 56 | for ev in *events 57 | set = wd_table[ev.wd] 58 | name = filter_name ev.name 59 | 60 | if set and set[name] 61 | set[name]() 62 | 63 | 64 | { 65 | :Watcher 66 | } 67 | 68 | -------------------------------------------------------------------------------- /spec/.gitignore: -------------------------------------------------------------------------------- 1 | temp_site/ 2 | -------------------------------------------------------------------------------- /spec/dispatch_spec.moon: -------------------------------------------------------------------------------- 1 | import Dispatch from require "sitegen.dispatch" 2 | 3 | describe "sitegen.dispatch", -> 4 | describe "structure", -> 5 | local d, default_structure 6 | before_each -> 7 | d = Dispatch! 8 | 9 | d\on "page", 1 10 | d\on "page", 2 11 | d\on "page.flour", 3 12 | d\on "page.flour.zone", 4 13 | 14 | default_structure = { 15 | page: { 16 | 1 17 | 2 18 | flour: { 19 | 3 20 | zone: { 21 | 4 22 | } 23 | } 24 | } 25 | } 26 | 27 | it "should create a dispatch", -> 28 | assert.same default_structure, d.callbacks 29 | 30 | it "matches callbacks", -> 31 | assert.same {1,2}, d\callbacks_for "page" 32 | assert.same {1,2}, d\callbacks_for "page.dirt" 33 | assert.same {1,2,3}, d\callbacks_for "page.flour" 34 | assert.same {1,2,3}, d\callbacks_for "page.flour.more" 35 | assert.same {1,2,3,4}, d\callbacks_for "page.flour.zone" 36 | assert.same {1,2,3,4}, d\callbacks_for "page.flour.zone.dad" 37 | 38 | describe "off", -> 39 | it "removes entire tree", -> 40 | d\off "page" 41 | assert.same {}, d.callbacks 42 | 43 | it "removes subset", -> 44 | d\off "page.flour" 45 | assert.same { 46 | page: { 47 | 1 48 | 2 49 | } 50 | }, d.callbacks 51 | 52 | it "removes nothing", -> 53 | d\off "page.wowza" 54 | assert.same default_structure, d.callbacks 55 | 56 | describe "callbacks", -> 57 | it "runs two callbacks", -> 58 | out = {} 59 | 60 | d = Dispatch! 61 | d\on "cool", -> 62 | table.insert out, "one" 63 | 64 | d\on "cool", -> 65 | table.insert out, "two" 66 | 67 | d\trigger "cool" 68 | 69 | assert.same {"one", "two"}, out 70 | 71 | it "runs two cancels second callback", -> 72 | out = {} 73 | 74 | d = Dispatch! 75 | d\on "cool", => 76 | @cancel = true 77 | table.insert out, "one" 78 | 79 | d\on "cool", => 80 | table.insert out, "two" 81 | 82 | d\trigger "cool" 83 | 84 | assert.same {"one"}, out 85 | 86 | describe "pipe", -> 87 | it "pipes with no callbacks", -> 88 | d = Dispatch! 89 | assert.same { 90 | "yeah" 91 | 4 92 | }, { 93 | d\pipe "hello.world", "yeah", 4 94 | } 95 | 96 | it "pipes with one callback", -> 97 | d = Dispatch! 98 | d\on "add", (e, number) -> 99 | number + 5 100 | assert.same {7}, { d\pipe "add", 2 } 101 | 102 | it "pipes with two callbacks, multi args", -> 103 | d = Dispatch! 104 | 105 | d\on "double", (e, number, string) -> 106 | number + 5, string .. "a" 107 | 108 | d\on "double", (e, number, string) -> 109 | number + 2, string .. "b" 110 | 111 | assert.same { 112 | 8, "helloab" 113 | }, { d\pipe "double", 1, "hello" } 114 | 115 | it "pipes with two callbacks, multi args, cancels after first", -> 116 | d = Dispatch! 117 | 118 | d\on "double", (e, number, string) -> 119 | e.cancel = true 120 | number + 5, string .. "a" 121 | 122 | d\on "double", (e, number, string) -> 123 | number + 2, string .. "b" 124 | 125 | assert.same { 126 | 6, "helloa" 127 | }, { d\pipe "double", 1, "hello" } 128 | 129 | -------------------------------------------------------------------------------- /spec/factory.moon: -------------------------------------------------------------------------------- 1 | 2 | original_Site = require "sitegen.site" 3 | page = require "sitegen.page" 4 | site_file = require "sitegen.site_file" 5 | 6 | local * 7 | 8 | next_counter = do 9 | counters = setmetatable {}, __index: => 1 10 | (name) -> 11 | with counters[name] 12 | counters[name] += 1 13 | 14 | Site = (opts={}) -> 15 | opts.rel_path or= "." 16 | 17 | original_Site site_file.SiteFile { 18 | rel_path: opts.rel_path 19 | } 20 | 21 | Page = (opts={}) -> 22 | opts.site or= Site! 23 | 24 | base = "some_page_#{next_counter "page"}" 25 | 26 | opts.meta or= {} 27 | opts.source or= "#{base}.md" 28 | opts.target or= "www/#{base}.html" 29 | opts.render_fn or= -> 30 | 31 | opts.read = -> error "read disabled" 32 | opts.write = -> error "read disabled" 33 | 34 | setmetatable opts, page.Page.__base 35 | 36 | opts.site.pages or= {} 37 | table.insert opts.site.pages, opts 38 | 39 | opts 40 | 41 | { :Site, :Page } 42 | 43 | -------------------------------------------------------------------------------- /spec/header_spec.moon: -------------------------------------------------------------------------------- 1 | 2 | import 3 | trim_leading_white 4 | trim 5 | from require "sitegen.common" 6 | 7 | describe "sitegen.header", -> 8 | it "should trim front", -> 9 | indented = [[ 10 | hello world 11 | another test 12 | test 13 | ]] 14 | 15 | assert.same [[ 16 | hello world 17 | another test 18 | test]], trim_leading_white indented 19 | 20 | it "extracts empty moonscript header", -> 21 | import extract_header from require "sitegen.header" 22 | body, header = extract_header [[{}hello world]] 23 | assert.same "hello world", body 24 | assert.same {}, header 25 | 26 | it "extracts complex moonscript header", -> 27 | import extract_header from require "sitegen.header" 28 | body, header = extract_header [[ 29 | {color: "blue", height: 1, things: {1,2,3,4}} 30 | hello world test test 31 | yeah 32 | ]] 33 | 34 | assert.same { 35 | color: "blue" 36 | height: 1 37 | things: {1,2,3,4} 38 | }, header 39 | 40 | assert.same [[hello world test test 41 | yeah]], trim body 42 | 43 | -------------------------------------------------------------------------------- /spec/html_spec.moon: -------------------------------------------------------------------------------- 1 | 2 | describe "html", -> 3 | it "renders some html", -> 4 | html = require "sitegen.html" 5 | out = html.build -> 6 | div { "hello world", class: "yeah" } 7 | 8 | assert.same '
hello world
', out 9 | 10 | describe "with sorted attributes", -> 11 | local html 12 | 13 | setup -> 14 | package.loaded.html = nil 15 | html = require "sitegen.html" 16 | html.sort_attributes true 17 | 18 | teardown -> 19 | package.loaded.html = nil 20 | 21 | it "renders some html with attributes", -> 22 | out = html.build -> 23 | div class: "yeah", good: "world", id: "okay", one: "two", three: "yeah" 24 | assert.same [[
]], out 25 | 26 | it "renders nested html", -> 27 | out = html.build -> 28 | div { 29 | class: "cool" 30 | -> span "yeah" 31 | } 32 | 33 | assert.same [[
yeah
]], out 34 | 35 | it "renders nested cdata", -> 36 | out = html.build -> 37 | cdata "good ol cdata" 38 | 39 | assert.same [=[]=], out 40 | 41 | it "renders text", -> 42 | out = html.build -> 43 | text "Great's going > <" 44 | 45 | assert.same [[Great's going > <]], out 46 | 47 | it "renders select", -> 48 | out = html.build -> 49 | tag["select"] { 50 | option "one" 51 | option "two" 52 | option "three" 53 | } 54 | 55 | assert.same [[]], out 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /spec/page_spec.moon: -------------------------------------------------------------------------------- 1 | query = require "sitegen.query" 2 | factory = require "spec.factory" 3 | 4 | describe "page", -> 5 | describe "with site & pages", -> 6 | local site 7 | before_each -> 8 | site = factory.Site! 9 | 10 | factory.Page { 11 | :site 12 | meta: { 13 | is_a: {"blog_post", "article"} 14 | } 15 | } 16 | 17 | factory.Page { 18 | :site 19 | meta: { 20 | is_a: "article" 21 | tags: {"cool"} 22 | } 23 | } 24 | 25 | factory.Page { :site } 26 | 27 | it "queries with empty result", -> 28 | pages = site\query_pages { tag: "hello" } 29 | assert.same {}, pages 30 | 31 | it "queries all with empty query", -> 32 | pages = site\query_pages { } 33 | assert.same 3, #pages 34 | 35 | it "queries raw", -> 36 | pages = site\query_pages { is_a: "article" } 37 | assert.same 1, #pages 38 | 39 | it "queries filter contains", -> 40 | pages = site\query_pages { is_a: query.filter.contains "article" } 41 | assert.same 2, #pages 42 | 43 | it "queries filter contains", -> 44 | pages = site\query_pages { tags: query.filter.contains "cool" } 45 | assert.same 1, #pages 46 | -------------------------------------------------------------------------------- /spec/plugins_spec.moon: -------------------------------------------------------------------------------- 1 | 2 | factory = require "spec.factory" 3 | 4 | HTMLRenderer = require "sitegen.renderers.html" 5 | 6 | import trim from require "sitegen.common" 7 | 8 | flatten_html = (html) -> 9 | trim (html\gsub "%>%s+%<", "><") 10 | 11 | describe "sitegen.plugins.indexer", -> 12 | it "indexes a page when using $index", -> 13 | page = factory.Page { 14 | render_fn: HTMLRenderer\load [[ 15 | $index 16 |

First header

17 |

Second header

18 |

Third header

19 |

Another first header

20 | ]] 21 | } 22 | 23 | page\render! 24 | assert.same [[

First header

Second header

Third header

Another first header

]], 25 | flatten_html page._inner_content 26 | 27 | it "indexes a page when passing index: true", -> 28 | page = factory.Page { 29 | meta: { 30 | index: true 31 | } 32 | render_fn: -> [[ 33 |

First header

34 |

Second header

35 |

another header

36 | ]] 37 | } 38 | 39 | page\render! 40 | assert.same [[

First header

Second header

another header

]], 41 | flatten_html page._inner_content 42 | 43 | it "indexes page with anchors instead of id", -> 44 | page = factory.Page { 45 | meta: { 46 | index: { 47 | link_headers: true 48 | } 49 | } 50 | render_fn: HTMLRenderer\load [[ 51 | $index 52 |

First header

53 |

Second header

54 | ]] 55 | } 56 | 57 | page\render! 58 | 59 | valid = {v, true for v in *{ 60 | [[

First header

Second header

]] 61 | [[

First header

Second header

]] 62 | 63 | [[

First header

Second header

]] 64 | [[

First header

Second header

]] 65 | }} 66 | 67 | assert.true valid[flatten_html page._inner_content] 68 | 69 | 70 | it "indexes with custom slugify", -> 71 | page = factory.Page { 72 | meta: { 73 | index: { 74 | slugify: (header) -> 75 | header.title\gsub("%W", "")\upper! 76 | } 77 | } 78 | render_fn: -> [[ 79 |

First header

80 |

Second header

81 | ]] 82 | } 83 | 84 | page\render! 85 | assert.same [[

First header

Second header

]], 86 | flatten_html page._inner_content 87 | 88 | 89 | describe "sitegen.plugins.syntaxhighlight", -> 90 | local site, sh 91 | before_each -> 92 | site = factory.Site {} 93 | sh = require("sitegen.plugins.syntaxhighlight") 94 | 95 | it "syntax highlights some code", -> 96 | plugin = sh site 97 | out = flatten_html plugin\filter [[ 98 | ```lua 99 | print("hello world") 100 | ```]] 101 | 102 | assert.same [[
print("hello world")
]], out 103 | 104 | 105 | it "doesnt highlight code inside of a cosmo template", -> 106 | plugin = sh site 107 | out = flatten_html plugin\filter [=[ 108 | ```lua 109 | print 5 110 | ``` 111 | 112 | $hello{[[ 113 | ```lua 114 | print thing 115 | ``` 116 | ]]}]=] 117 | 118 | assert.same [=[
print5
119 | 120 | $hello{[[ 121 | ```lua 122 | print thing 123 | ``` 124 | ]]}]=], out 125 | -------------------------------------------------------------------------------- /spec/renderer_spec.moon: -------------------------------------------------------------------------------- 1 | factory = require "spec.factory" 2 | 3 | describe "renderers", -> 4 | render_for_site = (site, renderer, str, meta={}, page) -> 5 | page or= factory.Page(:site) 6 | page.render_fn, page.meta = renderer\load str 7 | page.meta.template = false 8 | for k,v in pairs meta 9 | page.meta[k] = v 10 | page\render! 11 | 12 | describe "renderers.moon", -> 13 | local site, renderer 14 | 15 | render = (...) -> render_for_site site, renderer, ... 16 | 17 | before_each -> 18 | MoonRenderer = require "sitegen.renderers.moon" 19 | site = factory.Site! 20 | renderer = MoonRenderer site 21 | 22 | it "renders some raw text", -> 23 | assert.same 'what the heck', render "write 'what the heck'" 24 | 25 | it "renders with html helper from default formatter", -> 26 | assert.same '
you smell
', render [[ 27 | html -> 28 | div { color: "blue", "you smell" } 29 | ]] 30 | 31 | describe "renderers.html", -> 32 | local site, renderer 33 | 34 | before_each -> 35 | HTMLRenderer = require "sitegen.renderers.html" 36 | site = factory.Site! 37 | renderer = HTMLRenderer site 38 | 39 | render = (...) -> render_for_site site, renderer, ... 40 | 41 | it "renders basic string", -> 42 | assert.same "hello!", render "hello!" 43 | 44 | describe "cosmo helpers", -> 45 | it "renders if", -> 46 | assert.same "we have a val set", 47 | render '$if{"val"}[[we have a val set]]$if{"nope"}[[nope]]', { 48 | val: "yes" 49 | } 50 | 51 | it "renders unless", -> 52 | assert.same "nope", 53 | render '$unless{"val"}[[we have a val set]]$unless{"nope"}[[nope]]', { 54 | val: "yes" 55 | } 56 | 57 | it "renders each", -> 58 | assert.same "thing: 1, thing: 2, thing: 3, ", 59 | render '$each{{1,2,3}, "thing"}[[thing: $thing, ]]' 60 | 61 | it "renders eq", -> 62 | assert.same "no yes ", 63 | render '$eq{1,2}[[yes]][[no]] $eq{2,2}[[yes]][[no]] $eq{1,2}[[yes]]' 64 | 65 | it "renders markdown via helper", -> 66 | out = render [==[

All Guides

67 | $markdown{[[ 68 | * [Getting Started]($root/reference/getting_started.html) 69 | * [Getting Started With MoonScript]($root/reference/moon_getting_started.html) 70 | * [Getting Started With Lua]($root/reference/lua_getting_started.html) 71 | ]]}]==] 72 | 73 | assert.same out, [[

All Guides

74 | 83 | 84 | ]] 85 | 86 | 87 | it "renders url_for", -> 88 | factory.Page(:site, meta: {id: "cool"}) 89 | p = factory.Page { 90 | :site 91 | target: "www/other_cool.html" 92 | meta: {id: "other_cool"} 93 | } 94 | 95 | assert.same "./other_cool.html", 96 | render '$url_for{id = "other_cool"}' 97 | 98 | -- does relative url correctly 99 | assert.same "../../other_cool.html", 100 | render '$url_for{id = "other_cool"}', {}, factory.Page { 101 | :site 102 | target: "www/yeah/good/stuff.html" 103 | } 104 | 105 | it "renders query_pages", -> 106 | factory.Page(:site, meta: {type: "1", title: "Interesting page title"}) 107 | factory.Page(:site, meta: {type: "1", title: "Another page title"}) 108 | factory.Page(:site, meta: {type: "3", title: "Not interesting post"}) 109 | 110 | assert.same "Interesting page title,Another page title,", 111 | render '$query_pages{type = "1"}[[$title,]]' 112 | 113 | it "renders query_pages", -> 114 | factory.Page(:site, meta: {id: "1", title: "A"}) 115 | factory.Page(:site, meta: {id: "2", title: "B"}) 116 | factory.Page(:site, meta: {id: "3", title: "C"}) 117 | 118 | assert.same "B", render '$query_pages{id = "2"}[[$title]]' 119 | 120 | it "renders a user var", -> 121 | site.user_vars.hello = "zone" 122 | site.user_vars.world = (page, arg) -> 123 | page.target\gsub "%d+", "XX" 124 | 125 | assert.same "hello: zone, world: www/some_page_XX.html", render "hello: $hello, world: $world{}" 126 | 127 | it "renders comsmo with complex markup", -> 128 | site.user_vars.thing = (page, arg) -> 129 | assert.same { 130 | markup: [[ 131 | 132 | 133 | Hello world 134 | ```lua 135 | world 136 | ``` 137 | ]] 138 | }, arg 139 | "ok" 140 | 141 | assert.same "ok", render [=[$thing{ 142 | markup = [[ 143 | Hello world 144 | ```lua 145 | world 146 | ``` 147 | ]] 148 | }]=] 149 | 150 | 151 | describe "renderers.markdown", -> 152 | local site, renderer 153 | before_each -> 154 | MarkdownRenderer = require "sitegen.renderers.markdown" 155 | site = factory.Site! 156 | renderer = MarkdownRenderer site 157 | 158 | render = (...) -> render_for_site site, renderer, ... 159 | 160 | it "renders preserves cosmo", -> 161 | assert.same "

yes

\n", 162 | render '$if{"val"}[[yes]]', { val: "yes" } 163 | 164 | for {str} in *{ 165 | {"hello $one zone"} 166 | {"hello $one{} zone"} 167 | {"hello $one{1,2,3,4,5,6} zone"} 168 | {"hello $one{'be string aware }'} zone"} 169 | {[[hello $one{yes = "no", 5} zone]]} 170 | 171 | {[[hello $one{"be string aware }"} zone]]} 172 | {'hello $one{one = [[a } here]]} zone'} 173 | 174 | {[[hello $one{ 175 | color = { 176 | 5, blue = 'okay' 177 | } 178 | } zone]]} 179 | } 180 | import escape_cosmo, unescape_cosmo from require "sitegen.renderers.markdown" 181 | 182 | it "escapes and unescapes cosmo", -> 183 | escaped = escape_cosmo str 184 | assert.same escaped, 185 | "hello 0000sitegen_markdown00dollar0000.1 zone" 186 | assert.same str, (unescape_cosmo escape_cosmo str) 187 | 188 | -------------------------------------------------------------------------------- /spec/sitegen_spec.moon: -------------------------------------------------------------------------------- 1 | 2 | Site = require "sitegen.site" 3 | import SiteFile from require "sitegen.site_file" 4 | 5 | Path = require "sitegen.path" 6 | 7 | import escape_patt from require "sitegen.common" 8 | 9 | get_files = (path, prefix=path) -> 10 | files = Path.read_exec "find", path, "-type", "f" 11 | files = [f for f in files\gmatch "[^\n]+"] 12 | 13 | if prefix 14 | files = for file in *files 15 | file\gsub "^#{escape_patt prefix}/?", "" 16 | 17 | table.sort files 18 | files 19 | 20 | describe "sitegen", -> 21 | it "should load sitegen", -> 22 | sitegen = require "sitegen" 23 | 24 | describe "with path", -> 25 | local prefix, path, site 26 | 27 | before_each -> 28 | prefix = "spec/temp_site" 29 | Path.rmdir prefix 30 | Path.mkdir prefix 31 | path = Path\relative_to prefix 32 | 33 | sitefile = SiteFile rel_path: prefix 34 | site = Site sitefile 35 | 36 | write = (...) -> 37 | (assert path.write_file_safe ...) 38 | 39 | read = (...) -> 40 | (assert path.read_file ...) 41 | 42 | it "should build an empty site", -> 43 | site\init_from_fn => 44 | site\write! 45 | assert.same { 46 | ".sitegen_cache" 47 | "www/.gitignore" 48 | }, get_files prefix 49 | 50 | it "builds site with html renderer", -> 51 | write "test.html", "hello I an html file" 52 | site\init_from_fn => 53 | add "test.html" 54 | 55 | site\write! 56 | 57 | assert.same { 58 | ".sitegen_cache" 59 | "test.html" 60 | "www/.gitignore" 61 | "www/test.html" 62 | }, get_files prefix 63 | 64 | it "should build with a markdown file", -> 65 | write "test.md", "hello I an *markdown*" 66 | write "inside/other.md", "more markdown" 67 | 68 | site\init_from_fn => 69 | add "test.md" 70 | add "inside/other.md" 71 | 72 | site\write! 73 | 74 | assert.same { 75 | ".sitegen_cache" 76 | "inside/other.md" 77 | "test.md" 78 | "www/.gitignore" 79 | "www/inside/other.html" 80 | "www/test.html" 81 | }, get_files prefix 82 | 83 | 84 | it "should build many markdown files", -> 85 | write "hello.md", "hello I an *markdown*" 86 | write "world.md", "and I am world" 87 | 88 | site\init_from_fn => 89 | search "*.md" 90 | 91 | site\write! 92 | 93 | assert.same { 94 | ".sitegen_cache" 95 | "hello.md" 96 | "world.md" 97 | "www/.gitignore" 98 | "www/hello.html" 99 | "www/world.html" 100 | }, get_files prefix 101 | 102 | 103 | it "builds site with moon renderer", -> 104 | write "index.moon", [[write "hello world!"]] 105 | 106 | site\init_from_fn => 107 | @title = "The title" 108 | add "index.moon" 109 | 110 | site\write! 111 | 112 | assert.same [[ 113 | 114 | 115 | 116 | The title 117 | 118 | 119 | 120 | 121 | hello world! 122 | 123 | 124 | ]], read "www/index.html" 125 | 126 | assert.same { 127 | ".sitegen_cache" 128 | "index.moon" 129 | "www/.gitignore" 130 | "www/index.html" 131 | }, get_files prefix 132 | 133 | 134 | it "builds site with lapis renderer", -> 135 | write "hello.moon", [[ 136 | import Widget from require "lapis.html" 137 | 138 | class Thinger extends Widget 139 | @options: { 140 | title: "cool stuff" 141 | } 142 | 143 | content: => 144 | div class: "hi", "Hello world" 145 | div @title 146 | ]] 147 | 148 | site\init_from_fn => 149 | add_renderer "sitegen.renderers.lapis" 150 | add "hello.moon" 151 | 152 | site\write! 153 | 154 | assert.same { 155 | ".sitegen_cache" 156 | "hello.moon" 157 | "www/.gitignore" 158 | "www/hello.html" 159 | }, get_files prefix 160 | 161 | assert.same [[ 162 | 163 | 164 | 165 | cool stuff 166 | 167 | 168 | 169 | 170 |
Hello world
cool stuff
171 | 172 | 173 | ]], read "www/hello.html" 174 | 175 | it "builds site moon template", -> 176 | write "index.moon", [[write "this is the inside"]] 177 | write "templates/web.moon", [[ 178 | write "TEMPLATE TOP" 179 | write @body 180 | write "TEMPLATE BOTTOM"]] 181 | 182 | site\init_from_fn => 183 | @title = "The title" 184 | add "index.moon", template: "web" 185 | 186 | site\write! 187 | assert.same [[ 188 | TEMPLATE TOP 189 | this is the inside 190 | TEMPLATE BOTTOM]], read "www/index.html" 191 | 192 | it "builds site with markdown helper", -> 193 | write "index.html", [==[$markdown{[[hello *world*]]}]==] 194 | site\init_from_fn => 195 | add "index.html", template: false 196 | 197 | site\write! 198 | 199 | read "www/index.html" 200 | assert.same "

hello world

\n", read "www/index.html" 201 | 202 | it "builds site with user vars", -> 203 | write "index.html", [[hello $world and $something{color = 'blue'}]] 204 | 205 | site\init_from_fn => 206 | @world = "777" 207 | @something = (page, arg) -> 208 | "HELLO(color:#{arg.color})(target:#{page.target})" 209 | 210 | add "index.html", template: false 211 | 212 | site\write! 213 | assert.same "hello 777 and HELLO(color:blue)(target:www/index.html)", read "www/index.html" 214 | 215 | 216 | --------------------------------------------------------------------------------