Browse Source

make Prettier

master
AJ ONeal 4 years ago
parent
commit
9131fb9a42
  1. 1
      .prettierrc.json
  2. 98
      DATA.md
  3. 98
      ENTITY.md
  4. 34
      GLOSSARY.md
  5. 207
      README.md
  6. 46
      TODO.md
  7. 17
      bower.json
  8. 1111
      desirae.js
  9. 50
      lib/aggregate-core.js
  10. 242
      lib/browser-adapters.js
  11. 8
      lib/convert-ruhoh-config.js
  12. 6
      lib/datamap-core.js
  13. 52
      lib/frontmatter.js
  14. 51
      lib/node-adapters/fsapi-real.js
  15. 437
      lib/node-adapters/fsapi.js
  16. 8
      lib/node-adapters/index.js
  17. 10
      lib/node-adapters/sha1sum.js
  18. 39
      lib/render-core.js
  19. 249
      lib/transform-core.js
  20. 101
      lib/utils.js
  21. 14
      lib/verify-config.js
  22. 193
      package-lock.json
  23. 1
      package.json
  24. 107
      tests/permalink.js

1
.prettierrc.json

@ -0,0 +1 @@
{}

98
DATA.md

@ -1,12 +1,10 @@
Data
====
# Data
Every template gets an object with the exact same structure - whether it's a template or a widget or a page or a post.
Here we only document Desirae's default behavior, but there are many objects added for compatibility with Ruhoh that are not documented.
config.yml vs site.yml
------
## config.yml vs site.yml
site.yml is for anything that changes the content of the site (navigation, title, analytic and ad ids, default author, etc)
@ -16,66 +14,60 @@ config.yml is for anything that doesn't change the site (from where to read dire
desi = {}
```
desi
====
* `config` - literally `config.yml`, parsed
* `site` - literally `site.yml`, parsed
* `authors` - literally the authors from `authors/*.yml`, parsed
* `author` - the primary author of the site
* `env` - urls and paths for this build (be it production, development, staging, etc)
* `content` - pre-rendered content (i.e. content rendered into the post layout rendered into the default layout)
* `collection` - config related to this collection
* `entity` - the page, post, article, etc that is the focus of the present template process
* `themes` - all themes
* `theme` - the default theme
* `layout` - the selected layout for this theme
* `satch` - the selected swatch for this theme
* `categories` - all categories
* `tags` - all tags
* `styles` - ??? goes into the final template in the head
* `scripts` - ?? that goes into the final template just before the body close
desi.entity
===========
# desi
- `config` - literally `config.yml`, parsed
- `site` - literally `site.yml`, parsed
- `authors` - literally the authors from `authors/*.yml`, parsed
- `author` - the primary author of the site
- `env` - urls and paths for this build (be it production, development, staging, etc)
- `content` - pre-rendered content (i.e. content rendered into the post layout rendered into the default layout)
- `collection` - config related to this collection
- `entity` - the page, post, article, etc that is the focus of the present template process
- `themes` - all themes
- `theme` - the default theme
- `layout` - the selected layout for this theme
- `satch` - the selected swatch for this theme
- `categories` - all categories
- `tags` - all tags
- `styles` - ??? goes into the final template in the head
- `scripts` - ?? that goes into the final template just before the body close
# desi.entity
stuff
* `uuid`
* `title`
* `disqus_url`
* `disqus_identifier`
- `uuid`
- `title`
- `disqus_url`
- `disqus_identifier`
more stuff
* `type` - `post`, `page`, etc
* `authors` - literally the relevant authors from `authors/*.yml`, parsed
* `author` - the primary author of this entity
* `theme` - null or a non-default theme
* `layout` - null or a non-default layout for this theme
* `swatch` - null or a non-default swatch for this theme
* `categories`: [] // *all* categories in all collections
* `tags`: [] // *all* categories in all collections
* `production_canonical_url` the PRODUCTION canonical_url for this entity
* `production_url` the PRODUCTION url for this entity
* `production_path` the PRODUCTION path for this entity
* `url` the full url in the current environment (might be production, development, etc)
* `path` the non-host part (i.e. `/compiled_dev/articles/my-first-post.html`)
* `previous` the previous entity in this collection
* `next` the next entitiy in this collection
- `type` - `post`, `page`, etc
- `authors` - literally the relevant authors from `authors/*.yml`, parsed
- `author` - the primary author of this entity
- `theme` - null or a non-default theme
- `layout` - null or a non-default layout for this theme
- `swatch` - null or a non-default swatch for this theme
- `categories`: [] // _all_ categories in all collections
- `tags`: [] // _all_ categories in all collections
- `production_canonical_url` the PRODUCTION canonical_url for this entity
- `production_url` the PRODUCTION url for this entity
- `production_path` the PRODUCTION path for this entity
- `url` the full url in the current environment (might be production, development, etc)
- `path` the non-host part (i.e. `/compiled_dev/articles/my-first-post.html`)
- `previous` the previous entity in this collection
- `next` the next entitiy in this collection
NOTE: Plugins, widgets, etc SHOULD NOT modify config, site, authors, author, or env.
desi.posts
==========
# desi.posts
, posts: { collated: desi.collated }
desi.config
===========
# desi.config
desi.site
===========
# desi.site
desi.env
===========
# desi.env

98
ENTITY.md

@ -2,61 +2,65 @@ This is what an entity looks like:
```yml
# inherited from File Entity
path : My Posts/My-Old-Name.html
lastModifiedDate : 2015-07-04T13:56:01Z
createdDate : 2015-07-04T13:56:01Z
contents : '...' # whatever the file is
path: My Posts/My-Old-Name.html
lastModifiedDate: 2015-07-04T13:56:01Z
createdDate: 2015-07-04T13:56:01Z
contents: "..." # whatever the file is
# inherited from Collection Entity
name : My-Old-Name.html
relativePath : My Posts
ext : .html
collection : posts
name: My-Old-Name.html
relativePath: My Posts
ext: .html
collection: posts
# inherited from Content Entity
frontmatter : '---\n...\n---' # frontmatter as a string
yml : {} # frontmatter, parsed
body : 'I think ...' # body, after frontmatter
frontmatter: '---\n...\n---' # frontmatter as a string
yml: {} # frontmatter, parsed
body: "I think ..." # body, after frontmatter
# inherited from Normalized Entity
title : My Title # yml.title | titlize(entity.name)
slug : my-title # slugify(title)
slug_path : my-posts # slugifyPath(relativePath)
year : 2014
month : 07
day : 04
hour : 13
twelve_hour : 1
meridian : pm
minute : 22
categories : ['tech']
tags : ['http','url','website']
# includes index.html
relative_file : /posts/foo/index.html
# excludes index.html
relative_href : /posts/foo/
# actual url of this file, even if redirect
# excludes index.html
url : http://dev.example.com/posts/foo/
# the appropriate url, even in a redirect or duplicate
# excludes index.html
canonical_url : http://dev.example.com/posts/foo/
# production url, even in development (for disqus, etc)
# excludes index.html
production_url : http://example.com/posts/foo/
title: My Title # yml.title | titlize(entity.name)
slug: my-title # slugify(title)
slug_path: my-posts # slugifyPath(relativePath)
year: 2014
month: 07
day: 04
hour: 13
twelve_hour: 1
meridian: pm
minute: 22
categories: ["tech"]
tags:
["http", "url", "website"]
# includes index.html
relative_file:
/posts/foo/index.html
# excludes index.html
relative_href:
/posts/foo/
# actual url of this file, even if redirect
# excludes index.html
url:
http://dev.example.com/posts/foo/
# the appropriate url, even in a redirect or duplicate
# excludes index.html
canonical_url:
http://dev.example.com/posts/foo/
# production url, even in development (for disqus, etc)
# excludes index.html
production_url: http://example.com/posts/foo/
```
Note: The option `env.explicitIndexes` turns on `/index.html`. This option is automatically turned on when Dropbox is the host.
TODO
----
## TODO
* path relative from / in the browser
* path relative from base_path on the file system
- path relative from / in the browser
- path relative from base_path on the file system

34
GLOSSARY.md

@ -1,14 +1,12 @@
Glossary
========
# Glossary
Canonical URL
--------
base\_url + base\_path + permalink
## Canonical URL
Base URL
----
base_url + base_path + permalink
base\_url is the point of ownership
## Base URL
base_url is the point of ownership
In most cases that would be https://johndoe.com
@ -16,27 +14,25 @@ In some cases that might be https://school.edu/~/johndoe
It does NOT include a trailing /
Base Path
-----
## Base Path
base\_path is the blog directory
base_path is the blog directory
In most cases that would be / or /blog/
It DOES include BOTH a LEADING and TRAILING slash.
In the case of https://school.edu/~/johndoe/weblog, the base\_path would be /weblog/.
In the case of https://school.edu/~/johndoe/weblog, the base_path would be /weblog/.
Permalink
------
## Permalink
The permalink is the permanent part of the URL, after the path to the blog.
For example:
* http://blog.johndoe.com/articles/first-post.html the permalink is articles/first-post.html
* http://johndoe.com/blog/articles/first-post.html the permalink is still articles/first-post.html
* http://school.edu/~/johndoe/blog/articles/first-post.html the permalink is yet still articles/first-post.html
- http://blog.johndoe.com/articles/first-post.html the permalink is articles/first-post.html
- http://johndoe.com/blog/articles/first-post.html the permalink is still articles/first-post.html
- http://school.edu/~/johndoe/blog/articles/first-post.html the permalink is yet still articles/first-post.html
The permalink is ALWAYS RELATIVE (no leading slash)
@ -44,5 +40,5 @@ It is designed so that if you ever move your blog from one domain, point of owne
a very simple one-line redirect can be made to your webserver and all of the posts will end up in the right place
once again.
base\_url the
permalink refers to
base_url the
permalink refers to

207
README.md

@ -1,5 +1,4 @@
Did you mean DearDesi?
======================
# Did you mean DearDesi?
If you're looking for [DearDesi](http://dear.desi), the DIY blog platform for normal people
you should go to <http://dear.desi>.
@ -7,8 +6,7 @@ you should go to <http://dear.desi>.
Desirae (this repo) is the code that programmers to use to make DearDesi better
for everyone and to make Desirae-compatible blog platforms. (it's blog-ception!)
Desirae (v0.9)
=======
# Desirae (v0.9)
Desirae is a static webpage compiler written in JavaScript.
@ -19,18 +17,17 @@ It can also run entirely from the commandline (with io.js / node.js).
**Features:**
* `JavaScript` - it's so stable it takes 10 years to get new any features.
* Won't break every time you upgrade OS X and reinstall `brew` (*cough* ruby)
* Decent use of `try { ... } catch(e) ...` and `promise.catch()`
* the idea is that it shouldn't blow up at the slightest parse error without telling you which page is to blame (*cough* ruhoh *cough* jekyll)... bless me
* Browser (optional)
* using your front-end templates to build in your front-end? Imagine that!
* io.js (node.js) (optional)
* if you'd prefer to go headless, you can.
* The server is *very* minimal and could easily be implemented in any language (such as ruby or python).
- `JavaScript` - it's so stable it takes 10 years to get new any features.
- Won't break every time you upgrade OS X and reinstall `brew` (_cough_ ruby)
- Decent use of `try { ... } catch(e) ...` and `promise.catch()`
- the idea is that it shouldn't blow up at the slightest parse error without telling you which page is to blame (_cough_ ruhoh _cough_ jekyll)... bless me
- Browser (optional)
- using your front-end templates to build in your front-end? Imagine that!
- io.js (node.js) (optional)
- if you'd prefer to go headless, you can.
- The server is _very_ minimal and could easily be implemented in any language (such as ruby or python).
Installation
============
# Installation
```bash
bower install --save desirae
@ -38,8 +35,7 @@ bower install --save desirae
npm install --save desirae
```
Why
===
# Why
Because I hate ruby.
@ -51,14 +47,13 @@ more (last year I made dozens of gists and 0 blog posts) and I just couldn't get
versions and gems and whatnot... so I gave up and wrote my own (because I needed something
compatible with ruhoh).
Usage Overview
==============
# Usage Overview
### Before we get started
(disclaimers)
**Browser**: The default fs adapter will request the config files from `/api/fs/`.
**Browser**: The default fs adapter will request the config files from `/api/fs/`.
**Node**: Remember that desirae is built browser-first, and optimized to reduce the number of round-trips
(in case someone actually desides to host a Desi service, y'know?), so... the node adapter is built with
@ -70,11 +65,10 @@ It's an artifact of this project being born out of the ashes of my
### Getting Started
First off you need to declare a state object that will be used in every *desirae* action.
First off you need to declare a state object that will be used in every _desirae_ action.
```javascript
var desi = {}
;
var desi = {};
```
After that you'll load any plugins you need.
@ -156,26 +150,24 @@ Desi.registerRenderer(
);
```
And then you'll initialize Desirae with an *environment*.
And then you'll initialize Desirae with an _environment_.
```javascript
Desirae.init(
desi
, { url: 'https://johndoe.exmaple.com/blog'
, base_url: 'https://johndoe.exmaple.com'
, base_path: '/blog'
, compiled_path: 'compiled_dev'
// default: continue when possible
, onError: function (e) {
return Promise.reject(e);
}
// io.js / node.js only
, working_path: './path/to/blog'
}
).then(function () {
console.log('Desirae is initialized');
Desirae.init(desi, {
url: "https://johndoe.exmaple.com/blog",
base_url: "https://johndoe.exmaple.com",
base_path: "/blog",
compiled_path: "compiled_dev",
// default: continue when possible
onError: function (e) {
return Promise.reject(e);
},
// io.js / node.js only
working_path: "./path/to/blog",
}).then(function () {
console.log("Desirae is initialized");
});
```
@ -198,43 +190,38 @@ Desirae.write(desi, env).then(function () {
});
```
### Plugins
You need to start every file with a wrapper that is browser and io.js/node.js compatible
```javascript
/*jshint -W054 */
;(function (exports) {
'use strict';
var DesiraeMyModule = {}
;
(function (exports) {
"use strict";
var DesiraeMyModule = {};
// ... a bunch of code ...
DesiraeMyModule.doStuff = doStuff;
exports.DesiraeMyModule = DesiraeMyModule.DesiraeMyModule = DesiraeMyModule;
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);
```
Other than that, just be mindful that your code needs to run in both iojs/node and browser environments
so steer away from things that are super iojs/node-ish or super window-ish.
Configuration
=============
# Configuration
There are a few configuration files:
* `site.yml` is stuff that might be unique to your site, such as (title, url, adwords id, etc)
* `authors/<<your-handle.yml>>` contains information about you (name, handle, facebook, etc)
* `config.yml` contains directives that describe *how* the blog should be compiled - more technical stuff.
- `site.yml` is stuff that might be unique to your site, such as (title, url, adwords id, etc)
- `authors/<<your-handle.yml>>` contains information about you (name, handle, facebook, etc)
- `config.yml` contains directives that describe _how_ the blog should be compiled - more technical stuff.
If any of these files change, the entire site needs to be retemplated.
API
===
# API
I'd like to make the plugin system connect-style API for adding plugins or whatever so that,
for example, so you could have a custom markdown preprocessor (that handles includes, perhaps)
@ -246,28 +233,26 @@ But here's what I've got so far:
### (html, markdown, jade)
* `Desirae.registerRenderer(ext, fn)`
- `Desirae.registerRenderer(ext, fn)`
For example, if you want to add the ability to render from `slim` or `haml`
instead of just `markdown` you could find the appropriate
JavaScript module (or make requests to an API service that renders them for you) and do this
```javascript
var slim = exports.slimjs || require('slimjs')
;
function render(contentstr/*, desi*/) {
var slim = exports.slimjs || require("slimjs");
function render(contentstr /*, desi*/) {
return PromiseA.resolve(slim(contentstr));
}
Desirae.registerRenderer('.slim', render);
Desirae.registerRenderer(".slim", render);
```
## Data Mapping
### (desirae, ruhoh, etc)
* `Desirae.registerDataMapper(ext, fn)`
- `Desirae.registerDataMapper(ext, fn)`
If you want to use a non-desirae theme that uses attributes in a different than
how Desirae creates the view object internally
@ -276,18 +261,18 @@ data mapper to accomplish this.
Please try not to modify the original object if you can avoid it.
*TODO: maybe pass in a two-level deep shallow copy ?*
_TODO: maybe pass in a two-level deep shallow copy ?_
```javascript
Desirae.registerDataMapper('ruhoh@3.0', function (view) {
Desirae.registerDataMapper("ruhoh@3.0", function (view) {
return {
page: {
name: view.entity.title
}
, author: {
nickname: view.author.twitter
}
// ...
name: view.entity.title,
},
author: {
nickname: view.author.twitter,
},
// ...
};
});
```
@ -305,54 +290,54 @@ I'd love to work with anyone who is familiar with the Dropbox or similar APIs.
I think it would be awesome to support various means of storage. Perhaps github gists too.
Server
======
# Server
Obviously there has to be a server with some sort of storage and retrieval mechanism.
I've implemented a very simple node server using the filesystem.
GET /api/fs/walk
------------
## GET /api/fs/walk
`GET http://local.dear.desi:8080/api/fs/walk?dir=posts&dotfiles=true&extensions=md,markdown,jade,htm,html`
* `dir` **must** be supplied. returns a flat list of all files, recursively
* `dotfiles` default to `false`. includes dotfiles when `true`.
* `extensions` defaults to `null`. inclode **only** the supplied extensions when `true`.
- `dir` **must** be supplied. returns a flat list of all files, recursively
- `dotfiles` default to `false`. includes dotfiles when `true`.
- `extensions` defaults to `null`. inclode **only** the supplied extensions when `true`.
```json
[
{ "name": "happy-new-year.md"
, "createdDate": "2015-01-05T18:19:30.000Z"
, "lastModifiedDate": "2015-01-05T18:19:30.000Z"
, "size": 2121
, "relativePath": "posts/2015"
}
, { "name": "tips-for-the-ages.jade"
, "createdDate": "2014-06-16T18:19:30.000Z"
, "lastModifiedDate": "2014-06-16T18:19:30.000Z"
, "size": 389
, "relativePath": "posts"
}
, { "name": "my-first-post.html"
, "createdDate": "2013-08-01T22:47:37.000Z"
, "lastModifiedDate": "2013-08-01T22:47:37.000Z"
, "size": 4118
, "relativePath": "posts/2013"
{
"name": "happy-new-year.md",
"createdDate": "2015-01-05T18:19:30.000Z",
"lastModifiedDate": "2015-01-05T18:19:30.000Z",
"size": 2121,
"relativePath": "posts/2015"
},
{
"name": "tips-for-the-ages.jade",
"createdDate": "2014-06-16T18:19:30.000Z",
"lastModifiedDate": "2014-06-16T18:19:30.000Z",
"size": 389,
"relativePath": "posts"
},
{
"name": "my-first-post.html",
"createdDate": "2013-08-01T22:47:37.000Z",
"lastModifiedDate": "2013-08-01T22:47:37.000Z",
"size": 4118,
"relativePath": "posts/2013"
}
]
```
To retrieve multiple dir listings at once:
* for a few simple dirs without special chars just change `dir` to `dirs` and separate with commas
- for a few simple dirs without special chars just change `dir` to `dirs` and separate with commas
`GET http://local.dear.desi:8080/api/fs/walk?dirs=posts/2015,posts/2013&dotfiles=true&extensions=md,markdown,jade,htm,html`
* for many dirs, or dirs with special chars, `POST` an object containing an array of `dirs` with `&_method=GET` appended to the url.
- for many dirs, or dirs with special chars, `POST` an object containing an array of `dirs` with `&_method=GET` appended to the url.
```
POST http://local.dear.desi:8080/api/fs/walk?dotfiles=true&extensions=md,markdown,jade,htm,html&_method=GET
@ -367,27 +352,27 @@ POST http://local.dear.desi:8080/api/fs/walk?dotfiles=true&extensions=md,markdow
}
```
GET /api/fs/files
-------------
## GET /api/fs/files
`GET http://local.dear.desi:8080/api/fs/files?path=posts/happy-new-year.md`
```json
{ "path": "posts/intro-to-http-with-netcat-node-connect.md"
, "createdDate": "2013-08-01T22:47:37.000Z"
, "lastModifiedDate": "2013-08-01T22:47:37.000Z"
, "contents": "..."
, "sha1": "6eae3a5b062c6d0d79f070c26e6d62486b40cb46"
{
"path": "posts/intro-to-http-with-netcat-node-connect.md",
"createdDate": "2013-08-01T22:47:37.000Z",
"lastModifiedDate": "2013-08-01T22:47:37.000Z",
"contents": "...",
"sha1": "6eae3a5b062c6d0d79f070c26e6d62486b40cb46"
}
```
To retrieve multiple files at once:
* for a few simple files without special chars just change `path` to `paths` and separate with commas
- for a few simple files without special chars just change `path` to `paths` and separate with commas
`GET http://local.dear.desi:8080/api/fs/files?paths=posts/foo.md,posts/bar.md`
* for many files, or files with special chars, `POST` an object containing an array of `pathss` with `&_method=GET` appended to the url.
- for many files, or files with special chars, `POST` an object containing an array of `pathss` with `&_method=GET` appended to the url.
```
POST http://local.dear.desi:8080/api/fs/files?dotfiles=true&extensions=md,markdown,jade,htm,html&_method=GET
@ -406,8 +391,7 @@ POST http://local.dear.desi:8080/api/fs/files?dotfiles=true&extensions=md,markdo
]
```
POST /api/fs/files
------------------
## POST /api/fs/files
By default this should assume that you intended to write to the compiled directory
and return an error if you try to write to any other directory, unless `compiled=false` (not yet implemented).
@ -452,9 +436,8 @@ The response may include errors of all shapes and sizes.
}
```
POST /api/fs/copy
------------------
## POST /api/fs/copy
```json
{ files: { "assets/logo.png": "compiled/assets/logo.png" } }
{ "files": { "assets/logo.png": "compiled/assets/logo.png" } }
```

46
TODO.md

@ -4,7 +4,6 @@ show file path
show prod url
show dev url
POST tests
create a title and delete it (no error)
change the format. does the permalink change? (yes)
@ -16,22 +15,17 @@ change the format in the frontmatter permalink. does the format change? (yes)
create a description and delete it (no error)
create a description. does the frontmatter change? (yes)
protection
Don't allow changing the uuid, original_url, or original_date
TODO
---
## TODO
check that no other post uses the same permalink
default data-model 'ruhoh@2.2'
other data-model 'desirae@1.0'
Widgets
=======
# Widgets
All widgets should export an object with a `create(widgetConf, desiState)` function that returns a promise.
@ -42,7 +36,7 @@ widgets:
# stuff like google ad and disqus ids should go in config.yml or data.yml
config:
foobeep: boop
handle:
- html
- markdown
@ -52,11 +46,10 @@ widgets:
```
```javascript
'use strict';
"use strict";
module.exports.Foogizmo.create = function (foogizmoConf, desiState) {
return new Promise(function (resolve) {
function pager(desiPageState) {
// Do processing
@ -68,29 +61,28 @@ module.exports.Foogizmo.create = function (foogizmoConf, desiState) {
desiPostState.fooembedinator = function (fooval) {
// figure out what type of link fooval is and return iframe html
return '<iframe src="http://embedinator.com/"' + foovalProcessed + '></iframe>'
}
return (
'<iframe src="http://embedinator.com/"' +
foovalProcessed +
"></iframe>"
);
};
}
resolve({ foopager: pager, fooposter: poster });
});
}
};
```
Overlays
--------
## Overlays
For any config a widget uses, it should also check on post.fooconfig and theme.fooconfig to make sure that they don't override the foogizmo.config.fooconfig
Migrating from Ruhoh
====================
# Migrating from Ruhoh
There are only a few things in Ruhoh that could only be done in ruby or were otherwise difficult to work around.
config.yml
----------
## config.yml
Instead of having special names for some properties (`_root`)
and `use` sub attributes for others (`twitter` theme, posts directory),
@ -117,19 +109,15 @@ widgets [NO CHANGE]
All directories are ignored by default. If you want a directory to be interpreted as a collection of pages you need to specify it in the `collections` hash.
data.yml
--------
## data.yml
No changes
config.ru
---------
## config.ru
REMOVED (ruby only)
themes layout
-------------
## themes layout
TODO

17
bower.json

@ -1,15 +1,10 @@
{
"name": "desirae",
"version": "0.11.2",
"authors": [
"AJ ONeal <awesome@coolaj86.com>"
],
"authors": ["AJ ONeal <awesome@coolaj86.com>"],
"description": "A blogging platform in the browser. Wow!",
"main": "desirae.js",
"moduleType": [
"globals",
"node"
],
"moduleType": ["globals", "node"],
"keywords": [
"desirae",
"dear",
@ -26,13 +21,7 @@
],
"license": "Apache2",
"homepage": "http://github.com/DearDesi/desirae",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"ignore": ["**/.*", "node_modules", "bower_components", "test", "tests"],
"dependencies": {
"bluebird": "~2.6.2",
"escape-string-regexp": "~1.0.2",

1111
desirae.js

File diff suppressed because it is too large

50
lib/aggregate-core.js

@ -1,25 +1,23 @@
/*jshint -W054 */
;(function (exports) {
'use strict';
var path = exports.path || require('path')
, months
, cores = {}
;
(function (exports) {
"use strict";
var path = exports.path || require("path"),
months,
cores = {};
months = {
1: 'January'
, 2: 'February'
, 3: 'March'
, 4: 'April'
, 5: 'May'
, 6: 'June'
, 7: 'July'
, 8: 'August'
, 9: 'September'
, 10: 'October'
, 11: 'November'
, 12: 'December'
1: "January",
2: "February",
3: "March",
4: "April",
5: "May",
6: "June",
7: "July",
8: "August",
9: "September",
10: "October",
11: "November",
12: "December",
};
function byDate(a, b) {
@ -67,9 +65,7 @@
}
function collate(entities, env) {
var yearsArr = []
;
var yearsArr = [];
entities.forEach(function (f) {
var set;
var yindex = 3000 - f.year;
@ -85,10 +81,10 @@
if (!set.months[mindex]) {
set.months[mindex] = {
month_name: monthName
, month_number: mindex
, month: monthName
, pages: []
month_name: monthName,
month_number: mindex,
month: monthName,
pages: [],
};
}
set = set.months[mindex];
@ -125,4 +121,4 @@
};
exports.DesiraeAggregateCore = cores.DesiraeAggregateCore = cores;
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

242
lib/browser-adapters.js

@ -1,67 +1,66 @@
/*jshint -W054 */
;(function (exports) {
'use strict';
(function (exports) {
"use strict";
function create(Desi) {
// Chrome, Firefox, and even MSIE11+ all support crypto
var crypto = window.crypto || window.msCrypto
, PromiseA = window.Promise
, algos
;
var crypto = window.crypto || window.msCrypto,
PromiseA = window.Promise,
algos;
// convenience mappings for common digest algorithms
algos = {
'sha1': 'SHA-1'
, 'sha256': 'SHA-256'
, 'sha512': 'SHA-512'
sha1: "SHA-1",
sha256: "SHA-256",
sha512: "SHA-512",
};
// The function to generate a sha1sum is the same as generating any digest
// but here's a shortcut function anyway
function sha1sum(str) {
return hashsum('sha1', str);
return hashsum("sha1", str);
}
// a more general convenience function
function hashsum(hash, str) {
// you have to convert from string to array buffer
var ab
// you have to represent the algorithm as an object
, algo = { name: algos[hash] }
;
if ('string' === typeof str) {
// you have to convert from string to array buffer
var ab,
// you have to represent the algorithm as an object
algo = { name: algos[hash] };
if ("string" === typeof str) {
ab = str2ab(str);
} else {
ab = str;
}
// All crypto digest methods return a promise
return crypto.subtle.digest(algo, ab).then(function (digest) {
// you have to convert the ArrayBuffer to a DataView and then to a hex String
return ab2hex(digest);
}).catch(function (e) {
// if you specify an unsupported digest algorithm or non-ArrayBuffer, you'll get an error
console.error('sha1sum ERROR');
console.error(e);
throw e;
});
return crypto.subtle
.digest(algo, ab)
.then(function (digest) {
// you have to convert the ArrayBuffer to a DataView and then to a hex String
return ab2hex(digest);
})
.catch(function (e) {
// if you specify an unsupported digest algorithm or non-ArrayBuffer, you'll get an error
console.error("sha1sum ERROR");
console.error(e);
throw e;
});
}
// convert from arraybuffer to hex
function ab2hex(ab) {
var dv = new DataView(ab)
, i
, len
, hex = ''
, c
;
var dv = new DataView(ab),
i,
len,
hex = "",
c;
for (i = 0, len = dv.byteLength; i < len; i += 1) {
c = dv.getUint8(i).toString(16);
if (c.length < 2) {
c = '0' + c;
c = "0" + c;
}
hex += c;
@ -72,36 +71,31 @@
// convert from string to arraybuffer
function str2ab(stringToEncode, insertBom) {
stringToEncode = stringToEncode.replace(/\r\n/g,"\n");
stringToEncode = stringToEncode.replace(/\r\n/g, "\n");
var utftext = []
, n
, c
;
var utftext = [],
n,
c;
if (true === insertBom) {
utftext[0] = 0xef;
utftext[1] = 0xbb;
utftext[2] = 0xbf;
if (true === insertBom) {
utftext[0] = 0xef;
utftext[1] = 0xbb;
utftext[2] = 0xbf;
}
for (n = 0; n < stringToEncode.length; n += 1) {
c = stringToEncode.charCodeAt(n);
if (c < 128) {
utftext[utftext.length]= c;
utftext[utftext.length] = c;
} else if (c > 127 && c < 2048) {
utftext[utftext.length] = (c >> 6) | 192;
utftext[utftext.length] = (c & 63) | 128;
} else {
utftext[utftext.length] = (c >> 12) | 224;
utftext[utftext.length] = ((c >> 6) & 63) | 128;
utftext[utftext.length] = (c & 63) | 128;
}
else if((c > 127) && (c < 2048)) {
utftext[utftext.length] = (c >> 6) | 192;
utftext[utftext.length] = (c & 63) | 128;
}
else {
utftext[utftext.length] = (c >> 12) | 224;
utftext[utftext.length] = ((c >> 6) & 63) | 128;
utftext[utftext.length] = (c & 63) | 128;
}
}
return new Uint8Array(utftext).buffer;
}
@ -112,26 +106,21 @@
//
// FSAPI
//
var fsapi
;
var fsapi;
function request() {
}
request.get = function (url/*, query*/) {
function request() {}
request.get = function (url /*, query*/) {
// Return a new promise.
return new PromiseA(function(resolve, reject) {
return new PromiseA(function (resolve, reject) {
// Do the usual XHR stuff
var req = new XMLHttpRequest()
;
req.onload = function() {
var req = new XMLHttpRequest();
req.onload = function () {
// This is called even on 404 etc
// so check the status
if (200 === req.status) {
// Resolve the promise with the response text
resolve(req.response);
}
else {
} else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
@ -139,30 +128,27 @@
};
// Handle network errors
req.onerror = function() {
req.onerror = function () {
reject(Error("Network Error"));
};
// Make the request
req.open('GET', url);
req.open("GET", url);
req.send();
});
};
request.post = function (url/*, query*/, body) {
request.post = function (url /*, query*/, body) {
// Return a new promise.
return new PromiseA(function(resolve, reject) {
return new PromiseA(function (resolve, reject) {
// Do the usual XHR stuff
var req = new XMLHttpRequest()
;
req.onload = function() {
var req = new XMLHttpRequest();
req.onload = function () {
// This is called even on 404 etc
// so check the status
if (200 === req.status) {
// Resolve the promise with the response text
resolve(req.response);
}
else {
} else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
@ -170,14 +156,14 @@
};
// Handle network errors
req.onerror = function() {
req.onerror = function () {
reject(Error("Network Error"));
};
req.open('POST', url);
req.open("POST", url);
req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
// Make the request
if ('string' !== typeof body) {
if ("string" !== typeof body) {
body = JSON.stringify(body);
}
req.send(body);
@ -188,87 +174,93 @@
fsapi.getMeta = function (collections, opts) {
opts = opts || {};
var extensions = ''
, dotfiles = ''
, contents = ''
, sha1sum = ''
;
var extensions = "",
dotfiles = "",
contents = "",
sha1sum = "";
if (Array.isArray(opts.extensions)) {
extensions = '&extensions=' + opts.extensions.join(','); // md,markdown,jade,htm,html
extensions = "&extensions=" + opts.extensions.join(","); // md,markdown,jade,htm,html
}
if (opts.dotfiles) {
dotfiles = '&dotfiles=true';
dotfiles = "&dotfiles=true";
}
if (opts.contents) {
contents = '&contents=true';
contents = "&contents=true";
}
if (false === opts.sha1sum) {
sha1sum = '&sha1sum=false';
sha1sum = "&sha1sum=false";
}
return request.post('/api/fs/walk?_method=GET' + dotfiles + extensions + contents + sha1sum, {
dirs: collections
}).then(function (resp) {
return JSON.parse(resp);
}).catch(function (e) {
throw e;
});
return request
.post(
"/api/fs/walk?_method=GET" +
dotfiles +
extensions +
contents +
sha1sum,
{
dirs: collections,
}
)
.then(function (resp) {
return JSON.parse(resp);
})
.catch(function (e) {
throw e;
});
};
fsapi.getContents = function (filepaths) {
return request.post('/api/fs/files?_method=GET', {
paths: filepaths
}).then(function (resp) {
return JSON.parse(resp);
});
return request
.post("/api/fs/files?_method=GET", {
paths: filepaths,
})
.then(function (resp) {
return JSON.parse(resp);
});
};
fsapi.getCache = function () {
return request.get('/api/fs/static/cache.json').then(function (resp) {
return JSON.parse(resp);
}).catch(function (/*e*/) {
return {};
}).then(function (obj) {
return obj;
});
return request
.get("/api/fs/static/cache.json")
.then(function (resp) {
return JSON.parse(resp);
})
.catch(function (/*e*/) {
return {};
})
.then(function (obj) {
return obj;
});
};
fsapi.copy = function (files) {
var body = { files: files };
body = JSON.stringify(body); // this is more or less instant for a few MiB of posts
return request.post('/api/fs/copy', body).then(function (resp) {
var response = JSON.parse(resp)
;
return request.post("/api/fs/copy", body).then(function (resp) {
var response = JSON.parse(resp);
// not accurate for utf8/unicode, but close enough
response.size = body.length;
return response;
});
};
fsapi.putFiles = function (files) {
var body = { files: files }
;
var body = { files: files };
files.forEach(function (file) {
if (!file.contents || 'string' === typeof file.contents) {
if (!file.contents || "string" === typeof file.contents) {
return;
}
if (/\.json$/i.test(file.path)) {
file.contents = JSON.stringify(file.contents);
}
else if (/\.ya?ml$/i.test(file.path)) {
file.contents = exports.jsyaml.dump(file.contents);
} else if (/\.ya?ml$/i.test(file.path)) {
file.contents = exports.jsyaml.dump(file.contents);
}
});
body = JSON.stringify(body); // this is more or less instant for a few MiB of posts
return request.post('/api/fs/files', body).then(function (resp) {
var response = JSON.parse(resp)
;
return request.post("/api/fs/files", body).then(function (resp) {
var response = JSON.parse(resp);
// not accurate for utf8/unicode, but close enough
response.size = body.length;
return response;
@ -281,4 +273,4 @@
} else {
exports.create = create;
}
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

8
lib/convert-ruhoh-config.js

@ -1,7 +1,9 @@
'use strict';
"use strict";
module.exports.convert = function () {
console.error("I haven't implemented a ruhoh -> nuhoh converter yet, but it's not very hard to do.");
console.error(
"I haven't implemented a ruhoh -> nuhoh converter yet, but it's not very hard to do."
);
console.error("see https://github.com/coolaj86/nuhoh/tree/master/MIGRATE.md");
throw new Error('Not Implemented.');
throw new Error("Not Implemented.");
};

6
lib/datamap-core.js

@ -1,6 +1,6 @@
/*jshint -W054 */
;(function (exports) {
'use strict';
(function (exports) {
"use strict";
function desiMap(obj) {
obj.desi = obj;
@ -8,4 +8,4 @@
}
exports.DesiraeDatamapCore = desiMap.DesiraeDatamapCore = desiMap;
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

52
lib/frontmatter.js

@ -1,19 +1,17 @@
/*jshint -W054 */
;(function (exports) {
'use strict';
(function (exports) {
"use strict";
function create(Desi) {
Desi.YAML = {};
Desi.YAML.parse = (exports.jsyaml || require('js-yaml')).load;
Desi.YAML.stringify = (exports.jsyaml || require('js-yaml')).dump;
Desi.YAML = {};
Desi.YAML.parse = (exports.jsyaml || require("js-yaml")).load;
Desi.YAML.stringify = (exports.jsyaml || require("js-yaml")).dump;
function readFrontMatter(text) {
var lines
, line
, padIndent = ''
, ymllines = []
;
var lines,
line,
padIndent = "",
ymllines = [];
lines = text.split(/\n/);
line = lines.shift();
@ -25,14 +23,14 @@
// that start without indentation, so
// we can add it if this is the case
if (lines[0] && lines[0].match(/^\S/)) {
padIndent = '';
padIndent = "";
}
while (true) {
line = lines.shift();
// premature end-of-file (unsupported yaml)
if (!line && '' !== line) {
if (!line && "" !== line) {
ymllines = [];
break;
}
@ -44,24 +42,21 @@
if (line) {
// supported yaml
ymllines.push(padIndent + line);
ymllines.push(padIndent + line);
}
}
// XXX can't be sorted because arrays get messed up
//ymllines.sort();
if (ymllines) {
return '---\n' + ymllines.join('\n');
return "---\n" + ymllines.join("\n");
}
return;
}
function separateText(text, fm) {
var len
, yml
;
var len, yml;
yml = readFrontMatter(fm);
// strip frontmatter from text, if any
@ -72,27 +67,26 @@
len = 0;
}
return text.split(/\n/).slice(len).join('\n');
return text.split(/\n/).slice(len).join("\n");
}
function parseText(text) {
var fm = readFrontMatter(text)
, body = fm && separateText(text, fm)
, yml
;
var fm = readFrontMatter(text),
body = fm && separateText(text, fm),
yml;
if (fm) {
try {
yml = Desi.YAML.parse(fm);
} catch(e) {
} catch (e) {
//
}
}
return {
yml: yml
, frontmatter: fm
, body: body
yml: yml,
frontmatter: fm,
body: body,
};
}
@ -112,4 +106,4 @@
} else {
exports.create = create;
}
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

51
lib/node-adapters/fsapi-real.js

@ -1,51 +1,47 @@
'use strict';
var PromiseA = require('bluebird').Promise
, fs = PromiseA.promisifyAll(require('fs'))
;
"use strict";
var PromiseA = require("bluebird").Promise,
fs = PromiseA.promisifyAll(require("fs"));
function create(Desi, options) {
var fsapi = Desi.fsapi
;
var fsapi = Desi.fsapi;
options.blogdir = options.blogdir || options.working_path;
fsapi.getMeta = function (dirnames, opts) {
opts = opts || {};
var extensions = ''
, dotfiles = ''
, contents = ''
, sha1sum = ''
;
var extensions = "",
dotfiles = "",
contents = "",
sha1sum = "";
if (Array.isArray(opts.extensions)) {
extensions = '&extensions=' + opts.extensions.join(','); // md,markdown,jade,htm,html
extensions = "&extensions=" + opts.extensions.join(","); // md,markdown,jade,htm,html
}
if (opts.dotfiles) {
dotfiles = '&dotfiles=true';
dotfiles = "&dotfiles=true";
}
if (opts.contents) {
contents = '&contents=true';
contents = "&contents=true";
}
if (false === opts.sha1sum) {
sha1sum = '&sha1sum=false';
sha1sum = "&sha1sum=false";
}
return fsapi.walk.walkDirs(options.blogdir, dirnames, opts);
};
fsapi.getContents = function (filepaths) {
return fsapi.getfs(options.blogdir, filepaths);
};
fsapi.getCache = function () {
return fs.readFileAsync(options.blogdir, '/cache.json').catch(function (/*e*/) {
return {};
}).then(function (obj) {
return obj;
});
return fs
.readFileAsync(options.blogdir, "/cache.json")
.catch(function (/*e*/) {
return {};
})
.then(function (obj) {
return obj;
});
};
fsapi.copy = function (files) {
@ -55,14 +51,13 @@ function create(Desi, options) {
fsapi.putFiles = function (files, opts) {
files.forEach(function (file) {
if (!file.contents || 'string' === typeof file.contents) {
if (!file.contents || "string" === typeof file.contents) {
return;
}
if (/\.json$/i.test(file.path)) {
file.contents = JSON.stringify(file.contents);
}
else if (/\.ya?ml$/i.test(file.path)) {
file.contents = Desi.YAML.stringify(file.contents);
} else if (/\.ya?ml$/i.test(file.path)) {
file.contents = Desi.YAML.stringify(file.contents);
}
});

437
lib/node-adapters/fsapi.js

@ -1,18 +1,18 @@
'use strict';
var PromiseA = require('bluebird').Promise
, fs = PromiseA.promisifyAll(require('fs'))
, forEachAsync = require('foreachasync').forEachAsync
, path = require('path')
, walk = require('walk')
, escapeRegExp = require('escape-string-regexp')
, safeResolve = require('../utils').safeResolve
, sha1sum = function (str) { return require('secret-utils').hashsum('sha1', str); }
, mkdirp = PromiseA.promisify(require('mkdirp'))
, fsExtra = PromiseA.promisifyAll(require('fs.extra'))
//, tmpdir = require('os').tmpdir()
;
"use strict";
var PromiseA = require("bluebird").Promise,
fs = PromiseA.promisifyAll(require("fs")),
forEachAsync = require("foreachasync").forEachAsync,
path = require("path"),
walk = require("walk"),
escapeRegExp = require("escape-string-regexp"),
safeResolve = require("../utils").safeResolve,
sha1sum = function (str) {
return require("secret-utils").hashsum("sha1", str);
},
mkdirp = PromiseA.promisify(require("mkdirp")),
fsExtra = PromiseA.promisifyAll(require("fs.extra"));
//, tmpdir = require('os').tmpdir()
function strip(prefix, pathname) {
return pathname.substr(prefix.length + 1);
}
@ -23,24 +23,24 @@ function walkDir(parent, sub, opts) {
opts.sha1sum = true;
}
var prefix = path.resolve(parent)
, trueRoot = path.resolve(prefix, sub)
, files = []
;
var prefix = path.resolve(parent),
trueRoot = path.resolve(prefix, sub),
files = [];
function filter(name) {
if (!name) {
return false;
}
if (!opts.dotfiles && ('.' === name[0])) {
if (!opts.dotfiles && "." === name[0]) {
return false;
}
if (opts.extensions && opts.extensions.length) {
if (!opts.extensions.some(function (ext) {
return new RegExp('\\.' + escapeRegExp(ext) + '$').test(name);
})) {
if (
!opts.extensions.some(function (ext) {
return new RegExp("\\." + escapeRegExp(ext) + "$").test(name);
})
) {
return false;
}
}
@ -49,10 +49,8 @@ function walkDir(parent, sub, opts) {
}
return new PromiseA(function (resolve) {
var walker = walk.walk(trueRoot)
;
walker.on('nodeError', function (filepath, stat, next) {
var walker = walk.walk(trueRoot);
walker.on("nodeError", function (filepath, stat, next) {
//stats.forEach(function (stat) {
if (!filter(stat.name)) {
return;
@ -60,40 +58,37 @@ function walkDir(parent, sub, opts) {
stat.error.path = path.join(strip(prefix, filepath), stat.name);
files.push({
name: stat.name
, relativePath: strip(prefix, filepath)
, path: path.join(strip(prefix, filepath), stat.name)
name: stat.name,
relativePath: strip(prefix, filepath),
path: path.join(strip(prefix, filepath), stat.name),
, type: undefined
, error: stat.error
type: undefined,
error: stat.error,
});
//});
next();
});
walker.on('files', function (root, stats, next) {
var dirname = strip(prefix, root)
;
walker.on("files", function (root, stats, next) {
var dirname = strip(prefix, root);
function eachFile(stat) {
var file
;
var file;
if (!filter(stat.name)) {
return PromiseA.resolve();
}
file = {
name: stat.name
, relativePath: dirname
, path: path.join(dirname, stat.name)
name: stat.name,
relativePath: dirname,
path: path.join(dirname, stat.name),
, createdDate: (stat.birthtime||stat.ctime).toISOString()
, lastModifiedDate: stat.mtime.toISOString()
createdDate: (stat.birthtime || stat.ctime).toISOString(),
lastModifiedDate: stat.mtime.toISOString(),
, size: stat.size
, type: undefined // TODO include mimetype
size: stat.size,
type: undefined, // TODO include mimetype
};
files.push(file);
@ -102,17 +97,17 @@ function walkDir(parent, sub, opts) {
}
// TODO stream sha1 (for assets)
return fs.readFileAsync(path.join(root, stat.name), null).then(function (buffer) {
var contents = buffer.toString('utf8')
;
file.sha1 = sha1sum(contents);
file.type = undefined;
if (opts.contents) {
file.contents = contents;
}
});
return fs
.readFileAsync(path.join(root, stat.name), null)
.then(function (buffer) {
var contents = buffer.toString("utf8");
file.sha1 = sha1sum(contents);
file.type = undefined;
if (opts.contents) {
file.contents = contents;
}
});
}
if (!opts.contents) {
@ -125,7 +120,7 @@ function walkDir(parent, sub, opts) {
}
});
walker.on('end', function () {
walker.on("end", function () {
resolve(files);
});
});
@ -134,9 +129,7 @@ function walkDir(parent, sub, opts) {
function walkDirs(parent, subs, opts) {
opts = opts || {};
var collections = {}
;
var collections = {};
return forEachAsync(subs, function (sub) {
return walkDir(parent, sub, opts).then(function (results) {
collections[sub] = results;
@ -146,59 +139,53 @@ function walkDirs(parent, subs, opts) {
});
}
function getfs(blogdir, filepaths) {
var files = []
;
var files = [];
return forEachAsync(filepaths, function (filepath) {
var pathname = safeResolve(blogdir, filepath)
;
return fs.lstatAsync(pathname).then(function (stat) {
return fs.readFileAsync(pathname, null).then(function (buffer) {
files.push({
name: path.basename(pathname)
, relativePath: path.dirname(filepath)
, path: filepath
, createdDate: (stat.birthtime||stat.ctime).toISOString()
, lastModifiedDate: stat.mtime.toISOString()
, contents: buffer.toString('utf8')
, size: buffer.length
, sha1: sha1sum(buffer)
, type: undefined
var pathname = safeResolve(blogdir, filepath);
return fs
.lstatAsync(pathname)
.then(function (stat) {
return fs.readFileAsync(pathname, null).then(function (buffer) {
files.push({
name: path.basename(pathname),
relativePath: path.dirname(filepath),
path: filepath,
createdDate: (stat.birthtime || stat.ctime).toISOString(),
lastModifiedDate: stat.mtime.toISOString(),
contents: buffer.toString("utf8"),
size: buffer.length,
sha1: sha1sum(buffer),
type: undefined,
});
});
})
.catch(function (e) {
files.push({ path: filepath, error: e.message });
});
}).catch(function (e) {
files.push({ path: filepath, error: e.message });
});
}).then(function () {
return files;
});
}
function makeAllDirs(dirpaths) {
var errors = []
;
var errors = [];
return forEachAsync(dirpaths, function (pathname) {
return mkdirp(pathname).catch(function (e) {
// TODO exclude attempting to write files to this dir?
errors.push({
type: 'directory'
type: "directory",
, directory: pathname
directory: pathname,
, message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
message: e.message,
code: e.code,
errno: e.errno,
status: e.status,
syscall: e.syscall,
});
});
}).then(function () {
return errors;
@ -207,156 +194,164 @@ function makeAllDirs(dirpaths) {
function copyfs(blogdir, files) {
// TODO switch format to { source: ..., dest: ..., opts: ... } ?
var results = { errors: [] }
, dirpaths = {}
, sources = Object.keys(files)
;
var results = { errors: [] },
dirpaths = {},
sources = Object.keys(files);
return forEachAsync(sources, function (source) {
/*
var nsource = safeResolve(blogdir, source)
;
*/
var dest = safeResolve(blogdir, files[source])
, pathname = path.dirname(dest)
//, filename = path.basename(dest)
;
var dest = safeResolve(blogdir, files[source]),
pathname = path.dirname(dest);
//, filename = path.basename(dest)
dirpaths[pathname] = true;
return PromiseA.resolve();
}).then(function () {
// TODO is it better to do this lazy-like or as a batch?
// I figure as batch when there may be hundreds of files,
// likely within 2 or 3 directories
return makeAllDirs(Object.keys(dirpaths)).then(function (errors) {
errors.forEach(function (e) {
results.errors.push(e);
});
});
}).then(function () {
// TODO allow delete?
return forEachAsync(sources, function (source) {
return fsExtra.copyAsync(
safeResolve(blogdir, source)
, safeResolve(blogdir, files[source])
, { replace: true }
).catch(function (e) {
results.errors.push({
type: 'file'
, source: source
, destination: files[source]
, message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
})
.then(function () {
// TODO is it better to do this lazy-like or as a batch?
// I figure as batch when there may be hundreds of files,
// likely within 2 or 3 directories
return makeAllDirs(Object.keys(dirpaths)).then(function (errors) {
errors.forEach(function (e) {
results.errors.push(e);
});
});
})
.then(function () {
// TODO allow delete?
return forEachAsync(sources, function (source) {
return fsExtra
.copyAsync(
safeResolve(blogdir, source),
safeResolve(blogdir, files[source]),
{ replace: true }
)
.catch(function (e) {
results.errors.push({
type: "file",
source: source,
destination: files[source],
message: e.message,
code: e.code,
errno: e.errno,
status: e.status,
syscall: e.syscall,
});
});
});
})
.catch(function (e) {
results.error = {
message: e.message,
code: e.code,
errno: e.errno,
status: e.status,
syscall: e.syscall,
};
})
.then(function () {
return results;
});
}).catch(function (e) {
results.error = {
message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
};
}).then(function () {
return results;
});
}
function putfs(blogdir, files, options) {
options = options || {};
var putfsResults = { errors: [] }
, dirpaths = {}
;
var putfsResults = { errors: [] },
dirpaths = {};
return forEachAsync(files, function (file) {
var filepath = safeResolve(blogdir, file.path || path.join(file.relativePath, file.name))
, pathname = path.dirname(filepath)
, filename = file.name || path.basename(filepath)
;
var filepath = safeResolve(
blogdir,
file.path || path.join(file.relativePath, file.name)
),
pathname = path.dirname(filepath),
filename = file.name || path.basename(filepath);
file.realPath = filepath;
file.name = filename;
dirpaths[pathname] = true;
return PromiseA.resolve();
}).then(function () {
// TODO is it better to do this lazy-like or as a batch?
// I figure as batch when there may be hundreds of files,
// likely within 2 or 3 directories
return forEachAsync(Object.keys(dirpaths), function (pathname) {
return mkdirp(pathname).catch(function (e) {
// TODO exclude attempting to write files to this dir?
putfsResults.errors.push({
type: 'directory'
, directory: pathname
, message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
})
.then(function () {
// TODO is it better to do this lazy-like or as a batch?
// I figure as batch when there may be hundreds of files,
// likely within 2 or 3 directories
return forEachAsync(Object.keys(dirpaths), function (pathname) {
return mkdirp(pathname).catch(function (e) {
// TODO exclude attempting to write files to this dir?
putfsResults.errors.push({
type: "directory",
directory: pathname,
message: e.message,
code: e.code,
errno: e.errno,
status: e.status,
syscall: e.syscall,
});
});
});
});
}).then(function () {
// TODO sort deletes last
return forEachAsync(files, function (file) {
// TODO use lastModifiedDate as per client request?
// TODO compare sha1 sums for integrity
// NOTE existsAsync is backwards
return fs.existsAsync(file.realPath).then(function () {
return fs.writeFileAsync(file.realPath, file.contents, 'utf8');
}).catch(function (/*exists*/) {
if (file.delete || !file.contents) {
return fs.unlinkAsync(file.realPath);
}
if (false === options.replace || false === options.overwrite) {
throw new Error('EEXIST: the file already exists');
}
return fs.writeFileAsync(file.realPath, file.contents, 'utf8');
}).catch(function (e) {
putfsResults.errors.push({
type: 'file'
, file: file.realPath
, delete: !file.contents
, path: file.path
, relativePath: file.relativePath
, name: file.name
, message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
});
})
.then(function () {
// TODO sort deletes last
return forEachAsync(files, function (file) {
// TODO use lastModifiedDate as per client request?
// TODO compare sha1 sums for integrity
// NOTE existsAsync is backwards
return fs
.existsAsync(file.realPath)
.then(function () {
return fs.writeFileAsync(file.realPath, file.contents, "utf8");
})
.catch(function (/*exists*/) {
if (file.delete || !file.contents) {
return fs.unlinkAsync(file.realPath);
}
if (false === options.replace || false === options.overwrite) {
throw new Error("EEXIST: the file already exists");
}
return fs.writeFileAsync(file.realPath, file.contents, "utf8");
})
.catch(function (e) {
putfsResults.errors.push({
type: "file",
file: file.realPath,
delete: !file.contents,
path: file.path,
relativePath: file.relativePath,
name: file.name,
message: e.message,
code: e.code,
errno: e.errno,
status: e.status,
syscall: e.syscall,
});
});
});
})
.catch(function (e) {
putfsResults.error = {
message: e.message,
code: e.code,
errno: e.errno,
status: e.status,
syscall: e.syscall,
};
})
.then(function () {
return putfsResults;
});
}).catch(function (e) {
putfsResults.error = {
message: e.message
, code: e.code
, errno: e.errno
, status: e.status
, syscall: e.syscall
};
}).then(function () {
return putfsResults;
});
}
/*
walkDirs('blog', ['posts'], { contents: false }).then(function (stats) {

8
lib/node-adapters/index.js

@ -1,5 +1,5 @@
'use strict';
"use strict";
exports.fsapi = require('./fsapi');
exports.sha1sum = require('./sha1sum').sha1sum;
exports.realFsapi = require('./fsapi-real');
exports.fsapi = require("./fsapi");
exports.sha1sum = require("./sha1sum").sha1sum;
exports.realFsapi = require("./fsapi-real");

10
lib/node-adapters/sha1sum.js

@ -1,9 +1,7 @@
'use strict';
var PromiseA = require('bluebird').Promise
, secretutils = require('secret-utils')
;
"use strict";
var PromiseA = require("bluebird").Promise,
secretutils = require("secret-utils");
module.exports.sha1sum = function (str) {
return PromiseA.resolve( secretutils.hashsum('sha1', str) );
return PromiseA.resolve(secretutils.hashsum("sha1", str));
};

39
lib/render-core.js

@ -1,38 +1,35 @@
/*jshint -W054 */
;(function (exports) {
'use strict';
var PromiseA = exports.Promise || require('bluebird').Promise
;
function renderMd(contentstr/*, desi*/) {
var markitdown = (exports.markdownit || require('markdown-it'))({ html: true, linkify: true })
;
(function (exports) {
"use strict";
var PromiseA = exports.Promise || require("bluebird").Promise;
function renderMd(contentstr /*, desi*/) {
var markitdown = (exports.markdownit || require("markdown-it"))({
html: true,
linkify: true,
});
return PromiseA.resolve(
markitdown.render(contentstr)
//.replace('&quot;', '"')
//.replace('&#39;', "'")
//.replace('&#x2F;', '/')
//.replace('&quot;', '"')
//.replace('&#39;', "'")
//.replace('&#x2F;', '/')
);
}
function renderNoop(contentstr/*, desi*/) {
function renderNoop(contentstr /*, desi*/) {
// hmmm... that was easy
return PromiseA.resolve(contentstr);
}
function renderJade(contentstr, desi, options) {
options = options || {};
if (!('pretty' in options)) {
if (!("pretty" in options)) {
options.pretty = true;
}
var jade = (exports.jade || require('jade'))
, fn = jade.compile(contentstr, options)
, html = fn(desi)
;
var jade = exports.jade || require("jade"),
fn = jade.compile(contentstr, options),
html = fn(desi);
return PromiseA.resolve(html);
}
@ -41,4 +38,4 @@
exports.DesiraeRenderCss = renderNoop.DesiraeRenderCss = renderNoop;
exports.DesiraeRenderJs = renderNoop.DesiraeRenderJs = renderNoop;
exports.DesiraeRenderJade = renderJade.DesiraeRenderJade = renderJade;
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

249
lib/transform-core.js

@ -1,143 +1,153 @@
/*jshint -W054 */
;(function (exports) {
'use strict';
var cores = {}
, Desi = exports.Desirae || require('desirae').Desirae
, path = exports.path || require('path')
;
(function (exports) {
"use strict";
var cores = {},
Desi = exports.Desirae || require("desirae").Desirae,
path = exports.path || require("path");
cores.lint = function (desi, env, collection, entity) {
// TODO splice
//desi.content.collections = desi.content.collections.filter(function (entity) {
// TODO throw for any files that don't have a registered renderer
if (!entity.yml) {
if (!desi.config.empty_frontmatter) {
throw new Error("no frontmatter for " + (entity.path || entity.name) + "."
+ "Set `config.yml.empty_frontmatter: include|skip` to ignore this error."
);
}
// TODO throw for any files that don't have a registered renderer
if (!entity.yml) {
if (!desi.config.empty_frontmatter) {
throw new Error(
"no frontmatter for " +
(entity.path || entity.name) +
"." +
"Set `config.yml.empty_frontmatter: include|skip` to ignore this error."
);
}
if ('include' === desi.config.empty_frontmatter) {
entity.yml = {};
}
else if ('skip' === desi.config.empty_frontmatter) {
return false;
}
else {
throw new Error('unrecognize option ' + desi.config.empty_frontmatter +
' for `config.yml.empty_frontmatter: include|skip`.');
}
if ("include" === desi.config.empty_frontmatter) {
entity.yml = {};
} else if ("skip" === desi.config.empty_frontmatter) {
return false;
} else {
throw new Error(
"unrecognize option " +
desi.config.empty_frontmatter +
" for `config.yml.empty_frontmatter: include|skip`."
);
}
}
if (!entity.body || !entity.body.trim()) {
if (!desi.config.empty_body) {
throw new Error('empty content file ' + (entity.path || entity.name)
+ '. Set `config.yml.empty_body: include|skip` to ignore this error.'
);
}
if (!entity.body || !entity.body.trim()) {
if (!desi.config.empty_body) {
throw new Error(
"empty content file " +
(entity.path || entity.name) +
". Set `config.yml.empty_body: include|skip` to ignore this error."
);
}
if ('include' === desi.config.empty_body) {
entity.body = '';
}
else if ('skip' === desi.config.empty_body) {
return false;
}
else {
throw new Error('unrecognize option ' + desi.config.empty_frontmatter +
' for `config.yml.empty_body: include|skip`.');
}
if ("include" === desi.config.empty_body) {
entity.body = "";
} else if ("skip" === desi.config.empty_body) {
return false;
} else {
throw new Error(
"unrecognize option " +
desi.config.empty_frontmatter +
" for `config.yml.empty_body: include|skip`."
);
}
}
return true;
return true;
//});
};
cores.root = function (desi, env, collection, entity) {
entity.yml = entity.yml || {};
entity.layout = entity.yml.layout || '__page__';
entity.layout = entity.yml.layout || "__page__";
// _root is not subject to the same permalink rules as collections,
// so we just go ahead and define that here
if (/^index\.\w+$/.test(entity.path)) {
entity.permalink = '/';
entity.permalink = "/";
} else {
entity.permalink = entity.yml.permalink || entity.path.replace(/\.\w+$/, '/');
entity.permalink =
entity.yml.permalink || entity.path.replace(/\.\w+$/, "/");
entity.redirects = entity.redirects || [];
entity.redirects.push(entity.permalink.replace(/\/$/, '/index.html'));
entity.redirects.push(entity.permalink.replace(/\/$/, "/index.html"));
}
};
cores.normalize = function (desi, env, collection, entity) {
entity.title = entity.yml.title || Desi.firstCap(entity.name.replace(/\.\w+$/, ''));
entity.date = entity.yml.date;
entity.title =
entity.yml.title || Desi.firstCap(entity.name.replace(/\.\w+$/, ""));
entity.date = entity.yml.date;
if (!entity.date) {
// TODO tell YAML parser to keep the date a string
entity.date = new Date(entity.yml.created_at
|| entity.yml.time
|| entity.yml.updated_at
|| entity.createdDate
|| entity.lastModifiedDate
entity.date = new Date(
entity.yml.created_at ||
entity.yml.time ||
entity.yml.updated_at ||
entity.createdDate ||
entity.lastModifiedDate
).toISOString();
}
if ('object' === typeof entity.date) {
if ("object" === typeof entity.date) {
entity.date = entity.date.toISOString();
}
entity.updated_at = entity.yml.updated_at || entity.lastModifiedDate;
entity.published_at = Desi.fromLocaleDate(entity.date || entity.lastModifiedDate);
entity.year = entity.published_at.year;
entity.month = entity.published_at.month;
entity.day = entity.published_at.day;
entity.hour = entity.published_at.hour;
entity.twelve_hour = entity.published_at.twelve_hour;
entity.meridian = entity.published_at.meridian;
entity.minute = entity.published_at.minute;
entity.updated_at = entity.yml.updated_at || entity.lastModifiedDate;
entity.published_at = Desi.fromLocaleDate(
entity.date || entity.lastModifiedDate
);
entity.year = entity.published_at.year;
entity.month = entity.published_at.month;
entity.day = entity.published_at.day;
entity.hour = entity.published_at.hour;
entity.twelve_hour = entity.published_at.twelve_hour;
entity.meridian = entity.published_at.meridian;
entity.minute = entity.published_at.minute;
// let's just agree that that's too far
//entity.second = entity.published_at.second;
entity.slug = Desi.slugify(entity.title);
entity.slug_path = Desi.slugifyPath(entity.relativePath);
entity.slugPath = Desi.slugifyPath(entity.relativePath);
entity.slug = Desi.slugify(entity.title);
entity.slug_path = Desi.slugifyPath(entity.relativePath);
entity.slugPath = Desi.slugifyPath(entity.relativePath);
// TODO type checking like below
entity.redirects = Array.isArray(entity.yml.redirects) && entity.yml.redirects|| [];
entity.redirects =
(Array.isArray(entity.yml.redirects) && entity.yml.redirects) || [];
// categories
if (Array.isArray(entity.yml.categories)) {
entity.categories = entity.yml.categories;
}
else if ('string' === typeof entity.yml.categories) {
entity.categories = [entity.yml.categories];
}
else if ('string' === typeof entity.yml.category) {
entity.categories = [entity.yml.category];
}
else {
entity.categories = [];
entity.categories = entity.yml.categories;
} else if ("string" === typeof entity.yml.categories) {
entity.categories = [entity.yml.categories];
} else if ("string" === typeof entity.yml.category) {
entity.categories = [entity.yml.category];
} else {
entity.categories = [];
}
// tags
if (Array.isArray(entity.yml.tags)) {
entity.tags = entity.yml.tags;
}
else if ('string' === typeof entity.yml.tags) {
entity.tags = [entity.yml.tags];
}
else {
entity.tags = [];
entity.tags = entity.yml.tags;
} else if ("string" === typeof entity.yml.tags) {
entity.tags = [entity.yml.tags];
} else {
entity.tags = [];
}
entity.permalink = entity.permalink || entity.yml.permalink;
entity.permalink = entity.permalink || entity.yml.permalink;
if (!entity.permalink) {
// try the fallback_permalink first (because we're looking at files that don't have yml)
// then try the normal permalink (because :filename -> :title and whatnot, so it'll work)
entity.permalink = Desi.permalinkify(desi, collection.fallback_permalink || collection.permalink, entity);
entity.permalink = Desi.permalinkify(
desi,
collection.fallback_permalink || collection.permalink,
entity
);
}
/*
if (!/\.x?html?$/.test(entity.permalink)) {
entity.htmllink = path.join(entity.permalink, 'index.html');
@ -145,38 +155,51 @@
*/
// relative to the site
entity.relative_file = path.join(env.base_path, entity.permalink)
.replace(/\/$/, '/index.html');
entity.relative_href = path.join(env.base_path, entity.permalink)
.replace(/\/index\.html$/, '/');
entity.relative_link = entity.relative_href;
entity.url = env.base_url + path.join(env.base_path, entity.permalink)
.replace(/\/index\.html$/, '/');
entity.canonical_url = env.base_url + path.join(env.base_path, entity.permalink)
.replace(/\/index\.html$/, '/');
entity.production_url = desi.site.base_url + path.join(desi.site.base_path, entity.permalink)
.replace(/\/index\.html$/, '/');
entity.relative_url = path.join(env.base_path, entity.permalink)
.replace(/\/index\.html$/, '/');
if (env.explicitIndexes || env.explicitIndices || env.explicit_indexes || env.explicit_indices) {
entity.relative_file = path
.join(env.base_path, entity.permalink)
.replace(/\/$/, "/index.html");
entity.relative_href = path
.join(env.base_path, entity.permalink)
.replace(/\/index\.html$/, "/");
entity.relative_link = entity.relative_href;
entity.url =
env.base_url +
path.join(env.base_path, entity.permalink).replace(/\/index\.html$/, "/");
entity.canonical_url =
env.base_url +
path.join(env.base_path, entity.permalink).replace(/\/index\.html$/, "/");
entity.production_url =
desi.site.base_url +
path
.join(desi.site.base_path, entity.permalink)
.replace(/\/index\.html$/, "/");
entity.relative_url = path
.join(env.base_path, entity.permalink)
.replace(/\/index\.html$/, "/");
if (
env.explicitIndexes ||
env.explicitIndices ||
env.explicit_indexes ||
env.explicit_indices
) {
// NOTE: file_url is NOT replaced
['url', 'canonical_url', 'production_url', 'relative_url'].forEach(function (url) {
entity[url] = entity[url].replace(/\/$/, '/index.html');
});
["url", "canonical_url", "production_url", "relative_url"].forEach(
function (url) {
entity[url] = entity[url].replace(/\/$/, "/index.html");
}
);
}
// i.e. bootstrap, hero page, darkly
entity.theme = entity.theme || entity.yml.theme;
entity.layout = entity.layout || entity.yml.layout;
entity.swatch = entity.swatch || entity.yml.swatch;
entity.theme = entity.theme || entity.yml.theme;
entity.layout = entity.layout || entity.yml.layout;
entity.swatch = entity.swatch || entity.yml.swatch;
};
cores.disqus = function (desi, env, collection, entity) {
var yml = entity.yml
;
var yml = entity.yml;
if (yml.uuid) {
entity.disqus_identifier = yml.uuid;
}
@ -184,4 +207,4 @@
};
exports.DesiraeTransformCore = cores.DesiraeTransformCore = cores;
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

101
lib/utils.js

@ -1,9 +1,7 @@
;(function (exports) {
'use strict';
var path = exports.path || require('path')
;
(function (exports) {
"use strict";
var path = exports.path || require("path");
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
@ -11,10 +9,8 @@
function safeResolve(basename, target) {
basename = path.resolve(basename);
var targetname = path.resolve(basename, target)
, re = new RegExp('^' + escapeRegExp(basename) + '(/|$)')
;
var targetname = path.resolve(basename, target),
re = new RegExp("^" + escapeRegExp(basename) + "(/|$)");
return re.test(targetname) && targetname;
}
@ -22,29 +18,24 @@
exports.escapeRegExp = escapeRegExp;
function create(Desi) {
var fsapi = Desi.fsapi || require('./node-adapters').fsapi
;
var fsapi = Desi.fsapi || require("./node-adapters").fsapi;
fsapi.getConfigs = function (confs) {
var opts = { extensions: ['yml', 'yaml', 'json'], dotfiles: false, contents: true, sha1sum: true }
;
var opts = {
extensions: ["yml", "yaml", "json"],
dotfiles: false,
contents: true,
sha1sum: true,
};
return fsapi.getMeta(confs, opts).then(function (collections) {
var obj = {}
;
var obj = {};
Object.keys(collections).forEach(function (key) {
var files = collections[key]
, keyname = key.replace(/\.(json|ya?ml|\/)$/i, '')
;
var files = collections[key],
keyname = key.replace(/\.(json|ya?ml|\/)$/i, "");
obj[keyname] = obj[keyname] || {};
files.forEach(function (file) {
var filename = file.name.replace(/\.(json|ya?ml)$/i, '')
, data = {}
;
var filename = file.name.replace(/\.(json|ya?ml)$/i, ""),
data = {};
if (file.error) {
console.error(file);
console.error(file.error);
@ -57,17 +48,16 @@
if ("undefined" === data) {
data = {};
}
} catch(e) {
} catch (e) {
data = { error: e };
console.error("Could not parse yaml for " + filename);
console.error(file);
console.error(e);
}
}
else if (/\.(json)$/i.test(file.name)) {
} else if (/\.(json)$/i.test(file.name)) {
try {
data = JSON.parse(file.contents) || {};
} catch(e) {
} catch (e) {
data = { error: e };
console.error("Could not parse json for " + filename);
console.error(file);
@ -99,36 +89,34 @@
};
fsapi.getAllPartials = function () {
return fsapi.getConfigs(['partials', 'partials.yml']).then(function (results) {
var partials = {}
;
Object.keys(results.partials).forEach(function (key) {
var partial = results.partials[key]
;
Object.keys(partial).forEach(function (prop) {
if (partials[prop]) {
console.warn('partial \'' + prop + '\' overwritten by ' + key);
}
return fsapi
.getConfigs(["partials", "partials.yml"])
.then(function (results) {
var partials = {};
Object.keys(results.partials).forEach(function (key) {
var partial = results.partials[key];
Object.keys(partial).forEach(function (prop) {
if (partials[prop]) {
console.warn("partial '" + prop + "' overwritten by " + key);
}
partials[prop] = partial[prop];
partials[prop] = partial[prop];
});
});
});
return partials;
});
return partials;
});
};
fsapi.getAllConfigFiles = function () {
return fsapi.getConfigs(['config.yml', 'site.yml', 'authors']).then(function (results) {
var authors = results.authors
, config = results.config.config
, site = results.site.site
;
return { config: config, authors: authors, site: site };
});
return fsapi
.getConfigs(["config.yml", "site.yml", "authors"])
.then(function (results) {
var authors = results.authors,
config = results.config.config,
site = results.site.site;
return { config: config, authors: authors, site: site };
});
};
return exports;
@ -136,8 +124,7 @@
if (exports.Desirae) {
create(exports.Desirae);
}
else {
} else {
exports.create = create;
}
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

14
lib/verify-config.js

@ -1,5 +1,5 @@
;(function (exports) {
'use strict';
(function (exports) {
"use strict";
exports.verifyConfig = function (conf) {
if (!conf.NuhohSpec) {
@ -28,14 +28,18 @@
if (!Array.isArray(conf.collections)) {
if (conf.posts) {
console.error("Please indent and nest 'posts' under the key 'collection' to continue");
console.error(
"Please indent and nest 'posts' under the key 'collection' to continue"
);
}
throw new Error("missing key 'collections'.");
}
if (!conf.themes) {
if (conf.twitter) {
console.error("Please indent and nest 'twitter' under the key 'themes' to continue");
console.error(
"Please indent and nest 'twitter' under the key 'themes' to continue"
);
}
throw new Error("missing key 'themes'");
}
@ -55,4 +59,4 @@
throw new Error("missing key root");
}
};
}('undefined' !== typeof exports && exports || window));
})(("undefined" !== typeof exports && exports) || window);

193
package-lock.json

@ -0,0 +1,193 @@
{
"name": "desirae",
"version": "0.11.5",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"bluebird": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
"integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE="
},
"crypto-rand": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypto-rand/-/crypto-rand-0.0.2.tgz",
"integrity": "sha1-Hn3CMQLhiRo+6zQPtwrElYCzLt0="
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"foreachasync": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-5.1.3.tgz",
"integrity": "sha512-q3/l5D2pyZG8aYe9xZQDAnS/KyP7QJakLUNrU/qGkd4bQUTzCUmvVFjagRggoxxjtb1ZrJB+jQeG4ojOLWkztw=="
},
"fs-extra": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz",
"integrity": "sha1-9G8MdbeEH40gCzNIzU1pHVoJnRU=",
"requires": {
"jsonfile": "~1.0.1",
"mkdirp": "0.3.x",
"ncp": "~0.4.2",
"rimraf": "~2.2.0"
},
"dependencies": {
"mkdirp": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
"integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc="
}
}
},
"fs.extra": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fs.extra/-/fs.extra-1.3.2.tgz",
"integrity": "sha1-3QI/kwE77iRTHxszUUw3sg/ZM0k=",
"requires": {
"fs-extra": "~0.6.1",
"mkdirp": "~0.3.5",
"walk": "^2.3.9"
},
"dependencies": {
"mkdirp": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
"integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc="
}
}
},
"js-yaml": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"jsonfile": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz",
"integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0="
},
"linkify-it": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-0.1.5.tgz",
"integrity": "sha1-OMWD0y+pPtcm2gDHrwAQeL+2uUU=",
"requires": {
"uc.micro": "^1.0.0"
},
"dependencies": {
"uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
}
}
},
"markdown-it": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-3.1.0.tgz",
"integrity": "sha1-IKcejmexKXyWrEfQD3tuaQ1uDDY=",
"requires": {
"argparse": "~ 1.0.0",
"linkify-it": "~ 0.1.2",
"mdurl": "~ 1.0.0",
"uc.micro": "~ 0.1.0"
}
},
"mdurl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
},
"mustache": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz",
"integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ=="
},
"ncp": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/ncp/-/ncp-0.4.2.tgz",
"integrity": "sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ="
},
"node-uuid": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
"integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc="
},
"rimraf": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
"integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI="
},
"secret-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/secret-utils/-/secret-utils-1.0.2.tgz",
"integrity": "sha1-88GhRhCpTH+gMX3RVqj+AJgDb6c=",
"requires": {
"crypto-rand": "0.0.2",
"urlsafe-base64": "0.0.2"
}
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"uc.micro": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-0.1.0.tgz",
"integrity": "sha1-7aESHR/blhVO1v3oJHu724MzCMo="
},
"urlsafe-base64": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-0.0.2.tgz",
"integrity": "sha1-+VqmedXqb86RQtY8tXCd4x7r7UI="
},
"walk": {
"version": "2.3.14",
"resolved": "https://registry.npmjs.org/walk/-/walk-2.3.14.tgz",
"integrity": "sha512-5skcWAUmySj6hkBdH6B6+3ddMjVQYH5Qy9QGbPmN8kVmLteXk+yVXg+yfk1nbX30EYakahLrr8iPcCxJQSCBeg==",
"requires": {
"foreachasync": "^3.0.0"
},
"dependencies": {
"foreachasync": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz",
"integrity": "sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY="
}
}
}
}
}

1
package.json

@ -4,6 +4,7 @@
"description": "An in-browser static blog library and static site generator. Similar to Jekyll, Octopress, Nanoc, etc",
"main": "desirae.js",
"scripts": {
"prettier": "prettier --write './**/*.{js,css,html,json,md,py,xml}'",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {

107
tests/permalink.js

@ -1,4 +1,4 @@
'use strict';
"use strict";
// http://ruhoh.com/docs/2/pages/#toc_41
/*
@ -6,32 +6,31 @@
Otherwise it should use the the permalink for that collection.
*/
var tags
, permalinkTransforms
, cases
, path = /*exports.path ||*/ require('path')
;
var tags,
permalinkTransforms,
cases,
path = /*exports.path ||*/ require("path");
tags = {
year: "Year from the page’s filename"
, month: "Month from the page’s filename"
, day: "Day from the page’s filename"
, path: "The page file's path relative to the base of your website."
, relative_path: "The page file's path relative to its name-spaced directory."
, filename: "The page file's filename (path is not included)."
, categories: "The specified categories for this page. If more than one category is set, only the first one is used. If no categories exist, the URL omits this parameter."
, i_month: "Month from the page’s filename without leading zeros."
, i_day: "Day from the page’s filename without leading zeros."
, title: "The title, as a slug."
, slug: "alias of title"
, name: "alias of title"
, collection: "i.e. posts/ or essays/ or whatever/"
year: "Year from the page’s filename",
month: "Month from the page’s filename",
day: "Day from the page’s filename",
path: "The page file's path relative to the base of your website.",
relative_path: "The page file's path relative to its name-spaced directory.",
filename: "The page file's filename (path is not included).",
categories:
"The specified categories for this page. If more than one category is set, only the first one is used. If no categories exist, the URL omits this parameter.",
i_month: "Month from the page’s filename without leading zeros.",
i_day: "Day from the page’s filename without leading zeros.",
title: "The title, as a slug.",
slug: "alias of title",
name: "alias of title",
collection: "i.e. posts/ or essays/ or whatever/",
};
function pad(str, n) {
str = str.toString();
if (str.length < n) {
str = '0' + str;
str = "0" + str;
}
return str;
@ -39,49 +38,51 @@ function pad(str, n) {
// https://www.youtube.com/watch?v=1NryFD9_hR0&list=RDOeLUK4a6Ojc&index=2
cases = {
"/:title.html" : "/my-title.html"
, ":title/" : "/my-title/"
, "/:bad/:title/" : "/:bad/my-title/"
, "/:slug/" : "/my-title/"
, "/:path/:name.html" : "/posts/fun/my-title.html"
, "/:relative_path/:name/" : "/fun/my-title/"
, "/:year-:month-:day/:name" : "/2015-07-04/my-title/"
, "/:year/:i_month/:i_day/:name" : "/2015/7/4/my-title/"
, "/:filename.html" : "/my-file-name.html"
, "/:filename" : "/my-file-name/"
, "/:filename/" : "/my-file-name/"
, "/:collection/:title/" : "/posts/my-title/"
, "/:collection/:filename" : "/posts/my-file-name/"
, "/:something/:or/:other" : "/:something/:or/:other/"
, "/:categories/:title/" : "/desi/my-title/"
"/:title.html": "/my-title.html",
":title/": "/my-title/",
"/:bad/:title/": "/:bad/my-title/",
"/:slug/": "/my-title/",
"/:path/:name.html": "/posts/fun/my-title.html",
"/:relative_path/:name/": "/fun/my-title/",
"/:year-:month-:day/:name": "/2015-07-04/my-title/",
"/:year/:i_month/:i_day/:name": "/2015/7/4/my-title/",
"/:filename.html": "/my-file-name.html",
"/:filename": "/my-file-name/",
"/:filename/": "/my-file-name/",
"/:collection/:title/": "/posts/my-title/",
"/:collection/:filename": "/posts/my-file-name/",
"/:something/:or/:other": "/:something/:or/:other/",
"/:categories/:title/": "/desi/my-title/",
};
Object.keys(cases).forEach(function (tpl) {
var entity
, tpld
;
var entity, tpld;
entity = {
year : '2015'
, month : '07'
, day : '04'
, title : "My Title"
, slug : "my-title"
, name : "My-File-Name.html"
, relativePath : "posts/fun"
, path : "posts/fun/My-File-Name.html"
, collection : "posts"
, yml : { categories: ['desi'] }
year: "2015",
month: "07",
day: "04",
title: "My Title",
slug: "my-title",
name: "My-File-Name.html",
relativePath: "posts/fun",
path: "posts/fun/My-File-Name.html",
collection: "posts",
yml: { categories: ["desi"] },
};
tpld = permalinker(tpl, entity);
if (cases[tpl] !== tpld) {
console.error('[ERROR]');
console.error(tpl + ' ' + tpld + ' ' + cases[tpl]);
console.error("[ERROR]");
console.error(tpl + " " + tpld + " " + cases[tpl]);
throw new Error(
"Did not template permalink correctly. "
+ tpl + ' ' + tpld + ' ' + cases[tpl]
"Did not template permalink correctly. " +
tpl +
" " +
tpld +
" " +
cases[tpl]
);
}
});

Loading…
Cancel
Save