Compare commits

...

13 Commits

Author SHA1 Message Date
AJ ONeal 706111f8ba fix directory redirects 2020-11-11 17:47:50 -07:00
AJ ONeal b0a56bec64 0.12.4 2020-11-08 21:51:34 -07:00
AJ ONeal 5ee8ea9b60 bugfix Promise and fs 2020-11-08 21:51:35 -07:00
AJ ONeal 215cef976f update author info 2020-11-08 20:29:39 -07:00
AJ ONeal ceb90c2bfa 0.12.0 2020-11-08 20:16:59 -07:00
AJ ONeal b5223b1053 remove deps, update to valid SPDX license 2020-11-08 20:16:55 -07:00
AJ ONeal a9cb7a58a3 0.11.6 2020-11-08 20:03:42 -07:00
AJ ONeal c81ff7a441 bugfix missing navigation links 2020-11-08 20:03:30 -07:00
AJ ONeal 9131fb9a42 make Prettier 2020-11-08 20:01:26 -07:00
AJ ONeal 0909c8e9cc v0.11.5: merge some leftovers 2018-11-07 10:38:50 -07:00
AJ ONeal 60e2764a1b Merge branch 'v1.0' 2018-11-07 10:38:12 -07:00
AJ ONeal d77b91e27a v0.11.4: update mustache dep 2018-11-07 10:27:39 -07:00
AJ ONeal eaf0c9bfba v0.11.3: update URL 2018-11-07 10:16:39 -07:00
25 changed files with 1689 additions and 1720 deletions

1
.prettierrc.json Normal file
View File

@ -0,0 +1 @@
{}

94
DATA.md
View File

