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 | $date
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 | $date
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 |
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 { "", name, ">"}
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('^'), "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('^'),
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 [[one
56 | two
57 | three ]], 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 | 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 |
--------------------------------------------------------------------------------