@ -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. 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. 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) 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 = {}
``` ```
desi # desi
====
* `config` - literally `config.yml`, parsed - `config` - literally `config.yml`, parsed
* `site` - literally `site.yml`, parsed - `site` - literally `site.yml`, parsed
* `authors` - literally the authors from `authors/*.yml`, parsed - `authors` - literally the authors from `authors/*.yml`, parsed
* `author` - the primary author of the site - `author` - the primary author of the site
* `env` - urls and paths for this build (be it production, development, staging, etc) - `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) - `content` - pre-rendered content (i.e. content rendered into the post layout rendered into the default layout)
* `collection` - config related to this collection - `collection` - config related to this collection
* `entity` - the page, post, article, etc that is the focus of the present template process - `entity` - the page, post, article, etc that is the focus of the present template process
* `themes` - all themes - `themes` - all themes
* `theme` - the default theme - `theme` - the default theme
* `layout` - the selected layout for this theme - `layout` - the selected layout for this theme
* `satch` - the selected swatch for this theme - `satch` - the selected swatch for this theme
* `categories` - all categories - `categories` - all categories
* `tags` - all tags - `tags` - all tags
* `styles` - ??? goes into the final template in the head - `styles` - ??? goes into the final template in the head
* `scripts` - ?? that goes into the final template just before the body close - `scripts` - ?? that goes into the final template just before the body close
desi.entity # desi.entity
===========
stuff stuff
* `uuid` - `uuid`
* `title` - `title`
* `disqus_url` - `disqus_url`
* `disqus_identifier` - `disqus_identifier`
more stuff more stuff
* `type` - `post`, `page`, etc - `type` - `post`, `page`, etc
* `authors` - literally the relevant authors from `authors/*.yml`, parsed - `authors` - literally the relevant authors from `authors/*.yml`, parsed
* `author` - the primary author of this entity - `author` - the primary author of this entity
* `theme` - null or a non-default theme - `theme` - null or a non-default theme
* `layout` - null or a non-default layout for this theme - `layout` - null or a non-default layout for this theme
* `swatch` - null or a non-default swatch for this theme - `swatch` - null or a non-default swatch for this theme
* `categories`: [] // *all* categories in all collections - `categories`: [] // _all_ categories in all collections
* `tags`: [] // *all* categories in all collections - `tags`: [] // _all_ categories in all collections
* `production_canonical_url` the PRODUCTION canonical_url for this entity - `production_canonical_url` the PRODUCTION canonical_url for this entity
* `production_url` the PRODUCTION url for this entity - `production_url` the PRODUCTION url for this entity
* `production_path` the PRODUCTION path for this entity - `production_path` the PRODUCTION path for this entity
* `url` the full url in the current environment (might be production, development, etc) - `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`) - `path` the non-host part (i.e. `/compiled_dev/articles/my-first-post.html`)
* `previous` the previous entity in this collection - `previous` the previous entity in this collection
* `next` the next entitiy in this collection - `next` the next entitiy in this collection
NOTE: Plugins, widgets, etc SHOULD NOT modify config, site, authors, author, or env. NOTE: Plugins, widgets, etc SHOULD NOT modify config, site, authors, author, or env.
desi.posts # desi.posts
==========
, posts: { collated: desi.collated } , posts: { collated: desi.collated }
desi.config # desi.config
===========
desi.site # desi.site
===========
desi.env # desi.env
===========

View File

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

View File

@ -1,14 +1,12 @@
Glossary # Glossary
========
Canonical URL ## Canonical URL
--------
base\_url + base\_path + permalink
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 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 / 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/ In most cases that would be / or /blog/
It DOES include BOTH a LEADING and TRAILING slash. 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. The permalink is the permanent part of the URL, after the path to the blog.
For example: For example:
* http://blog.johndoe.com/articles/first-post.html the permalink is 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://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://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) 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 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. once again.
base\_url the base_url the
permalink refers to permalink refers to

206
LICENSE
View File

@ -1,202 +1,4 @@
Apache License This Source Code Form is subject to the terms of the Mozilla
Version 2.0, January 2004 Public License, v. 2.0. If a copy of the MPL was not distributed
http://www.apache.org/licenses/ with this file, You can obtain one at
https://mozilla.org/MPL/2.0/.
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

210
README.md
View File

@ -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 If you're looking for [DearDesi](http://dear.desi), the DIY blog platform for normal people
you should go to <http://dear.desi>. 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 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!) 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. 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:** **Features:**
* `JavaScript` - it's so stable it takes 10 years to get new any 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) - Won't break every time you upgrade OS X and reinstall `brew` (_cough_ ruby)
* Decent use of `try { ... } catch(e) ...` and `promise.catch()` - 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 - 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) - Browser (optional)
* using your front-end templates to build in your front-end? Imagine that! - using your front-end templates to build in your front-end? Imagine that!
* io.js (node.js) (optional) - io.js (node.js) (optional)
* if you'd prefer to go headless, you can. - 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). - The server is _very_ minimal and could easily be implemented in any language (such as ruby or python).
Installation # Installation
============
```bash ```bash
bower install --save desirae bower install --save desirae
@ -38,8 +35,7 @@ bower install --save desirae
npm install --save desirae npm install --save desirae
``` ```
Why # Why
===
Because I hate ruby. 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 versions and gems and whatnot... so I gave up and wrote my own (because I needed something
compatible with ruhoh). compatible with ruhoh).
Usage Overview # Usage Overview
==============
### Before we get started ### Before we get started
(disclaimers) (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 **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 (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 ### 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 ```javascript
var desi = {} var desi = {};
;
``` ```
After that you'll load any plugins you need. 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 ```javascript
Desirae.init( Desirae.init(desi, {
desi url: "https://johndoe.exmaple.com/blog",
, { url: 'https://johndoe.exmaple.com/blog' base_url: "https://johndoe.exmaple.com",
, base_url: 'https://johndoe.exmaple.com' base_path: "/blog",
, base_path: '/blog' compiled_path: "compiled_dev",
, compiled_path: 'compiled_dev'
// default: continue when possible // default: continue when possible
, onError: function (e) { onError: function (e) {
return Promise.reject(e); return Promise.reject(e);
} },
// io.js / node.js only // io.js / node.js only
, working_path: './path/to/blog' working_path: "./path/to/blog",
} }).then(function () {
).then(function () { console.log("Desirae is initialized");
console.log('Desirae is initialized');
}); });
``` ```
@ -198,43 +190,38 @@ Desirae.write(desi, env).then(function () {
}); });
``` ```
### Plugins ### Plugins
You need to start every file with a wrapper that is browser and io.js/node.js compatible You need to start every file with a wrapper that is browser and io.js/node.js compatible
```javascript ```javascript
/*jshint -W054 */ /*jshint -W054 */
;(function (exports) { (function (exports) {
'use strict'; "use strict";
var DesiraeMyModule = {}
;
var DesiraeMyModule = {};
// ... a bunch of code ... // ... a bunch of code ...
DesiraeMyModule.doStuff = doStuff; DesiraeMyModule.doStuff = doStuff;
exports.DesiraeMyModule = DesiraeMyModule.DesiraeMyModule = DesiraeMyModule; 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 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. so steer away from things that are super iojs/node-ish or super window-ish.
Configuration # Configuration
=============
There are a few configuration files: There are a few configuration files:
* `site.yml` is stuff that might be unique to your site, such as (title, url, adwords id, etc) - `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) - `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. - `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. 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, 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) 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) ### (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` For example, if you want to add the ability to render from `slim` or `haml`
instead of just `markdown` you could find the appropriate 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 module (or make requests to an API service that renders them for you) and do this
```javascript ```javascript
var slim = exports.slimjs || require('slimjs') var slim = exports.slimjs || require("slimjs");
; function render(contentstr /*, desi*/) {
return Promise.resolve(slim(contentstr));
function render(contentstr/*, desi*/) {
return PromiseA.resolve(slim(contentstr));
} }
Desirae.registerRenderer('.slim', render); Desirae.registerRenderer(".slim", render);
``` ```
## Data Mapping ## Data Mapping
### (desirae, ruhoh, etc) ### (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 If you want to use a non-desirae theme that uses attributes in a different than
how Desirae creates the view object internally 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. 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 ```javascript
Desirae.registerDataMapper('ruhoh@3.0', function (view) { Desirae.registerDataMapper("ruhoh@3.0", function (view) {
return { return {
page: { page: {
name: view.entity.title name: view.entity.title,
} },
, author: { author: {
nickname: view.author.twitter 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. 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. 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. 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` `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 - `dir` **must** be supplied. returns a flat list of all files, recursively
* `dotfiles` default to `false`. includes dotfiles when `true`. - `dotfiles` default to `false`. includes dotfiles when `true`.
* `extensions` defaults to `null`. inclode **only** the supplied extensions when `true`. - `extensions` defaults to `null`. inclode **only** the supplied extensions when `true`.
```json ```json
[ [
{ "name": "happy-new-year.md" {
, "createdDate": "2015-01-05T18:19:30.000Z" "name": "happy-new-year.md",
, "lastModifiedDate": "2015-01-05T18:19:30.000Z" "createdDate": "2015-01-05T18:19:30.000Z",
, "size": 2121 "lastModifiedDate": "2015-01-05T18:19:30.000Z",
, "relativePath": "posts/2015" "size": 2121,
} "relativePath": "posts/2015"
},
, { "name": "tips-for-the-ages.jade" {
, "createdDate": "2014-06-16T18:19:30.000Z" "name": "tips-for-the-ages.jade",
, "lastModifiedDate": "2014-06-16T18:19:30.000Z" "createdDate": "2014-06-16T18:19:30.000Z",
, "size": 389 "lastModifiedDate": "2014-06-16T18:19:30.000Z",
, "relativePath": "posts" "size": 389,
} "relativePath": "posts"
, { "name": "my-first-post.html" },
, "createdDate": "2013-08-01T22:47:37.000Z" {
, "lastModifiedDate": "2013-08-01T22:47:37.000Z" "name": "my-first-post.html",
, "size": 4118 "createdDate": "2013-08-01T22:47:37.000Z",
, "relativePath": "posts/2013" "lastModifiedDate": "2013-08-01T22:47:37.000Z",
"size": 4118,
"relativePath": "posts/2013"
} }
] ]
``` ```
To retrieve multiple dir listings at once: 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` `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 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` `GET http://local.dear.desi:8080/api/fs/files?path=posts/happy-new-year.md`
```json ```json
{ "path": "posts/intro-to-http-with-netcat-node-connect.md" {
, "createdDate": "2013-08-01T22:47:37.000Z" "path": "posts/intro-to-http-with-netcat-node-connect.md",
, "lastModifiedDate": "2013-08-01T22:47:37.000Z" "createdDate": "2013-08-01T22:47:37.000Z",
, "contents": "..." "lastModifiedDate": "2013-08-01T22:47:37.000Z",
, "sha1": "6eae3a5b062c6d0d79f070c26e6d62486b40cb46" "contents": "...",
"sha1": "6eae3a5b062c6d0d79f070c26e6d62486b40cb46"
} }
``` ```
To retrieve multiple files at once: 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` `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 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 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). and return an error if you try to write to any other directory, unless `compiled=false` (not yet implemented).
@ -452,9 +436,15 @@ The response may include errors of all shapes and sizes.
} }
``` ```
POST /api/fs/copy ## POST /api/fs/copy
------------------
```json ```json
{ files: { "assets/logo.png": "compiled/assets/logo.png" } } { "files": { "assets/logo.png": "compiled/assets/logo.png" } }
``` ```
# License
This Source Code Form is subject to the terms of the Mozilla \
Public License, v. 2.0. If a copy of the MPL was not distributed \
with this file, You can obtain one at \
https://mozilla.org/MPL/2.0/.

46
TODO.md
View File

@ -4,7 +4,6 @@ show file path
show prod url show prod url
show dev url show dev url
POST tests POST tests
create a title and delete it (no error) create a title and delete it (no error)
change the format. does the permalink change? (yes) 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 and delete it (no error)
create a description. does the frontmatter change? (yes) create a description. does the frontmatter change? (yes)
protection protection
Don't allow changing the uuid, original_url, or original_date Don't allow changing the uuid, original_url, or original_date
TODO ## TODO
---
check that no other post uses the same permalink check that no other post uses the same permalink
default data-model 'ruhoh@2.2' default data-model 'ruhoh@2.2'
other data-model 'desirae@1.0' other data-model 'desirae@1.0'
Widgets # Widgets
=======
All widgets should export an object with a `create(widgetConf, desiState)` function that returns a promise. 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 # stuff like google ad and disqus ids should go in config.yml or data.yml
config: config:
foobeep: boop foobeep: boop
handle: handle:
- html - html
- markdown - markdown
@ -52,11 +46,10 @@ widgets:
``` ```
```javascript ```javascript
'use strict'; "use strict";
module.exports.Foogizmo.create = function (foogizmoConf, desiState) { module.exports.Foogizmo.create = function (foogizmoConf, desiState) {
return new Promise(function (resolve) { return new Promise(function (resolve) {
function pager(desiPageState) { function pager(desiPageState) {
// Do processing // Do processing
@ -68,29 +61,28 @@ module.exports.Foogizmo.create = function (foogizmoConf, desiState) {
desiPostState.fooembedinator = function (fooval) { desiPostState.fooembedinator = function (fooval) {
// figure out what type of link fooval is and return iframe html // 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 }); 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 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. 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`) Instead of having special names for some properties (`_root`)
and `use` sub attributes for others (`twitter` theme, posts directory), 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. 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 No changes
## config.ru
config.ru
---------
REMOVED (ruby only) REMOVED (ruby only)
themes layout ## themes layout
-------------
TODO TODO

View File

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

1159
desirae.js

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -1,7 +1,9 @@
'use strict'; "use strict";
module.exports.convert = function () { 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"); console.error("see https://github.com/coolaj86/nuhoh/tree/master/MIGRATE.md");
throw new Error('Not Implemented.'); throw new Error("Not Implemented.");
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
;(function (exports) { (function (exports) {
'use strict'; "use strict";
exports.verifyConfig = function (conf) { exports.verifyConfig = function (conf) {
if (!conf.NuhohSpec) { if (!conf.NuhohSpec) {
@ -28,14 +28,18 @@
if (!Array.isArray(conf.collections)) { if (!Array.isArray(conf.collections)) {
if (conf.posts) { 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'."); throw new Error("missing key 'collections'.");
} }
if (!conf.themes) { if (!conf.themes) {
if (conf.twitter) { 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'"); throw new Error("missing key 'themes'");
} }
@ -55,4 +59,4 @@
throw new Error("missing key root"); throw new Error("missing key root");
} }
}; };
}('undefined' !== typeof exports && exports || window)); })(("undefined" !== typeof exports && exports) || window);

175
package-lock.json generated Normal file
View File

@ -0,0 +1,175 @@
{
"name": "desirae",
"version": "0.12.4",
"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"
}
},
"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="
},
"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="
}
}
}
}
}

View File

@ -1,14 +1,15 @@
{ {
"name": "desirae", "name": "desirae",
"version": "0.11.2", "version": "0.12.4",
"description": "An in-browser static blog library and static site generator. Similar to Jekyll, Octopress, Nanoc, etc", "description": "An in-browser static blog library and static site generator. Similar to Jekyll, Octopress, Nanoc, etc",
"main": "desirae.js", "main": "desirae.js",
"scripts": { "scripts": {
"prettier": "prettier --write './**/*.{js,css,html,json,md,py,xml}'",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"repository": { "repository": {
"type": "git", "type": "git+https",
"url": "git@git.daplie.com:Daplie/desirae.git" "url": "https://git.coolaj86.com/coolaj86/desirae.js.git"
}, },
"keywords": [ "keywords": [
"dear", "dear",
@ -22,21 +23,19 @@
"octopress", "octopress",
"nanoc" "nanoc"
], ],
"author": "AJ ONeal", "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "Apache2", "license": "MPL-2.0",
"bugs": { "bugs": {
"url": "https://git.daplie.com/Daplie/desirae/issues" "url": "https://git.coolaj86.com/coolaj86/desirae.js/issues"
}, },
"homepage": "https://git.daplie.com/Daplie/desirae", "homepage": "https://git.coolaj86.com/coolaj86/desirae.js",
"dependencies": { "dependencies": {
"bluebird": "^2.5.3",
"escape-string-regexp": "^1.0.2", "escape-string-regexp": "^1.0.2",
"foreachasync": "^5.0.5", "foreachasync": "^5.0.5",
"fs.extra": "^1.3.2", "fs.extra": "^1.3.2",
"js-yaml": "^3.2.5", "js-yaml": "^3.2.5",
"markdown-it": "^3.0.2", "markdown-it": "^3.0.2",
"mkdirp": "^0.5.0", "mustache": "^2.3.2",
"mustache": "^1.0.0",
"node-uuid": "^1.4.2", "node-uuid": "^1.4.2",
"secret-utils": "^1.0.2", "secret-utils": "^1.0.2",
"walk": "^2.3.9" "walk": "^2.3.9"

View File

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