Compare commits
	
		
			No commits in common. "master" and "v0.9.4" have entirely different histories.
		
	
	
		
	
		
@ -1 +0,0 @@
 | 
			
		||||
{}
 | 
			
		||||
							
								
								
									
										94
									
								
								DATA.md
									
									
									
									
									
								
							
							
						
						
									
										94
									
								
								DATA.md
									
									
									
									
									
								
							@ -1,10 +1,12 @@
 | 
			
		||||
# Data
 | 
			
		||||
Data
 | 
			
		||||
====
 | 
			
		||||
 | 
			
		||||
Every template gets an object with the exact same structure - whether it's a template or a widget or a page or a post.
 | 
			
		||||
 | 
			
		||||
Here we only document Desirae's default behavior, but there are many objects added for compatibility with Ruhoh that are not documented.
 | 
			
		||||
 | 
			
		||||
## config.yml vs site.yml
 | 
			
		||||
config.yml vs site.yml
 | 
			
		||||
------
 | 
			
		||||
 | 
			
		||||
site.yml is for anything that changes the content of the site (navigation, title, analytic and ad ids, default author, etc)
 | 
			
		||||
 | 
			
		||||
@ -14,60 +16,66 @@ config.yml is for anything that doesn't change the site (from where to read dire
 | 
			
		||||
desi = {}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# desi
 | 
			
		||||
desi
 | 
			
		||||
====
 | 
			
		||||
 | 
			
		||||
- `config` - literally `config.yml`, parsed
 | 
			
		||||
- `site` - literally `site.yml`, parsed
 | 
			
		||||
- `authors` - literally the authors from `authors/*.yml`, parsed
 | 
			
		||||
- `author` - the primary author of the site
 | 
			
		||||
- `env` - urls and paths for this build (be it production, development, staging, etc)
 | 
			
		||||
- `content` - pre-rendered content (i.e. content rendered into the post layout rendered into the default layout)
 | 
			
		||||
- `collection` - config related to this collection
 | 
			
		||||
- `entity` - the page, post, article, etc that is the focus of the present template process
 | 
			
		||||
- `themes` - all themes
 | 
			
		||||
- `theme` - the default theme
 | 
			
		||||
- `layout` - the selected layout for this theme
 | 
			
		||||
- `satch` - the selected swatch for this theme
 | 
			
		||||
- `categories` - all categories
 | 
			
		||||
- `tags` - all tags
 | 
			
		||||
- `styles` - ??? goes into the final template in the head
 | 
			
		||||
- `scripts` - ?? that goes into the final template just before the body close
 | 
			
		||||
* `config` - literally `config.yml`, parsed
 | 
			
		||||
* `site` - literally `site.yml`, parsed
 | 
			
		||||
* `authors` - literally the authors from `authors/*.yml`, parsed
 | 
			
		||||
* `author` - the primary author of the site
 | 
			
		||||
* `env` - urls and paths for this build (be it production, development, staging, etc)
 | 
			
		||||
* `content` - pre-rendered content (i.e. content rendered into the post layout rendered into the default layout)
 | 
			
		||||
* `collection` - config related to this collection
 | 
			
		||||
* `entity` - the page, post, article, etc that is the focus of the present template process
 | 
			
		||||
* `themes` - all themes
 | 
			
		||||
* `theme` - the default theme
 | 
			
		||||
* `layout` - the selected layout for this theme
 | 
			
		||||
* `satch` - the selected swatch for this theme
 | 
			
		||||
* `categories` - all categories
 | 
			
		||||
* `tags` - all tags
 | 
			
		||||
* `styles` - ??? goes into the final template in the head
 | 
			
		||||
* `scripts` - ?? that goes into the final template just before the body close
 | 
			
		||||
 | 
			
		||||
# desi.entity
 | 
			
		||||
desi.entity
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
stuff
 | 
			
		||||
 | 
			
		||||
- `uuid`
 | 
			
		||||
- `title`
 | 
			
		||||
- `disqus_url`
 | 
			
		||||
- `disqus_identifier`
 | 
			
		||||
* `uuid`
 | 
			
		||||
* `title`
 | 
			
		||||
* `disqus_url`
 | 
			
		||||
* `disqus_identifier`
 | 
			
		||||
 | 
			
		||||
more stuff
 | 
			
		||||
 | 
			
		||||
- `type` - `post`, `page`, etc
 | 
			
		||||
- `authors` - literally the relevant authors from `authors/*.yml`, parsed
 | 
			
		||||
- `author` - the primary author of this entity
 | 
			
		||||
- `theme` - null or a non-default theme
 | 
			
		||||
- `layout` - null or a non-default layout for this theme
 | 
			
		||||
- `swatch` - null or a non-default swatch for this theme
 | 
			
		||||
- `categories`: [] // _all_ categories in all collections
 | 
			
		||||
- `tags`: [] // _all_ categories in all collections
 | 
			
		||||
- `production_canonical_url` the PRODUCTION canonical_url for this entity
 | 
			
		||||
- `production_url` the PRODUCTION url for this entity
 | 
			
		||||
- `production_path` the PRODUCTION path for this entity
 | 
			
		||||
- `url` the full url in the current environment (might be production, development, etc)
 | 
			
		||||
- `path` the non-host part (i.e. `/compiled_dev/articles/my-first-post.html`)
 | 
			
		||||
- `previous` the previous entity in this collection
 | 
			
		||||
- `next` the next entitiy in this collection
 | 
			
		||||
* `type` - `post`, `page`, etc
 | 
			
		||||
* `authors` - literally the relevant authors from `authors/*.yml`, parsed
 | 
			
		||||
* `author` - the primary author of this entity
 | 
			
		||||
* `theme` - null or a non-default theme
 | 
			
		||||
* `layout` - null or a non-default layout for this theme
 | 
			
		||||
* `swatch` - null or a non-default swatch for this theme
 | 
			
		||||
* `categories`: []    // *all* categories in all collections
 | 
			
		||||
* `tags`: []    // *all* categories in all collections
 | 
			
		||||
* `production_canonical_url` the PRODUCTION canonical_url for this entity
 | 
			
		||||
* `production_url` the PRODUCTION url for this entity
 | 
			
		||||
* `production_path` the PRODUCTION path for this entity
 | 
			
		||||
* `url` the full url in the current environment (might be production, development, etc)
 | 
			
		||||
* `path` the non-host part (i.e. `/compiled_dev/articles/my-first-post.html`)
 | 
			
		||||
* `previous` the previous entity in this collection
 | 
			
		||||
* `next` the next entitiy in this collection
 | 
			
		||||
 | 
			
		||||
NOTE: Plugins, widgets, etc SHOULD NOT modify config, site, authors, author, or env.
 | 
			
		||||
 | 
			
		||||
# desi.posts
 | 
			
		||||
desi.posts
 | 
			
		||||
==========
 | 
			
		||||
 | 
			
		||||
      , posts: { collated: desi.collated }
 | 
			
		||||
 | 
			
		||||
# desi.config
 | 
			
		||||
desi.config
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
# desi.site
 | 
			
		||||
desi.site
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
# desi.env
 | 
			
		||||
desi.env
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										66
									
								
								ENTITY.md
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								ENTITY.md
									
									
									
									
									
								
							@ -1,66 +0,0 @@
 | 
			
		||||
This is what an entity looks like:
 | 
			
		||||
 | 
			
		||||
```yml
 | 
			
		||||
# inherited from File Entity
 | 
			
		||||
path: My Posts/My-Old-Name.html
 | 
			
		||||
lastModifiedDate: 2015-07-04T13:56:01Z
 | 
			
		||||
createdDate: 2015-07-04T13:56:01Z
 | 
			
		||||
contents: "..." # whatever the file is
 | 
			
		||||
 | 
			
		||||
# inherited from Collection Entity
 | 
			
		||||
name: My-Old-Name.html
 | 
			
		||||
relativePath: My Posts
 | 
			
		||||
ext: .html
 | 
			
		||||
collection: posts
 | 
			
		||||
 | 
			
		||||
# inherited from Content Entity
 | 
			
		||||
frontmatter: '---\n...\n---' # frontmatter as a string
 | 
			
		||||
yml: {} # frontmatter, parsed
 | 
			
		||||
body: "I think ..." # body, after frontmatter
 | 
			
		||||
 | 
			
		||||
# inherited from Normalized Entity
 | 
			
		||||
title: My Title # yml.title | titlize(entity.name)
 | 
			
		||||
slug: my-title # slugify(title)
 | 
			
		||||
slug_path: my-posts # slugifyPath(relativePath)
 | 
			
		||||
 | 
			
		||||
year: 2014
 | 
			
		||||
month: 07
 | 
			
		||||
day: 04
 | 
			
		||||
hour: 13
 | 
			
		||||
twelve_hour: 1
 | 
			
		||||
meridian: pm
 | 
			
		||||
minute: 22
 | 
			
		||||
 | 
			
		||||
categories: ["tech"]
 | 
			
		||||
tags:
 | 
			
		||||
  ["http", "url", "website"]
 | 
			
		||||
 | 
			
		||||
  # includes index.html
 | 
			
		||||
relative_file:
 | 
			
		||||
  /posts/foo/index.html
 | 
			
		||||
 | 
			
		||||
  # excludes index.html
 | 
			
		||||
relative_href:
 | 
			
		||||
  /posts/foo/
 | 
			
		||||
 | 
			
		||||
  # actual url of this file, even if redirect
 | 
			
		||||
  # excludes index.html
 | 
			
		||||
url:
 | 
			
		||||
  http://dev.example.com/posts/foo/
 | 
			
		||||
 | 
			
		||||
  # the appropriate url, even in a redirect or duplicate
 | 
			
		||||
  # excludes index.html
 | 
			
		||||
canonical_url:
 | 
			
		||||
  http://dev.example.com/posts/foo/
 | 
			
		||||
 | 
			
		||||
  # production url, even in development (for disqus, etc)
 | 
			
		||||
  # excludes index.html
 | 
			
		||||
production_url: http://example.com/posts/foo/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Note: The option `env.explicitIndexes` turns on `/index.html`. This option is automatically turned on when Dropbox is the host.
 | 
			
		||||
 | 
			
		||||
## TODO
 | 
			
		||||
 | 
			
		||||
- path relative from / in the browser
 | 
			
		||||
- path relative from base_path on the file system
 | 
			
		||||
							
								
								
									
										44
									
								
								GLOSSARY.md
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								GLOSSARY.md
									
									
									
									
									
								
							@ -1,44 +0,0 @@
 | 
			
		||||
# Glossary
 | 
			
		||||
 | 
			
		||||
## Canonical URL
 | 
			
		||||
 | 
			
		||||
base_url + base_path + permalink
 | 
			
		||||
 | 
			
		||||
## Base URL
 | 
			
		||||
 | 
			
		||||
base_url is the point of ownership
 | 
			
		||||
 | 
			
		||||
In most cases that would be https://johndoe.com
 | 
			
		||||
 | 
			
		||||
In some cases that might be https://school.edu/~/johndoe
 | 
			
		||||
 | 
			
		||||
It does NOT include a trailing /
 | 
			
		||||
 | 
			
		||||
## Base Path
 | 
			
		||||
 | 
			
		||||
base_path is the blog directory
 | 
			
		||||
 | 
			
		||||
In most cases that would be / or /blog/
 | 
			
		||||
 | 
			
		||||
It DOES include BOTH a LEADING and TRAILING slash.
 | 
			
		||||
 | 
			
		||||
In the case of https://school.edu/~/johndoe/weblog, the base_path would be /weblog/.
 | 
			
		||||
 | 
			
		||||
## Permalink
 | 
			
		||||
 | 
			
		||||
The permalink is the permanent part of the URL, after the path to the blog.
 | 
			
		||||
 | 
			
		||||
For example:
 | 
			
		||||
 | 
			
		||||
- http://blog.johndoe.com/articles/first-post.html the permalink is articles/first-post.html
 | 
			
		||||
- http://johndoe.com/blog/articles/first-post.html the permalink is still articles/first-post.html
 | 
			
		||||
- http://school.edu/~/johndoe/blog/articles/first-post.html the permalink is yet still articles/first-post.html
 | 
			
		||||
 | 
			
		||||
The permalink is ALWAYS RELATIVE (no leading slash)
 | 
			
		||||
 | 
			
		||||
It is designed so that if you ever move your blog from one domain, point of ownership, or path to a new one,
 | 
			
		||||
a very simple one-line redirect can be made to your webserver and all of the posts will end up in the right place
 | 
			
		||||
once again.
 | 
			
		||||
 | 
			
		||||
base_url the
 | 
			
		||||
permalink refers to
 | 
			
		||||
							
								
								
									
										206
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										206
									
								
								LICENSE
									
									
									
									
									
								
							@ -1,4 +1,202 @@
 | 
			
		||||
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/.
 | 
			
		||||
Apache License
 | 
			
		||||
                           Version 2.0, January 2004
 | 
			
		||||
                        http://www.apache.org/licenses/
 | 
			
		||||
 | 
			
		||||
   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.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										295
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										295
									
								
								README.md
									
									
									
									
									
								
							@ -1,4 +1,5 @@
 | 
			
		||||
# Did you mean DearDesi?
 | 
			
		||||
Did you mean DearDesi?
 | 
			
		||||
======================
 | 
			
		||||
 | 
			
		||||
If you're looking for [DearDesi](http://dear.desi), the DIY blog platform for normal people
 | 
			
		||||
you should go to <http://dear.desi>.
 | 
			
		||||
@ -6,7 +7,8 @@ you should go to <http://dear.desi>.
 | 
			
		||||
Desirae (this repo) is the code that programmers to use to make DearDesi better
 | 
			
		||||
for everyone and to make Desirae-compatible blog platforms. (it's blog-ception!)
 | 
			
		||||
 | 
			
		||||
# Desirae (v0.9)
 | 
			
		||||
Desirae (v0.9)
 | 
			
		||||
=======
 | 
			
		||||
 | 
			
		||||
Desirae is a static webpage compiler written in JavaScript.
 | 
			
		||||
 | 
			
		||||
@ -17,17 +19,18 @@ It can also run entirely from the commandline (with io.js / node.js).
 | 
			
		||||
 | 
			
		||||
**Features:**
 | 
			
		||||
 | 
			
		||||
- `JavaScript` - it's so stable it takes 10 years to get new any features.
 | 
			
		||||
  - Won't break every time you upgrade OS X and reinstall `brew` (_cough_ ruby)
 | 
			
		||||
- Decent use of `try { ... } catch(e) ...` and `promise.catch()`
 | 
			
		||||
  - the idea is that it shouldn't blow up at the slightest parse error without telling you which page is to blame (_cough_ ruhoh _cough_ jekyll)... bless me
 | 
			
		||||
- Browser (optional)
 | 
			
		||||
  - using your front-end templates to build in your front-end? Imagine that!
 | 
			
		||||
- io.js (node.js) (optional)
 | 
			
		||||
  - if you'd prefer to go headless, you can.
 | 
			
		||||
- The server is _very_ minimal and could easily be implemented in any language (such as ruby or python).
 | 
			
		||||
  * `JavaScript` - it's so stable it takes 10 years to get new any features.
 | 
			
		||||
    * Won't break every time you upgrade OS X and reinstall `brew` (*cough* ruby)
 | 
			
		||||
  * Decent use of `try { ... } catch(e) ...` and `promise.catch()`
 | 
			
		||||
    * the idea is that it shouldn't blow up at the slightest parse error without telling you which page is to blame (*cough* ruhoh *cough* jekyll)... bless me
 | 
			
		||||
  * Browser (optional)
 | 
			
		||||
    * using your front-end templates to build in your front-end? Imagine that!
 | 
			
		||||
  * io.js (node.js) (optional)
 | 
			
		||||
    * if you'd prefer to go headless, you can.
 | 
			
		||||
  * The server is *very* minimal and could easily be implemented in any language (such as ruby or python).
 | 
			
		||||
 | 
			
		||||
# Installation
 | 
			
		||||
Installation
 | 
			
		||||
============
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
bower install --save desirae
 | 
			
		||||
@ -35,7 +38,8 @@ bower install --save desirae
 | 
			
		||||
npm install --save desirae
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# Why
 | 
			
		||||
Why
 | 
			
		||||
===
 | 
			
		||||
 | 
			
		||||
Because I hate ruby.
 | 
			
		||||
 | 
			
		||||
@ -47,7 +51,8 @@ more (last year I made dozens of gists and 0 blog posts) and I just couldn't get
 | 
			
		||||
versions and gems and whatnot... so I gave up and wrote my own (because I needed something
 | 
			
		||||
compatible with ruhoh).
 | 
			
		||||
 | 
			
		||||
# Usage Overview
 | 
			
		||||
Usage Overview
 | 
			
		||||
==============
 | 
			
		||||
 | 
			
		||||
### Before we get started
 | 
			
		||||
 | 
			
		||||
@ -65,109 +70,33 @@ It's an artifact of this project being born out of the ashes of my
 | 
			
		||||
 | 
			
		||||
### Getting Started
 | 
			
		||||
 | 
			
		||||
First off you need to declare a state object that will be used in every _desirae_ action.
 | 
			
		||||
First off you need to declare a state object that will be used in every *desirae* action.
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
var desi = {};
 | 
			
		||||
var desi = {}
 | 
			
		||||
  ;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
After that you'll load any plugins you need.
 | 
			
		||||
 | 
			
		||||
Here's how you would load all of the common plugins:
 | 
			
		||||
After that you'll initialize Desirae with an *environment*.
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
// load the module whether in browser or node
 | 
			
		||||
function dload(filename, exportname) {
 | 
			
		||||
  return dload('undefined' !== typeof window && window[exportname] || require(filename)[exportname];
 | 
			
		||||
}
 | 
			
		||||
//
 | 
			
		||||
// 1. Transform (yml, slug, etc)
 | 
			
		||||
//
 | 
			
		||||
Desi.registerTransform(
 | 
			
		||||
  'lint'
 | 
			
		||||
, dload('desirae/lib/transform-core', 'DesiraeTransformCore').lint
 | 
			
		||||
, { collections: true }
 | 
			
		||||
);
 | 
			
		||||
Desi.registerTransform(
 | 
			
		||||
  'root'
 | 
			
		||||
, dload('desirae/lib/transform-core', 'DesiraeTransformCore').root
 | 
			
		||||
, { root: true }
 | 
			
		||||
);
 | 
			
		||||
Desi.registerTransform(
 | 
			
		||||
  'normalize'
 | 
			
		||||
, dload('desirae/lib/transform-core', 'DesiraeTransformCore').normalize
 | 
			
		||||
, { root: true, collections: true }
 | 
			
		||||
);
 | 
			
		||||
Desi.registerTransform(
 | 
			
		||||
  'disqus'
 | 
			
		||||
, dload('desirae/lib/transform-core', 'DesiraeTransformCore').disqus
 | 
			
		||||
, { collections: true }
 | 
			
		||||
);
 | 
			
		||||
Desirae.init(
 | 
			
		||||
  desi
 | 
			
		||||
, { url:                'https://johndoe.exmaple.com/blog'
 | 
			
		||||
  , base_url:           'https://johndoe.exmaple.com'
 | 
			
		||||
  , base_path:          '/blog'
 | 
			
		||||
  , compiled_path:      'compiled_dev'
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// 2. Register Aggregators (rss, categories, tags, etc)
 | 
			
		||||
//
 | 
			
		||||
Desi.registerAggregator(dload('desirae/lib/aggregate-core', 'DesiraeAggregateCore').collate);
 | 
			
		||||
                        // default: continue when possible
 | 
			
		||||
  , onError:            function (e) {
 | 
			
		||||
                          return Promise.reject(e);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// 3. Register Datamappers (ruhoh, desirae, jade, mustache, liquid)
 | 
			
		||||
//
 | 
			
		||||
Desi.registerDataMapper('desirae', dload('desirae/lib/datamap-core', 'DesiraeDatamapCore'));
 | 
			
		||||
Desi.registerDataMapper('desirae@1.0', dload('desirae/lib/datamap-core', 'DesiraeDatamapCore'));
 | 
			
		||||
Desi.registerDataMapper('ruhoh', dload('desirae-datamap-ruhoh', 'DesiraeDatamapRuhoh'));
 | 
			
		||||
Desi.registerDataMapper('ruhoh@1.0', dload('desirae-datamap-ruhoh', 'DesiraeDatamapRuhoh'));
 | 
			
		||||
Desi.registerDataMapper('ruhoh@2.6', dload('desirae-datamap-ruhoh', 'DesiraeDatamapRuhoh'));
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// 4. Register Renderers (md -> html, less -> css, etc)
 | 
			
		||||
//
 | 
			
		||||
Desi.registerRenderer(
 | 
			
		||||
  'js'
 | 
			
		||||
, dload('desirae/lib/render-core', 'DesiraeRenderJs')
 | 
			
		||||
, { themes: true, assets: true }
 | 
			
		||||
);
 | 
			
		||||
Desi.registerRenderer(
 | 
			
		||||
  'css'
 | 
			
		||||
, dload('desirae/lib/render-core', 'DesiraeRenderCss')
 | 
			
		||||
, { themes: true, assets: true }
 | 
			
		||||
);
 | 
			
		||||
Desi.registerRenderer(
 | 
			
		||||
  'html'
 | 
			
		||||
, dload('desirae/lib/render-core', 'DesiraeRenderHtml')
 | 
			
		||||
, { root: true, collections: true, themes: true, assets: true }
 | 
			
		||||
);
 | 
			
		||||
['md', 'markdown'].forEach(function (ext) {
 | 
			
		||||
  Desi.registerRenderer(
 | 
			
		||||
    ext
 | 
			
		||||
  , dload('desirae/lib/render-core', 'DesiraeRenderMarkdown')
 | 
			
		||||
  , { root: true, collections: true }
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
Desi.registerRenderer(
 | 
			
		||||
  'jade'
 | 
			
		||||
, dload('desirae/lib/render-core', 'DesiraeRenderJade')
 | 
			
		||||
, { root: true, collections: true, themes: true }
 | 
			
		||||
);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
And then you'll initialize Desirae with an _environment_.
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
Desirae.init(desi, {
 | 
			
		||||
  url: "https://johndoe.exmaple.com/blog",
 | 
			
		||||
  base_url: "https://johndoe.exmaple.com",
 | 
			
		||||
  base_path: "/blog",
 | 
			
		||||
  compiled_path: "compiled_dev",
 | 
			
		||||
 | 
			
		||||
  // default: continue when possible
 | 
			
		||||
  onError: function (e) {
 | 
			
		||||
    return Promise.reject(e);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  // io.js / node.js only
 | 
			
		||||
  working_path: "./path/to/blog",
 | 
			
		||||
}).then(function () {
 | 
			
		||||
  console.log("Desirae is initialized");
 | 
			
		||||
                        // io.js / node.js only
 | 
			
		||||
  , working_path:       './path/to/blog'
 | 
			
		||||
  }
 | 
			
		||||
).then(function () {
 | 
			
		||||
  console.log('Desirae is initialized');
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@ -190,38 +119,19 @@ Desirae.write(desi, env).then(function () {
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Plugins
 | 
			
		||||
 | 
			
		||||
You need to start every file with a wrapper that is browser and io.js/node.js compatible
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
/*jshint -W054 */
 | 
			
		||||
(function (exports) {
 | 
			
		||||
  "use strict";
 | 
			
		||||
 | 
			
		||||
  var DesiraeMyModule = {};
 | 
			
		||||
  // ... a bunch of code ...
 | 
			
		||||
 | 
			
		||||
  DesiraeMyModule.doStuff = doStuff;
 | 
			
		||||
 | 
			
		||||
  exports.DesiraeMyModule = DesiraeMyModule.DesiraeMyModule = DesiraeMyModule;
 | 
			
		||||
})(("undefined" !== typeof exports && exports) || window);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Other than that, just be mindful that your code needs to run in both iojs/node and browser environments
 | 
			
		||||
so steer away from things that are super iojs/node-ish or super window-ish.
 | 
			
		||||
 | 
			
		||||
# Configuration
 | 
			
		||||
Configuration
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
There are a few configuration files:
 | 
			
		||||
 | 
			
		||||
- `site.yml` is stuff that might be unique to your site, such as (title, url, adwords id, etc)
 | 
			
		||||
- `authors/<<your-handle.yml>>` contains information about you (name, handle, facebook, etc)
 | 
			
		||||
- `config.yml` contains directives that describe _how_ the blog should be compiled - more technical stuff.
 | 
			
		||||
* `site.yml` is stuff that might be unique to your site, such as (title, url, adwords id, etc)
 | 
			
		||||
* `authors/<<your-handle.yml>>` contains information about you (name, handle, facebook, etc)
 | 
			
		||||
* `config.yml` contains directives that describe *how* the blog should be compiled - more technical stuff.
 | 
			
		||||
 | 
			
		||||
If any of these files change, the entire site needs to be retemplated.
 | 
			
		||||
 | 
			
		||||
# API
 | 
			
		||||
API
 | 
			
		||||
===
 | 
			
		||||
 | 
			
		||||
I'd like to make the plugin system connect-style API for adding plugins or whatever so that,
 | 
			
		||||
for example, so you could have a custom markdown preprocessor (that handles includes, perhaps)
 | 
			
		||||
@ -233,26 +143,28 @@ But here's what I've got so far:
 | 
			
		||||
 | 
			
		||||
### (html, markdown, jade)
 | 
			
		||||
 | 
			
		||||
- `Desirae.registerRenderer(ext, fn)`
 | 
			
		||||
* `Desirae.registerRenderer(ext, fn)`
 | 
			
		||||
 | 
			
		||||
For example, if you want to add the ability to render from `slim` or `haml`
 | 
			
		||||
instead of just `markdown` you could find the appropriate
 | 
			
		||||
JavaScript module (or make requests to an API service that renders them for you) and do this
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
var slim = exports.slimjs || require("slimjs");
 | 
			
		||||
function render(contentstr /*, desi*/) {
 | 
			
		||||
  return Promise.resolve(slim(contentstr));
 | 
			
		||||
var slim = exports.slimjs || require('slimjs')
 | 
			
		||||
  ;
 | 
			
		||||
 | 
			
		||||
function render(contentstr/*, desi*/) {
 | 
			
		||||
  return PromiseA.resolve(slim(contentstr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Desirae.registerRenderer(".slim", render);
 | 
			
		||||
Desirae.registerRenderer('.slim', render);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Data Mapping
 | 
			
		||||
 | 
			
		||||
### (desirae, ruhoh, etc)
 | 
			
		||||
 | 
			
		||||
- `Desirae.registerDataMapper(ext, fn)`
 | 
			
		||||
* `Desirae.registerDataMapper(ext, fn)`
 | 
			
		||||
 | 
			
		||||
If you want to use a non-desirae theme that uses attributes in a different than
 | 
			
		||||
how Desirae creates the view object internally
 | 
			
		||||
@ -261,17 +173,17 @@ data mapper to accomplish this.
 | 
			
		||||
 | 
			
		||||
Please try not to modify the original object if you can avoid it.
 | 
			
		||||
 | 
			
		||||
_TODO: maybe pass in a two-level deep shallow copy ?_
 | 
			
		||||
*TODO: maybe pass in a two-level deep shallow copy ?*
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
Desirae.registerDataMapper("ruhoh@3.0", function (view) {
 | 
			
		||||
Desirae.registerDataMapper('ruhoh@3.0', function (view) {
 | 
			
		||||
  return {
 | 
			
		||||
    page: {
 | 
			
		||||
      name: view.entity.title,
 | 
			
		||||
    },
 | 
			
		||||
    author: {
 | 
			
		||||
      nickname: view.author.twitter,
 | 
			
		||||
    },
 | 
			
		||||
      name: view.entity.title
 | 
			
		||||
    }
 | 
			
		||||
  , author: {
 | 
			
		||||
      nickname: view.author.twitter
 | 
			
		||||
    }
 | 
			
		||||
    // ... 
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
@ -290,54 +202,54 @@ I'd love to work with anyone who is familiar with the Dropbox or similar APIs.
 | 
			
		||||
 | 
			
		||||
I think it would be awesome to support various means of storage. Perhaps github gists too.
 | 
			
		||||
 | 
			
		||||
# Server
 | 
			
		||||
 | 
			
		||||
Server
 | 
			
		||||
======
 | 
			
		||||
 | 
			
		||||
Obviously there has to be a server with some sort of storage and retrieval mechanism.
 | 
			
		||||
 | 
			
		||||
I've implemented a very simple node server using the filesystem.
 | 
			
		||||
 | 
			
		||||
## GET /api/fs/walk
 | 
			
		||||
GET /api/fs/walk
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
`GET http://local.dear.desi:8080/api/fs/walk?dir=posts&dotfiles=true&extensions=md,markdown,jade,htm,html`
 | 
			
		||||
 | 
			
		||||
- `dir` **must** be supplied. returns a flat list of all files, recursively
 | 
			
		||||
- `dotfiles` default to `false`. includes dotfiles when `true`.
 | 
			
		||||
- `extensions` defaults to `null`. inclode **only** the supplied extensions when `true`.
 | 
			
		||||
* `dir` **must** be supplied. returns a flat list of all files, recursively
 | 
			
		||||
* `dotfiles` default to `false`. includes dotfiles when `true`.
 | 
			
		||||
* `extensions` defaults to `null`. inclode **only** the supplied extensions when `true`.
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
[
 | 
			
		||||
  {
 | 
			
		||||
    "name": "happy-new-year.md",
 | 
			
		||||
    "createdDate": "2015-01-05T18:19:30.000Z",
 | 
			
		||||
    "lastModifiedDate": "2015-01-05T18:19:30.000Z",
 | 
			
		||||
    "size": 2121,
 | 
			
		||||
    "relativePath": "posts/2015"
 | 
			
		||||
  },
 | 
			
		||||
  { "name": "happy-new-year.md"
 | 
			
		||||
  , "createdDate": "2015-01-05T18:19:30.000Z"
 | 
			
		||||
  , "lastModifiedDate": "2015-01-05T18:19:30.000Z"
 | 
			
		||||
  , "size": 2121
 | 
			
		||||
  , "relativePath": "posts/2015"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    "name": "tips-for-the-ages.jade",
 | 
			
		||||
    "createdDate": "2014-06-16T18:19:30.000Z",
 | 
			
		||||
    "lastModifiedDate": "2014-06-16T18:19:30.000Z",
 | 
			
		||||
    "size": 389,
 | 
			
		||||
    "relativePath": "posts"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "name": "my-first-post.html",
 | 
			
		||||
    "createdDate": "2013-08-01T22:47:37.000Z",
 | 
			
		||||
    "lastModifiedDate": "2013-08-01T22:47:37.000Z",
 | 
			
		||||
    "size": 4118,
 | 
			
		||||
    "relativePath": "posts/2013"
 | 
			
		||||
, { "name": "tips-for-the-ages.jade"
 | 
			
		||||
  , "createdDate": "2014-06-16T18:19:30.000Z"
 | 
			
		||||
  , "lastModifiedDate": "2014-06-16T18:19:30.000Z"
 | 
			
		||||
  , "size": 389
 | 
			
		||||
  , "relativePath": "posts"
 | 
			
		||||
  }
 | 
			
		||||
, { "name": "my-first-post.html"
 | 
			
		||||
  , "createdDate": "2013-08-01T22:47:37.000Z"
 | 
			
		||||
  , "lastModifiedDate": "2013-08-01T22:47:37.000Z"
 | 
			
		||||
  , "size": 4118
 | 
			
		||||
  , "relativePath": "posts/2013"
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
To retrieve multiple dir listings at once:
 | 
			
		||||
 | 
			
		||||
- for a few simple dirs without special chars just change `dir` to `dirs` and separate with commas
 | 
			
		||||
* for a few simple dirs without special chars just change `dir` to `dirs` and separate with commas
 | 
			
		||||
 | 
			
		||||
`GET http://local.dear.desi:8080/api/fs/walk?dirs=posts/2015,posts/2013&dotfiles=true&extensions=md,markdown,jade,htm,html`
 | 
			
		||||
 | 
			
		||||
- for many dirs, or dirs with special chars, `POST` an object containing an array of `dirs` with `&_method=GET` appended to the url.
 | 
			
		||||
* for many dirs, or dirs with special chars, `POST` an object containing an array of `dirs` with  `&_method=GET` appended to the url.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
POST http://local.dear.desi:8080/api/fs/walk?dotfiles=true&extensions=md,markdown,jade,htm,html&_method=GET
 | 
			
		||||
@ -352,27 +264,27 @@ POST http://local.dear.desi:8080/api/fs/walk?dotfiles=true&extensions=md,markdow
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## GET /api/fs/files
 | 
			
		||||
GET /api/fs/files
 | 
			
		||||
-------------
 | 
			
		||||
 | 
			
		||||
`GET http://local.dear.desi:8080/api/fs/files?path=posts/happy-new-year.md`
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "path": "posts/intro-to-http-with-netcat-node-connect.md",
 | 
			
		||||
  "createdDate": "2013-08-01T22:47:37.000Z",
 | 
			
		||||
  "lastModifiedDate": "2013-08-01T22:47:37.000Z",
 | 
			
		||||
  "contents": "...",
 | 
			
		||||
  "sha1": "6eae3a5b062c6d0d79f070c26e6d62486b40cb46"
 | 
			
		||||
{ "path": "posts/intro-to-http-with-netcat-node-connect.md"
 | 
			
		||||
, "createdDate": "2013-08-01T22:47:37.000Z"
 | 
			
		||||
, "lastModifiedDate": "2013-08-01T22:47:37.000Z"
 | 
			
		||||
, "contents": "..."
 | 
			
		||||
, "sha1": "6eae3a5b062c6d0d79f070c26e6d62486b40cb46"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
To retrieve multiple files at once:
 | 
			
		||||
 | 
			
		||||
- for a few simple files without special chars just change `path` to `paths` and separate with commas
 | 
			
		||||
* for a few simple files without special chars just change `path` to `paths` and separate with commas
 | 
			
		||||
 | 
			
		||||
`GET http://local.dear.desi:8080/api/fs/files?paths=posts/foo.md,posts/bar.md`
 | 
			
		||||
 | 
			
		||||
- for many files, or files with special chars, `POST` an object containing an array of `pathss` with `&_method=GET` appended to the url.
 | 
			
		||||
* for many files, or files with special chars, `POST` an object containing an array of `pathss` with  `&_method=GET` appended to the url.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
POST http://local.dear.desi:8080/api/fs/files?dotfiles=true&extensions=md,markdown,jade,htm,html&_method=GET
 | 
			
		||||
@ -391,7 +303,8 @@ POST http://local.dear.desi:8080/api/fs/files?dotfiles=true&extensions=md,markdo
 | 
			
		||||
]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## POST /api/fs/files
 | 
			
		||||
POST /api/fs/files
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
By default this should assume that you intended to write to the compiled directory
 | 
			
		||||
and return an error if you try to write to any other directory, unless `compiled=false` (not yet implemented).
 | 
			
		||||
@ -436,15 +349,9 @@ The response may include errors of all shapes and sizes.
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## POST /api/fs/copy
 | 
			
		||||
POST /api/fs/copy
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{ "files": { "assets/logo.png": "compiled/assets/logo.png" } }
 | 
			
		||||
{ files: { "assets/logo.png": "compiled/assets/logo.png" } }
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# 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/.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										44
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								TODO.md
									
									
									
									
									
								
							@ -4,6 +4,7 @@ show file path
 | 
			
		||||
show prod url
 | 
			
		||||
show dev url
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
POST tests
 | 
			
		||||
create a title and delete it (no error)
 | 
			
		||||
change the format. does the permalink change? (yes)
 | 
			
		||||
@ -15,17 +16,22 @@ change the format in the frontmatter permalink. does the format change? (yes)
 | 
			
		||||
create a description and delete it (no error)
 | 
			
		||||
create a description. does the frontmatter change? (yes)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
protection
 | 
			
		||||
Don't allow changing the uuid, original_url, or original_date
 | 
			
		||||
 | 
			
		||||
## TODO
 | 
			
		||||
TODO
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
check that no other post uses the same permalink
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
default data-model 'ruhoh@2.2'
 | 
			
		||||
other data-model 'desirae@1.0'
 | 
			
		||||
 | 
			
		||||
# Widgets
 | 
			
		||||
Widgets
 | 
			
		||||
=======
 | 
			
		||||
 | 
			
		||||
All widgets should export an object with a `create(widgetConf, desiState)` function that returns a promise.
 | 
			
		||||
 | 
			
		||||
@ -46,10 +52,11 @@ widgets:
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
"use strict";
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
module.exports.Foogizmo.create = function (foogizmoConf, desiState) {
 | 
			
		||||
  return new Promise(function (resolve) {
 | 
			
		||||
 | 
			
		||||
    function pager(desiPageState) {
 | 
			
		||||
      // Do processing
 | 
			
		||||
 | 
			
		||||
@ -61,28 +68,29 @@ module.exports.Foogizmo.create = function (foogizmoConf, desiState) {
 | 
			
		||||
 | 
			
		||||
      desiPostState.fooembedinator = function (fooval) {
 | 
			
		||||
        // figure out what type of link fooval is and return iframe html
 | 
			
		||||
        return (
 | 
			
		||||
          '<iframe src="http://embedinator.com/"' +
 | 
			
		||||
          foovalProcessed +
 | 
			
		||||
          "></iframe>"
 | 
			
		||||
        );
 | 
			
		||||
      };
 | 
			
		||||
        return '<iframe src="http://embedinator.com/"' + foovalProcessed + '></iframe>'
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    resolve({ foopager: pager, fooposter: poster });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Overlays
 | 
			
		||||
Overlays
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
For any config a widget uses, it should also check on post.fooconfig and theme.fooconfig to make sure that they don't override the foogizmo.config.fooconfig
 | 
			
		||||
 | 
			
		||||
# Migrating from Ruhoh
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Migrating from Ruhoh
 | 
			
		||||
====================
 | 
			
		||||
 | 
			
		||||
There are only a few things in Ruhoh that could only be done in ruby or were otherwise difficult to work around.
 | 
			
		||||
 | 
			
		||||
## config.yml
 | 
			
		||||
config.yml
 | 
			
		||||
----------
 | 
			
		||||
 | 
			
		||||
Instead of having special names for some properties (`_root`)
 | 
			
		||||
and `use` sub attributes for others (`twitter` theme, posts directory),
 | 
			
		||||
@ -109,15 +117,19 @@ widgets [NO CHANGE]
 | 
			
		||||
 | 
			
		||||
All directories are ignored by default. If you want a directory to be interpreted as a collection of pages you need to specify it in the `collections` hash.
 | 
			
		||||
 | 
			
		||||
## data.yml
 | 
			
		||||
data.yml
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
No changes
 | 
			
		||||
 | 
			
		||||
## config.ru
 | 
			
		||||
 | 
			
		||||
config.ru
 | 
			
		||||
---------
 | 
			
		||||
 | 
			
		||||
REMOVED (ruby only)
 | 
			
		||||
 | 
			
		||||
## themes layout
 | 
			
		||||
themes layout
 | 
			
		||||
-------------
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										22
									
								
								bower.json
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								bower.json
									
									
									
									
									
								
							@ -1,10 +1,15 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "desirae",
 | 
			
		||||
  "version": "0.11.2",
 | 
			
		||||
  "authors": ["AJ ONeal <awesome@coolaj86.com>"],
 | 
			
		||||
  "version": "0.1.0",
 | 
			
		||||
  "authors": [
 | 
			
		||||
    "AJ ONeal <awesome@coolaj86.com>"
 | 
			
		||||
  ],
 | 
			
		||||
  "description": "A blogging platform in the browser. Wow!",
 | 
			
		||||
  "main": "desirae.js",
 | 
			
		||||
  "moduleType": ["globals", "node"],
 | 
			
		||||
  "moduleType": [
 | 
			
		||||
    "globals",
 | 
			
		||||
    "node"
 | 
			
		||||
  ],
 | 
			
		||||
  "keywords": [
 | 
			
		||||
    "desirae",
 | 
			
		||||
    "dear",
 | 
			
		||||
@ -21,13 +26,20 @@
 | 
			
		||||
  ],
 | 
			
		||||
  "license": "Apache2",
 | 
			
		||||
  "homepage": "http://github.com/DearDesi/desirae",
 | 
			
		||||
  "ignore": ["**/.*", "node_modules", "bower_components", "test", "tests"],
 | 
			
		||||
  "ignore": [
 | 
			
		||||
    "**/.*",
 | 
			
		||||
    "node_modules",
 | 
			
		||||
    "bower_components",
 | 
			
		||||
    "test",
 | 
			
		||||
    "tests"
 | 
			
		||||
  ],
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "bluebird": "~2.6.2",
 | 
			
		||||
    "escape-string-regexp": "~1.0.2",
 | 
			
		||||
    "forEachAsync": "~5.0.5",
 | 
			
		||||
    "js-yaml": "~3.2.5",
 | 
			
		||||
    "markdown-it": "~3.0.2",
 | 
			
		||||
    "mustache": "~1.0.0",
 | 
			
		||||
    "mustache": "~0.8.2",
 | 
			
		||||
    "node-uuid": "~1.4.2",
 | 
			
		||||
    "path": "~3.46.1"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1649
									
								
								desirae.js
									
									
									
									
									
								
							
							
						
						
									
										1649
									
								
								desirae.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,124 +0,0 @@
 | 
			
		||||
/*jshint -W054 */
 | 
			
		||||
(function (exports) {
 | 
			
		||||
  "use strict";
 | 
			
		||||
 | 
			
		||||
  var path = exports.path || require("path"),
 | 
			
		||||
    months,
 | 
			
		||||
    cores = {};
 | 
			
		||||
  months = {
 | 
			
		||||
    1: "January",
 | 
			
		||||
    2: "February",
 | 
			
		||||
    3: "March",
 | 
			
		||||
    4: "April",
 | 
			
		||||
    5: "May",
 | 
			
		||||
    6: "June",
 | 
			
		||||
    7: "July",
 | 
			
		||||
    8: "August",
 | 
			
		||||
    9: "September",
 | 
			
		||||
    10: "October",
 | 
			
		||||
    11: "November",
 | 
			
		||||
    12: "December",
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  function byDate(a, b) {
 | 
			
		||||
    a.year = parseInt(a.year, 10) || 0;
 | 
			
		||||
    b.year = parseInt(b.year, 10) || 0;
 | 
			
		||||
    if (a.year > b.year) {
 | 
			
		||||
      return -1;
 | 
			
		||||
    } else if (a.year < b.year) {
 | 
			
		||||
      return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    a.month = parseInt(a.month, 10) || 0;
 | 
			
		||||
    b.month = parseInt(b.month, 10) || 0;
 | 
			
		||||
    if (a.month > b.month) {
 | 
			
		||||
      return -1;
 | 
			
		||||
    } else if (a.month < b.month) {
 | 
			
		||||
      return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    a.day = parseInt(a.day, 10) || 0;
 | 
			
		||||
    b.day = parseInt(b.day, 10) || 0;
 | 
			
		||||
    if (a.day > b.day) {
 | 
			
		||||
      return -1;
 | 
			
		||||
    } else if (a.day < b.day) {
 | 
			
		||||
      return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (a.hour > b.hour) {
 | 
			
		||||
      return -1;
 | 
			
		||||
    } else if (a.hour < b.hour) {
 | 
			
		||||
      return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (a.minute > b.minute) {
 | 
			
		||||
      return -1;
 | 
			
		||||
    } else if (a.minute < b.minute) {
 | 
			
		||||
      return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (a.title.toLowerCase() <= b.title.toLowerCase()) {
 | 
			
		||||
      return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function collate(entities, env) {
 | 
			
		||||
    var yearsArr = [];
 | 
			
		||||
    entities.forEach(function (f) {
 | 
			
		||||
      var set;
 | 
			
		||||
      var yindex = 3000 - f.year;
 | 
			
		||||
      var mindex = 12 - f.month;
 | 
			
		||||
      var monthName = months[parseInt(f.month, 10)];
 | 
			
		||||
 | 
			
		||||
      f.url = path.join(env.base_path, f.permalink);
 | 
			
		||||
 | 
			
		||||
      if (!yearsArr[yindex]) {
 | 
			
		||||
        yearsArr[yindex] = { year: f.year, months: [] };
 | 
			
		||||
      }
 | 
			
		||||
      set = yearsArr[yindex];
 | 
			
		||||
 | 
			
		||||
      if (!set.months[mindex]) {
 | 
			
		||||
        set.months[mindex] = {
 | 
			
		||||
          month_name: monthName,
 | 
			
		||||
          month_number: mindex,
 | 
			
		||||
          month: monthName,
 | 
			
		||||
          pages: [],
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
      set = set.months[mindex];
 | 
			
		||||
 | 
			
		||||
      f.month_name = months[parseInt(f.month, 10)];
 | 
			
		||||
      f.month_number = parseInt(f.month, 10);
 | 
			
		||||
      set.pages.push(f);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    yearsArr = yearsArr.filter(function (y) {
 | 
			
		||||
      if (!y) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      y.months = y.months.filter(function (m) {
 | 
			
		||||
        return m && m.pages.length;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (!y.months.length) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return true;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return { years: yearsArr };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  cores.collate = function (desi, env) {
 | 
			
		||||
    // TODO categories
 | 
			
		||||
    // TODO tags
 | 
			
		||||
    desi.content.collections.sort(byDate);
 | 
			
		||||
    desi.collated = collate(desi.content.collections, env);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  exports.DesiraeAggregateCore = cores.DesiraeAggregateCore = cores;
 | 
			
		||||
})(("undefined" !== typeof exports && exports) || window);
 | 
			
		||||
@ -1,66 +1,67 @@
 | 
			
		||||
/*jshint -W054 */
 | 
			
		||||
(function (exports) {
 | 
			
		||||
  "use strict";
 | 
			
		||||
;(function (exports) {
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
  function create(Desi) {
 | 
			
		||||
    // Chrome, Firefox, and even MSIE11+ all support crypto
 | 
			
		||||
    var crypto = window.crypto || window.msCrypto,
 | 
			
		||||
      Promise = window.Promise,
 | 
			
		||||
      algos;
 | 
			
		||||
    var crypto = window.crypto || window.msCrypto
 | 
			
		||||
      , PromiseA = window.Promise
 | 
			
		||||
      , algos
 | 
			
		||||
      ;
 | 
			
		||||
 | 
			
		||||
    // convenience mappings for common digest algorithms
 | 
			
		||||
    algos = {
 | 
			
		||||
      sha1: "SHA-1",
 | 
			
		||||
      sha256: "SHA-256",
 | 
			
		||||
      sha512: "SHA-512",
 | 
			
		||||
      'sha1': 'SHA-1'
 | 
			
		||||
    , 'sha256': 'SHA-256'
 | 
			
		||||
    , 'sha512': 'SHA-512'
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // The function to generate a sha1sum is the same as generating any digest
 | 
			
		||||
    // but here's a shortcut function anyway
 | 
			
		||||
    function sha1sum(str) {
 | 
			
		||||
      return hashsum("sha1", str);
 | 
			
		||||
      return hashsum('sha1', str);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // a more general convenience function
 | 
			
		||||
    function hashsum(hash, str) {
 | 
			
		||||
      // you have to convert from string to array buffer
 | 
			
		||||
      var ab,
 | 
			
		||||
        // you have to convert from string to array buffer
 | 
			
		||||
      var ab
 | 
			
		||||
        // you have to represent the algorithm as an object 
 | 
			
		||||
        algo = { name: algos[hash] };
 | 
			
		||||
      if ("string" === typeof str) {
 | 
			
		||||
        , algo = { name: algos[hash] }
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
      if ('string' === typeof str) {
 | 
			
		||||
        ab = str2ab(str);
 | 
			
		||||
      } else {
 | 
			
		||||
        ab = str;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // All crypto digest methods return a promise
 | 
			
		||||
      return crypto.subtle
 | 
			
		||||
        .digest(algo, ab)
 | 
			
		||||
        .then(function (digest) {
 | 
			
		||||
          // you have to convert the ArrayBuffer to a DataView and then to a hex String
 | 
			
		||||
          return ab2hex(digest);
 | 
			
		||||
        })
 | 
			
		||||
        .catch(function (e) {
 | 
			
		||||
          // if you specify an unsupported digest algorithm or non-ArrayBuffer, you'll get an error
 | 
			
		||||
          console.error("sha1sum ERROR");
 | 
			
		||||
          console.error(e);
 | 
			
		||||
          throw e;
 | 
			
		||||
        });
 | 
			
		||||
      return crypto.subtle.digest(algo, ab).then(function (digest) {
 | 
			
		||||
        // you have to convert the ArrayBuffer to a DataView and then to a hex String
 | 
			
		||||
        return ab2hex(digest);
 | 
			
		||||
      }).catch(function (e) {
 | 
			
		||||
        // if you specify an unsupported digest algorithm or non-ArrayBuffer, you'll get an error
 | 
			
		||||
        console.error('sha1sum ERROR');
 | 
			
		||||
        console.error(e);
 | 
			
		||||
        throw e;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // convert from arraybuffer to hex
 | 
			
		||||
    function ab2hex(ab) {
 | 
			
		||||
      var dv = new DataView(ab),
 | 
			
		||||
        i,
 | 
			
		||||
        len,
 | 
			
		||||
        hex = "",
 | 
			
		||||
        c;
 | 
			
		||||
      var dv = new DataView(ab)
 | 
			
		||||
        , i
 | 
			
		||||
        , len
 | 
			
		||||
        , hex = ''
 | 
			
		||||
        , c
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
      for (i = 0, len = dv.byteLength; i < len; i += 1) {
 | 
			
		||||
        c = dv.getUint8(i).toString(16);
 | 
			
		||||
 | 
			
		||||
        if (c.length < 2) {
 | 
			
		||||
          c = "0" + c;
 | 
			
		||||
          c = '0' + c;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        hex += c;
 | 
			
		||||
@ -71,31 +72,36 @@
 | 
			
		||||
 | 
			
		||||
    // convert from string to arraybuffer
 | 
			
		||||
    function str2ab(stringToEncode, insertBom) {
 | 
			
		||||
      stringToEncode = stringToEncode.replace(/\r\n/g, "\n");
 | 
			
		||||
      stringToEncode = stringToEncode.replace(/\r\n/g,"\n");
 | 
			
		||||
 | 
			
		||||
      var utftext = [],
 | 
			
		||||
        n,
 | 
			
		||||
        c;
 | 
			
		||||
      var utftext = []
 | 
			
		||||
        , n
 | 
			
		||||
        , c
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
      if (true === insertBom) {
 | 
			
		||||
        utftext[0] = 0xef;
 | 
			
		||||
        utftext[1] = 0xbb;
 | 
			
		||||
        utftext[2] = 0xbf;
 | 
			
		||||
      if (true === insertBom)  {
 | 
			
		||||
        utftext[0] =  0xef;
 | 
			
		||||
        utftext[1] =  0xbb;
 | 
			
		||||
        utftext[2] =  0xbf;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (n = 0; n < stringToEncode.length; n += 1) {
 | 
			
		||||
 | 
			
		||||
        c = stringToEncode.charCodeAt(n);
 | 
			
		||||
 | 
			
		||||
        if (c < 128) {
 | 
			
		||||
          utftext[utftext.length] = c;
 | 
			
		||||
        } 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;
 | 
			
		||||
            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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
      }
 | 
			
		||||
      return new Uint8Array(utftext).buffer;
 | 
			
		||||
    }
 | 
			
		||||
@ -106,21 +112,26 @@
 | 
			
		||||
    //
 | 
			
		||||
    // FSAPI
 | 
			
		||||
    //
 | 
			
		||||
    var fsapi;
 | 
			
		||||
    var fsapi
 | 
			
		||||
      ;
 | 
			
		||||
 | 
			
		||||
    function request() {}
 | 
			
		||||
    request.get = function (url /*, query*/) {
 | 
			
		||||
    function request() {
 | 
			
		||||
    }
 | 
			
		||||
    request.get = function (url/*, query*/) {
 | 
			
		||||
      // Return a new promise.
 | 
			
		||||
      return new Promise(function (resolve, reject) {
 | 
			
		||||
      return new PromiseA(function(resolve, reject) {
 | 
			
		||||
        // Do the usual XHR stuff
 | 
			
		||||
        var req = new XMLHttpRequest();
 | 
			
		||||
        req.onload = function () {
 | 
			
		||||
        var req = new XMLHttpRequest()
 | 
			
		||||
          ;
 | 
			
		||||
 | 
			
		||||
        req.onload = function() {
 | 
			
		||||
          // This is called even on 404 etc
 | 
			
		||||
          // so check the status
 | 
			
		||||
          if (200 === req.status) {
 | 
			
		||||
            // Resolve the promise with the response text
 | 
			
		||||
            resolve(req.response);
 | 
			
		||||
          } else {
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            // Otherwise reject with the status text
 | 
			
		||||
            // which will hopefully be a meaningful error
 | 
			
		||||
            reject(Error(req.statusText));
 | 
			
		||||
@ -128,27 +139,30 @@
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Handle network errors
 | 
			
		||||
        req.onerror = function () {
 | 
			
		||||
        req.onerror = function() {
 | 
			
		||||
          reject(Error("Network Error"));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Make the request
 | 
			
		||||
        req.open("GET", url);
 | 
			
		||||
        req.open('GET', url);
 | 
			
		||||
        req.send();
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
    request.post = function (url /*, query*/, body) {
 | 
			
		||||
    request.post = function (url/*, query*/, body) {
 | 
			
		||||
      // Return a new promise.
 | 
			
		||||
      return new Promise(function (resolve, reject) {
 | 
			
		||||
      return new PromiseA(function(resolve, reject) {
 | 
			
		||||
        // Do the usual XHR stuff
 | 
			
		||||
        var req = new XMLHttpRequest();
 | 
			
		||||
        req.onload = function () {
 | 
			
		||||
        var req = new XMLHttpRequest()
 | 
			
		||||
          ;
 | 
			
		||||
 | 
			
		||||
        req.onload = function() {
 | 
			
		||||
          // This is called even on 404 etc
 | 
			
		||||
          // so check the status
 | 
			
		||||
          if (200 === req.status) {
 | 
			
		||||
            // Resolve the promise with the response text
 | 
			
		||||
            resolve(req.response);
 | 
			
		||||
          } else {
 | 
			
		||||
          }
 | 
			
		||||
          else {
 | 
			
		||||
            // Otherwise reject with the status text
 | 
			
		||||
            // which will hopefully be a meaningful error
 | 
			
		||||
            reject(Error(req.statusText));
 | 
			
		||||
@ -156,14 +170,14 @@
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Handle network errors
 | 
			
		||||
        req.onerror = function () {
 | 
			
		||||
        req.onerror = function() {
 | 
			
		||||
          reject(Error("Network Error"));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        req.open("POST", url);
 | 
			
		||||
        req.open('POST', url);
 | 
			
		||||
        req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
 | 
			
		||||
        // Make the request
 | 
			
		||||
        if ("string" !== typeof body) {
 | 
			
		||||
        if ('string' !== typeof body) {
 | 
			
		||||
          body = JSON.stringify(body);
 | 
			
		||||
        }
 | 
			
		||||
        req.send(body);
 | 
			
		||||
@ -174,93 +188,87 @@
 | 
			
		||||
    fsapi.getMeta = function (collections, opts) {
 | 
			
		||||
      opts = opts || {};
 | 
			
		||||
 | 
			
		||||
      var extensions = "",
 | 
			
		||||
        dotfiles = "",
 | 
			
		||||
        contents = "",
 | 
			
		||||
        sha1sum = "";
 | 
			
		||||
      var extensions = ''
 | 
			
		||||
        , dotfiles = ''
 | 
			
		||||
        , contents = ''
 | 
			
		||||
        , sha1sum = ''
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
      if (Array.isArray(opts.extensions)) {
 | 
			
		||||
        extensions = "&extensions=" + opts.extensions.join(","); // md,markdown,jade,htm,html
 | 
			
		||||
        extensions = '&extensions=' + opts.extensions.join(','); // md,markdown,jade,htm,html
 | 
			
		||||
      }
 | 
			
		||||
      if (opts.dotfiles) {
 | 
			
		||||
        dotfiles = "&dotfiles=true";
 | 
			
		||||
        dotfiles = '&dotfiles=true';
 | 
			
		||||
      }
 | 
			
		||||
      if (opts.contents) {
 | 
			
		||||
        contents = "&contents=true";
 | 
			
		||||
        contents = '&contents=true';
 | 
			
		||||
      }
 | 
			
		||||
      if (false === opts.sha1sum) {
 | 
			
		||||
        sha1sum = "&sha1sum=false";
 | 
			
		||||
        sha1sum = '&sha1sum=false';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return request
 | 
			
		||||
        .post(
 | 
			
		||||
          "/api/fs/walk?_method=GET" +
 | 
			
		||||
            dotfiles +
 | 
			
		||||
            extensions +
 | 
			
		||||
            contents +
 | 
			
		||||
            sha1sum,
 | 
			
		||||
          {
 | 
			
		||||
            dirs: collections,
 | 
			
		||||
          }
 | 
			
		||||
        )
 | 
			
		||||
        .then(function (resp) {
 | 
			
		||||
          return JSON.parse(resp);
 | 
			
		||||
        })
 | 
			
		||||
        .catch(function (e) {
 | 
			
		||||
          throw e;
 | 
			
		||||
        });
 | 
			
		||||
      return request.post('/api/fs/walk?_method=GET' + dotfiles + extensions + contents + sha1sum, {
 | 
			
		||||
        dirs: collections
 | 
			
		||||
      }).then(function (resp) {
 | 
			
		||||
        return JSON.parse(resp);
 | 
			
		||||
      }).catch(function (e) {
 | 
			
		||||
        throw e;
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    fsapi.getContents = function (filepaths) {
 | 
			
		||||
      return request
 | 
			
		||||
        .post("/api/fs/files?_method=GET", {
 | 
			
		||||
          paths: filepaths,
 | 
			
		||||
        })
 | 
			
		||||
        .then(function (resp) {
 | 
			
		||||
          return JSON.parse(resp);
 | 
			
		||||
        });
 | 
			
		||||
      return request.post('/api/fs/files?_method=GET', {
 | 
			
		||||
        paths: filepaths
 | 
			
		||||
      }).then(function (resp) {
 | 
			
		||||
        return JSON.parse(resp);
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    fsapi.getCache = function () {
 | 
			
		||||
      return request
 | 
			
		||||
        .get("/api/fs/static/cache.json")
 | 
			
		||||
        .then(function (resp) {
 | 
			
		||||
          return JSON.parse(resp);
 | 
			
		||||
        })
 | 
			
		||||
        .catch(function (/*e*/) {
 | 
			
		||||
          return {};
 | 
			
		||||
        })
 | 
			
		||||
        .then(function (obj) {
 | 
			
		||||
          return obj;
 | 
			
		||||
        });
 | 
			
		||||
      return request.get('/api/fs/static/cache.json').then(function (resp) {
 | 
			
		||||
        return JSON.parse(resp);
 | 
			
		||||
      }).catch(function (/*e*/) {
 | 
			
		||||
        return {};
 | 
			
		||||
      }).then(function (obj) {
 | 
			
		||||
        return obj;
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    fsapi.copy = function (files) {
 | 
			
		||||
      var body = { files: files };
 | 
			
		||||
      body = JSON.stringify(body); // this is more or less instant for a few MiB of posts
 | 
			
		||||
      return request.post("/api/fs/copy", body).then(function (resp) {
 | 
			
		||||
        var response = JSON.parse(resp);
 | 
			
		||||
      return request.post('/api/fs/copy', body).then(function (resp) {
 | 
			
		||||
        var response = JSON.parse(resp)
 | 
			
		||||
          ;
 | 
			
		||||
 | 
			
		||||
        // not accurate for utf8/unicode, but close enough
 | 
			
		||||
        response.size = body.length;
 | 
			
		||||
        return response;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    fsapi.putFiles = function (files) {
 | 
			
		||||
      var body = { files: files };
 | 
			
		||||
      var body = { files: files }
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
      files.forEach(function (file) {
 | 
			
		||||
        if (!file.contents || "string" === typeof file.contents) {
 | 
			
		||||
        if (!file.contents || 'string' === typeof file.contents) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        if (/\.json$/i.test(file.path)) {
 | 
			
		||||
          file.contents = JSON.stringify(file.contents);
 | 
			
		||||
        } else if (/\.ya?ml$/i.test(file.path)) {
 | 
			
		||||
        }
 | 
			
		||||
        else if (/\.ya?ml$/i.test(file.path)) {
 | 
			
		||||
          file.contents = exports.jsyaml.dump(file.contents); 
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      body = JSON.stringify(body); // this is more or less instant for a few MiB of posts
 | 
			
		||||
      return request.post("/api/fs/files", body).then(function (resp) {
 | 
			
		||||
        var response = JSON.parse(resp);
 | 
			
		||||
      return request.post('/api/fs/files', body).then(function (resp) {
 | 
			
		||||
        var response = JSON.parse(resp)
 | 
			
		||||
          ;
 | 
			
		||||
 | 
			
		||||
        // not accurate for utf8/unicode, but close enough
 | 
			
		||||
        response.size = body.length;
 | 
			
		||||
        return response;
 | 
			
		||||
@ -273,4 +281,4 @@
 | 
			
		||||
  } else {
 | 
			
		||||
    exports.create = create;
 | 
			
		||||
  }
 | 
			
		||||
})(("undefined" !== typeof exports && exports) || window);
 | 
			
		||||
}('undefined' !== typeof exports && exports || window));
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,7 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
module.exports.convert = function () {
 | 
			
		||||
  console.error(
 | 
			
		||||
    "I haven't implemented a ruhoh -> nuhoh converter yet, but it's not very hard to do."
 | 
			
		||||
  );
 | 
			
		||||
  console.error("I haven't implemented a ruhoh -> nuhoh converter yet, but it's not very hard to do.");
 | 
			
		||||
  console.error("see https://github.com/coolaj86/nuhoh/tree/master/MIGRATE.md");
 | 
			
		||||
  throw new Error("Not Implemented.");
 | 
			
		||||
  throw new Error('Not Implemented.');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,11 +0,0 @@
 | 
			
		||||
/*jshint -W054 */
 | 
			
		||||
(function (exports) {
 | 
			
		||||
  "use strict";
 | 
			
		||||
 | 
			
		||||
  function desiMap(obj) {
 | 
			
		||||
    obj.desi = obj;
 | 
			
		||||
    return obj;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  exports.DesiraeDatamapCore = desiMap.DesiraeDatamapCore = desiMap;
 | 
			
		||||
})(("undefined" !== typeof exports && exports) || window);
 | 
			
		||||
@ -1,17 +1,19 @@
 | 
			
		||||
/*jshint -W054 */
 | 
			
		||||
(function (exports) {
 | 
			
		||||
  "use strict";
 | 
			
		||||
;(function (exports) {
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
  function create(Desi) {
 | 
			
		||||
    Desi.YAML = {};
 | 
			
		||||
    Desi.YAML.parse = (exports.jsyaml || require("js-yaml")).load;
 | 
			
		||||
    Desi.YAML.stringify = (exports.jsyaml || require("js-yaml")).dump;
 | 
			
		||||
    Desi.YAML           = {};
 | 
			
		||||
    Desi.YAML.parse     = (exports.jsyaml || require('js-yaml')).load;
 | 
			
		||||
    Desi.YAML.stringify = (exports.jsyaml || require('js-yaml')).dump;
 | 
			
		||||
 | 
			
		||||
    function readFrontMatter(text) {
 | 
			
		||||
      var lines,
 | 
			
		||||
        line,
 | 
			
		||||
        padIndent = "",
 | 
			
		||||
        ymllines = [];
 | 
			
		||||
      var lines
 | 
			
		||||
        , line
 | 
			
		||||
        , padIndent = ''
 | 
			
		||||
        , ymllines = []
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
      lines = text.split(/\n/);
 | 
			
		||||
      line = lines.shift();
 | 
			
		||||
 | 
			
		||||
@ -23,14 +25,14 @@
 | 
			
		||||
      // that start without indentation, so
 | 
			
		||||
      // we can add it if this is the case
 | 
			
		||||
      if (lines[0] && lines[0].match(/^\S/)) {
 | 
			
		||||
        padIndent = "";
 | 
			
		||||
        padIndent = '';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      while (true) {
 | 
			
		||||
        line = lines.shift();
 | 
			
		||||
 | 
			
		||||
        // premature end-of-file (unsupported yaml)
 | 
			
		||||
        if (!line && "" !== line) {
 | 
			
		||||
        if (!line && '' !== line) {
 | 
			
		||||
          ymllines = [];
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
@ -46,17 +48,20 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      // XXX can't be sorted because arrays get messed up
 | 
			
		||||
      //ymllines.sort();
 | 
			
		||||
      if (ymllines) {
 | 
			
		||||
        return "---\n" + ymllines.join("\n");
 | 
			
		||||
        return '---\n' + ymllines.join('\n');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function separateText(text, fm) {
 | 
			
		||||
      var len, yml;
 | 
			
		||||
      var len
 | 
			
		||||
        , yml
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
      yml = readFrontMatter(fm);
 | 
			
		||||
      // strip frontmatter from text, if any
 | 
			
		||||
@ -67,26 +72,27 @@
 | 
			
		||||
        len = 0;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return text.split(/\n/).slice(len).join("\n");
 | 
			
		||||
      return text.split(/\n/).slice(len).join('\n');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function parseText(text) {
 | 
			
		||||
      var fm = readFrontMatter(text),
 | 
			
		||||
        body = fm && separateText(text, fm),
 | 
			
		||||
        yml;
 | 
			
		||||
      var fm = readFrontMatter(text)
 | 
			
		||||
        , body = fm && separateText(text, fm)
 | 
			
		||||
        , yml
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
      if (fm) {
 | 
			
		||||
        try {
 | 
			
		||||
          yml = Desi.YAML.parse(fm);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
        } catch(e) {
 | 
			
		||||
          //
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        yml: yml,
 | 
			
		||||
        frontmatter: fm,
 | 
			
		||||
        body: body,
 | 
			
		||||
        yml: yml
 | 
			
		||||
      , frontmatter: fm
 | 
			
		||||
      , body: body
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -106,4 +112,4 @@
 | 
			
		||||
  } else {
 | 
			
		||||
    exports.create = create;
 | 
			
		||||
  }
 | 
			
		||||
})(("undefined" !== typeof exports && exports) || window);
 | 
			
		||||
}('undefined' !== typeof exports && exports || window));
 | 
			
		||||
 | 
			
		||||
@ -1,47 +1,51 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
var fs = require('fs').promises;
 | 
			
		||||
var PromiseA = require('bluebird').Promise
 | 
			
		||||
  , fs = PromiseA.promisifyAll(require('fs'))
 | 
			
		||||
  ;
 | 
			
		||||
 | 
			
		||||
function create(Desi, options) {
 | 
			
		||||
  var fsapi = Desi.fsapi;
 | 
			
		||||
  var fsapi = Desi.fsapi
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
  options.blogdir = options.blogdir || options.working_path;
 | 
			
		||||
 | 
			
		||||
  fsapi.getMeta = function (dirnames, opts) {
 | 
			
		||||
    opts = opts || {};
 | 
			
		||||
 | 
			
		||||
    var extensions = "",
 | 
			
		||||
      dotfiles = "",
 | 
			
		||||
      contents = "",
 | 
			
		||||
      sha1sum = "";
 | 
			
		||||
    var extensions = ''
 | 
			
		||||
      , dotfiles = ''
 | 
			
		||||
      , contents = ''
 | 
			
		||||
      , sha1sum = ''
 | 
			
		||||
      ;
 | 
			
		||||
 | 
			
		||||
    if (Array.isArray(opts.extensions)) {
 | 
			
		||||
      extensions = "&extensions=" + opts.extensions.join(","); // md,markdown,jade,htm,html
 | 
			
		||||
      extensions = '&extensions=' + opts.extensions.join(','); // md,markdown,jade,htm,html
 | 
			
		||||
    }
 | 
			
		||||
    if (opts.dotfiles) {
 | 
			
		||||
      dotfiles = "&dotfiles=true";
 | 
			
		||||
      dotfiles = '&dotfiles=true';
 | 
			
		||||
    }
 | 
			
		||||
    if (opts.contents) {
 | 
			
		||||
      contents = "&contents=true";
 | 
			
		||||
      contents = '&contents=true';
 | 
			
		||||
    }
 | 
			
		||||
    if (false === opts.sha1sum) {
 | 
			
		||||
      sha1sum = "&sha1sum=false";
 | 
			
		||||
      sha1sum = '&sha1sum=false';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return fsapi.walk.walkDirs(options.blogdir, dirnames, opts);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  fsapi.getContents = function (filepaths) {
 | 
			
		||||
 | 
			
		||||
    return fsapi.getfs(options.blogdir, filepaths);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  fsapi.getCache = function () {
 | 
			
		||||
    return fs
 | 
			
		||||
      .readFile(options.blogdir, "/cache.json")
 | 
			
		||||
      .catch(function (/*e*/) {
 | 
			
		||||
        return {};
 | 
			
		||||
      })
 | 
			
		||||
      .then(function (obj) {
 | 
			
		||||
        return obj;
 | 
			
		||||
      });
 | 
			
		||||
    return fs.readFileAsync(options.blogdir, '/cache.json').catch(function (/*e*/) {
 | 
			
		||||
      return {};
 | 
			
		||||
    }).then(function (obj) {
 | 
			
		||||
      return obj;
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  fsapi.copy = function (files) {
 | 
			
		||||
@ -51,12 +55,13 @@ function create(Desi, options) {
 | 
			
		||||
 | 
			
		||||
  fsapi.putFiles = function (files, opts) {
 | 
			
		||||
    files.forEach(function (file) {
 | 
			
		||||
      if (!file.contents || "string" === typeof file.contents) {
 | 
			
		||||
      if (!file.contents || 'string' === typeof file.contents) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (/\.json$/i.test(file.path)) {
 | 
			
		||||
        file.contents = JSON.stringify(file.contents);
 | 
			
		||||
      } else if (/\.ya?ml$/i.test(file.path)) {
 | 
			
		||||
      }
 | 
			
		||||
      else if (/\.ya?ml$/i.test(file.path)) {
 | 
			
		||||
        file.contents = Desi.YAML.stringify(file.contents); 
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -1,18 +1,18 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
var fs = require("fs").promises;
 | 
			
		||||
var path = require("path");
 | 
			
		||||
var PromiseA      = require('bluebird').Promise
 | 
			
		||||
  , fs            = PromiseA.promisifyAll(require('fs'))
 | 
			
		||||
  , forEachAsync  = require('foreachasync').forEachAsync
 | 
			
		||||
  , path          = require('path')
 | 
			
		||||
  , walk          = require('walk')
 | 
			
		||||
  , escapeRegExp  = require('escape-string-regexp')
 | 
			
		||||
  , safeResolve   = require('../utils').safeResolve
 | 
			
		||||
  , sha1sum       = function (str) { return require('secret-utils').hashsum('sha1', str); }
 | 
			
		||||
  , mkdirp        = PromiseA.promisify(require('mkdirp'))
 | 
			
		||||
  , fsExtra       = PromiseA.promisifyAll(require('fs.extra'))
 | 
			
		||||
  //, tmpdir        = require('os').tmpdir()
 | 
			
		||||
  ;
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
  return pathname.substr(prefix.length + 1);
 | 
			
		||||
}
 | 
			
		||||
@ -23,24 +23,24 @@ function walkDir(parent, sub, opts) {
 | 
			
		||||
    opts.sha1sum = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var prefix = path.resolve(parent);
 | 
			
		||||
  var trueRoot = path.resolve(prefix, sub);
 | 
			
		||||
  var files = [];
 | 
			
		||||
  var prefix = path.resolve(parent)
 | 
			
		||||
    , trueRoot = path.resolve(prefix, sub)
 | 
			
		||||
    , files = []
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
  function filter(name) {
 | 
			
		||||
    if (!name) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!opts.dotfiles && "." === name[0]) {
 | 
			
		||||
    if (!opts.dotfiles && ('.' === name[0])) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (opts.extensions && opts.extensions.length) {
 | 
			
		||||
      if (
 | 
			
		||||
        !opts.extensions.some(function (ext) {
 | 
			
		||||
          return new RegExp("\\." + escapeRegExp(ext) + "$").test(name);
 | 
			
		||||
        })
 | 
			
		||||
      ) {
 | 
			
		||||
      if (!opts.extensions.some(function (ext) {
 | 
			
		||||
        return new RegExp('\\.' + escapeRegExp(ext) + '$').test(name);
 | 
			
		||||
      })) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@ -48,9 +48,11 @@ function walkDir(parent, sub, opts) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return new Promise(function (resolve) {
 | 
			
		||||
    var walker = walk.walk(trueRoot);
 | 
			
		||||
    walker.on("nodeError", function (filepath, stat, next) {
 | 
			
		||||
  return new PromiseA(function (resolve) {
 | 
			
		||||
    var walker = walk.walk(trueRoot)
 | 
			
		||||
      ;
 | 
			
		||||
 | 
			
		||||
    walker.on('nodeError', function (filepath, stat, next) {
 | 
			
		||||
      //stats.forEach(function (stat) {
 | 
			
		||||
      if (!filter(stat.name)) {
 | 
			
		||||
        return;
 | 
			
		||||
@ -58,56 +60,59 @@ function walkDir(parent, sub, opts) {
 | 
			
		||||
 | 
			
		||||
      stat.error.path = path.join(strip(prefix, filepath), stat.name);
 | 
			
		||||
      files.push({
 | 
			
		||||
        name: stat.name,
 | 
			
		||||
        relativePath: strip(prefix, filepath),
 | 
			
		||||
        path: path.join(strip(prefix, filepath), stat.name),
 | 
			
		||||
        name: stat.name
 | 
			
		||||
      , relativePath: strip(prefix, filepath)
 | 
			
		||||
      , path: path.join(strip(prefix, filepath), stat.name)
 | 
			
		||||
 | 
			
		||||
        type: undefined,
 | 
			
		||||
        error: stat.error,
 | 
			
		||||
      , type: undefined
 | 
			
		||||
      , error: stat.error
 | 
			
		||||
      });
 | 
			
		||||
      //});
 | 
			
		||||
 | 
			
		||||
      next();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    walker.on("files", function (root, stats, next) {
 | 
			
		||||
      var dirname = strip(prefix, root);
 | 
			
		||||
    walker.on('files', function (root, stats, next) {
 | 
			
		||||
      var dirname = strip(prefix, root)
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
      function eachFile(stat) {
 | 
			
		||||
        var file;
 | 
			
		||||
        var file
 | 
			
		||||
          ;
 | 
			
		||||
 | 
			
		||||
        if (!filter(stat.name)) {
 | 
			
		||||
          return Promise.resolve();
 | 
			
		||||
          return PromiseA.resolve();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        file = {
 | 
			
		||||
          name: stat.name,
 | 
			
		||||
          relativePath: dirname,
 | 
			
		||||
          path: path.join(dirname, stat.name),
 | 
			
		||||
          name: stat.name
 | 
			
		||||
        , relativePath: dirname
 | 
			
		||||
        , path: path.join(dirname, stat.name)
 | 
			
		||||
 | 
			
		||||
          createdDate: (stat.birthtime || stat.ctime).toISOString(),
 | 
			
		||||
          lastModifiedDate: stat.mtime.toISOString(),
 | 
			
		||||
        , createdDate: (stat.birthtime||stat.ctime).toISOString()
 | 
			
		||||
        , lastModifiedDate: stat.mtime.toISOString()
 | 
			
		||||
 | 
			
		||||
          size: stat.size,
 | 
			
		||||
          type: undefined, // TODO include mimetype
 | 
			
		||||
        , size: stat.size
 | 
			
		||||
        , type: undefined // TODO include mimetype
 | 
			
		||||
        };
 | 
			
		||||
        files.push(file);
 | 
			
		||||
 | 
			
		||||
        if (!(opts.sha1sum || opts.content)) {
 | 
			
		||||
          return Promise.resolve();
 | 
			
		||||
          return PromiseA.resolve();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO stream sha1 (for assets)
 | 
			
		||||
        return fs
 | 
			
		||||
          .readFile(path.join(root, stat.name), null)
 | 
			
		||||
          .then(function (buffer) {
 | 
			
		||||
            var contents = buffer.toString("utf8");
 | 
			
		||||
            file.sha1 = sha1sum(contents);
 | 
			
		||||
            file.type = undefined;
 | 
			
		||||
        return fs.readFileAsync(path.join(root, stat.name), null).then(function (buffer) {
 | 
			
		||||
          var contents = buffer.toString('utf8')
 | 
			
		||||
            ;
 | 
			
		||||
 | 
			
		||||
            if (opts.contents) {
 | 
			
		||||
              file.contents = contents;
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
          file.sha1 = sha1sum(contents);
 | 
			
		||||
          file.type = undefined;
 | 
			
		||||
 | 
			
		||||
          if (opts.contents) {
 | 
			
		||||
            file.contents = contents;
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!opts.contents) {
 | 
			
		||||
@ -120,7 +125,7 @@ function walkDir(parent, sub, opts) {
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    walker.on("end", function () {
 | 
			
		||||
    walker.on('end', function () {
 | 
			
		||||
      resolve(files);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
@ -129,7 +134,9 @@ function walkDir(parent, sub, opts) {
 | 
			
		||||
function walkDirs(parent, subs, opts) {
 | 
			
		||||
  opts = opts || {};
 | 
			
		||||
 | 
			
		||||
  var collections = {};
 | 
			
		||||
  var collections = {}
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
  return forEachAsync(subs, function (sub) {
 | 
			
		||||
    return walkDir(parent, sub, opts).then(function (results) {
 | 
			
		||||
      collections[sub] = results;
 | 
			
		||||
@ -139,53 +146,59 @@ function walkDirs(parent, subs, opts) {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function getfs(blogdir, filepaths) {
 | 
			
		||||
  var files = [];
 | 
			
		||||
  var files = []
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
  return forEachAsync(filepaths, function (filepath) {
 | 
			
		||||
    var pathname = safeResolve(blogdir, filepath);
 | 
			
		||||
    return fs
 | 
			
		||||
      .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,
 | 
			
		||||
    var pathname = safeResolve(blogdir, filepath)
 | 
			
		||||
      ;
 | 
			
		||||
 | 
			
		||||
            createdDate: (stat.birthtime || stat.ctime).toISOString(),
 | 
			
		||||
            lastModifiedDate: stat.mtime.toISOString(),
 | 
			
		||||
    return fs.lstatAsync(pathname).then(function (stat) {
 | 
			
		||||
      return fs.readFileAsync(pathname, null).then(function (buffer) {
 | 
			
		||||
 | 
			
		||||
            contents: buffer.toString("utf8"),
 | 
			
		||||
            size: buffer.length,
 | 
			
		||||
            sha1: sha1sum(buffer),
 | 
			
		||||
            type: undefined,
 | 
			
		||||
          });
 | 
			
		||||
        files.push({
 | 
			
		||||
          name: path.basename(pathname)
 | 
			
		||||
        , relativePath: path.dirname(filepath)
 | 
			
		||||
        , path: filepath
 | 
			
		||||
 | 
			
		||||
        , createdDate: (stat.birthtime||stat.ctime).toISOString()
 | 
			
		||||
        , lastModifiedDate: stat.mtime.toISOString()
 | 
			
		||||
 | 
			
		||||
        , contents: buffer.toString('utf8')
 | 
			
		||||
        , size: buffer.length
 | 
			
		||||
        , sha1: sha1sum(buffer)
 | 
			
		||||
        , type: undefined
 | 
			
		||||
        });
 | 
			
		||||
      })
 | 
			
		||||
      .catch(function (e) {
 | 
			
		||||
        files.push({ path: filepath, error: e.message });
 | 
			
		||||
      });
 | 
			
		||||
    }).catch(function (e) {
 | 
			
		||||
      files.push({ path: filepath, error: e.message });
 | 
			
		||||
    });
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    return files;
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function makeAllDirs(dirpaths) {
 | 
			
		||||
  var errors = [];
 | 
			
		||||
  var errors = []
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
  return forEachAsync(dirpaths, function (pathname) {
 | 
			
		||||
    return fs.mkdir(pathname, { recursive: true }).catch(function (e) {
 | 
			
		||||
    return mkdirp(pathname).catch(function (e) {
 | 
			
		||||
      // TODO exclude attempting to write files to this dir?
 | 
			
		||||
      errors.push({
 | 
			
		||||
        type: "directory",
 | 
			
		||||
        type: 'directory'
 | 
			
		||||
 | 
			
		||||
        directory: pathname,
 | 
			
		||||
      , directory: pathname
 | 
			
		||||
 | 
			
		||||
        message: e.message,
 | 
			
		||||
        code: e.code,
 | 
			
		||||
        errno: e.errno,
 | 
			
		||||
        status: e.status,
 | 
			
		||||
        syscall: e.syscall,
 | 
			
		||||
      , message: e.message
 | 
			
		||||
      , code: e.code
 | 
			
		||||
      , errno: e.errno
 | 
			
		||||
      , status: e.status
 | 
			
		||||
      , syscall: e.syscall
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    return errors;
 | 
			
		||||
@ -194,162 +207,156 @@ function makeAllDirs(dirpaths) {
 | 
			
		||||
 | 
			
		||||
function copyfs(blogdir, files) {
 | 
			
		||||
  // TODO switch format to { source: ..., dest: ..., opts: ... } ?
 | 
			
		||||
  var results = { errors: [] },
 | 
			
		||||
    dirpaths = {},
 | 
			
		||||
    sources = Object.keys(files);
 | 
			
		||||
  var results = { errors: [] }
 | 
			
		||||
    , dirpaths = {}
 | 
			
		||||
    , sources = Object.keys(files)
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
  return forEachAsync(sources, function (source) {
 | 
			
		||||
    /*
 | 
			
		||||
    var nsource = safeResolve(blogdir, source)
 | 
			
		||||
      ;
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    var dest = safeResolve(blogdir, files[source]),
 | 
			
		||||
      pathname = path.dirname(dest);
 | 
			
		||||
    //, filename = path.basename(dest)
 | 
			
		||||
    var dest = safeResolve(blogdir, files[source])
 | 
			
		||||
      , pathname = path.dirname(dest)
 | 
			
		||||
      //, filename = path.basename(dest)
 | 
			
		||||
      ;
 | 
			
		||||
 | 
			
		||||
    dirpaths[pathname] = true;
 | 
			
		||||
    
 | 
			
		||||
    return 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 makeAllDirs(Object.keys(dirpaths)).then(function (errors) {
 | 
			
		||||
        errors.forEach(function (e) {
 | 
			
		||||
          results.errors.push(e);
 | 
			
		||||
    return PromiseA.resolve();
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    // TODO is it better to do this lazy-like or as a batch?
 | 
			
		||||
    // I figure as batch when there may be hundreds of files,
 | 
			
		||||
    // likely within 2 or 3 directories
 | 
			
		||||
    return makeAllDirs(Object.keys(dirpaths)).then(function (errors) {
 | 
			
		||||
      errors.forEach(function (e) {
 | 
			
		||||
        results.errors.push(e);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    // TODO allow delete?
 | 
			
		||||
    return forEachAsync(sources, function (source) {
 | 
			
		||||
      return fsExtra.copyAsync(
 | 
			
		||||
        safeResolve(blogdir, source)
 | 
			
		||||
      , safeResolve(blogdir, files[source])
 | 
			
		||||
      , { replace: true }
 | 
			
		||||
      ).catch(function (e) {
 | 
			
		||||
        results.errors.push({
 | 
			
		||||
          type: 'file'
 | 
			
		||||
 | 
			
		||||
        , source: source
 | 
			
		||||
        , destination: files[source]
 | 
			
		||||
 | 
			
		||||
        , message: e.message
 | 
			
		||||
        , code: e.code
 | 
			
		||||
        , errno: e.errno
 | 
			
		||||
        , status: e.status
 | 
			
		||||
        , syscall: e.syscall
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    })
 | 
			
		||||
    .then(function () {
 | 
			
		||||
      // TODO 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) {
 | 
			
		||||
  options = options || {};
 | 
			
		||||
 | 
			
		||||
  var putfsResults = { errors: [] },
 | 
			
		||||
    dirpaths = {};
 | 
			
		||||
  var putfsResults = { errors: [] }
 | 
			
		||||
    , dirpaths = {}
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
  return forEachAsync(files, function (file) {
 | 
			
		||||
    var filepath = safeResolve(
 | 
			
		||||
        blogdir,
 | 
			
		||||
        file.path || path.join(file.relativePath, file.name)
 | 
			
		||||
      ),
 | 
			
		||||
      pathname = path.dirname(filepath),
 | 
			
		||||
      filename = file.name || path.basename(filepath);
 | 
			
		||||
    var filepath = safeResolve(blogdir, file.path || path.join(file.relativePath, file.name))
 | 
			
		||||
      , pathname = path.dirname(filepath)
 | 
			
		||||
      , filename = file.name || path.basename(filepath)
 | 
			
		||||
      ;
 | 
			
		||||
 | 
			
		||||
    file.realPath = filepath;
 | 
			
		||||
    file.name = filename;
 | 
			
		||||
 | 
			
		||||
    dirpaths[pathname] = true;
 | 
			
		||||
    
 | 
			
		||||
    return 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",
 | 
			
		||||
    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,
 | 
			
		||||
        , directory: pathname
 | 
			
		||||
 | 
			
		||||
            message: e.message,
 | 
			
		||||
            code: e.code,
 | 
			
		||||
            errno: e.errno,
 | 
			
		||||
            status: e.status,
 | 
			
		||||
            syscall: e.syscall,
 | 
			
		||||
          });
 | 
			
		||||
        , message: e.message
 | 
			
		||||
        , code: e.code
 | 
			
		||||
        , errno: e.errno
 | 
			
		||||
        , status: e.status
 | 
			
		||||
        , syscall: e.syscall
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }).then(function () {
 | 
			
		||||
    // TODO sort deletes last
 | 
			
		||||
    return forEachAsync(files, function (file) {
 | 
			
		||||
      // TODO use lastModifiedDate as per client request?
 | 
			
		||||
      // TODO compare sha1 sums for integrity
 | 
			
		||||
      // NOTE existsAsync is backwards
 | 
			
		||||
      return fs.existsAsync(file.realPath).then(function () {
 | 
			
		||||
        return fs.writeFileAsync(file.realPath, file.contents, 'utf8');
 | 
			
		||||
      }).catch(function (/*exists*/) {
 | 
			
		||||
        if (file.delete || !file.contents) {
 | 
			
		||||
          return fs.unlinkAsync(file.realPath);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (false === options.replace || false === options.overwrite) {
 | 
			
		||||
          throw new Error('EEXIST: the file already exists');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return fs.writeFileAsync(file.realPath, file.contents, 'utf8');
 | 
			
		||||
      }).catch(function (e) {
 | 
			
		||||
        putfsResults.errors.push({
 | 
			
		||||
          type: 'file'
 | 
			
		||||
 | 
			
		||||
        , file: file.realPath
 | 
			
		||||
        , delete: !file.contents
 | 
			
		||||
        , path: file.path
 | 
			
		||||
        , relativePath: file.relativePath
 | 
			
		||||
        , name: file.name
 | 
			
		||||
 | 
			
		||||
        , message: e.message
 | 
			
		||||
        , code: e.code
 | 
			
		||||
        , errno: e.errno
 | 
			
		||||
        , status: e.status
 | 
			
		||||
        , syscall: e.syscall
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    })
 | 
			
		||||
    .then(function () {
 | 
			
		||||
      // TODO sort deletes last
 | 
			
		||||
      return forEachAsync(files, function (file) {
 | 
			
		||||
        // TODO use lastModifiedDate as per client request?
 | 
			
		||||
        // TODO compare sha1 sums for integrity
 | 
			
		||||
        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) {
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
exports.fsapi = require("./fsapi");
 | 
			
		||||
exports.sha1sum = require("./sha1sum").sha1sum;
 | 
			
		||||
exports.realFsapi = require("./fsapi-real");
 | 
			
		||||
exports.fsapi = require('./fsapi');
 | 
			
		||||
exports.sha1sum = require('./sha1sum').sha1sum;
 | 
			
		||||
exports.realFsapi = require('./fsapi-real');
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,9 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
var secretutils = require("secret-utils");
 | 
			
		||||
var PromiseA = require('bluebird').Promise
 | 
			
		||||
  , secretutils = require('secret-utils')
 | 
			
		||||
  ;
 | 
			
		||||
 | 
			
		||||
module.exports.sha1sum = function (str) {
 | 
			
		||||
  return Promise.resolve(secretutils.hashsum("sha1", str));
 | 
			
		||||
  return PromiseA.resolve( secretutils.hashsum('sha1', str) );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,40 +0,0 @@
 | 
			
		||||
/*jshint -W054 */
 | 
			
		||||
(function (exports) {
 | 
			
		||||
  "use strict";
 | 
			
		||||
 | 
			
		||||
  function renderMd(contentstr /*, desi*/) {
 | 
			
		||||
    var markitdown = (exports.markdownit || require("markdown-it"))({
 | 
			
		||||
      html: true,
 | 
			
		||||
      linkify: true,
 | 
			
		||||
    });
 | 
			
		||||
    return Promise.resolve(
 | 
			
		||||
      markitdown.render(contentstr)
 | 
			
		||||
      //.replace('"', '"')
 | 
			
		||||
      //.replace(''', "'")
 | 
			
		||||
      //.replace('/', '/')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function renderNoop(contentstr /*, desi*/) {
 | 
			
		||||
    // hmmm... that was easy
 | 
			
		||||
    return Promise.resolve(contentstr);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function renderJade(contentstr, desi, options) {
 | 
			
		||||
    options = options || {};
 | 
			
		||||
    if (!("pretty" in options)) {
 | 
			
		||||
      options.pretty = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var jade = exports.jade || require("jade"),
 | 
			
		||||
      fn = jade.compile(contentstr, options),
 | 
			
		||||
      html = fn(desi);
 | 
			
		||||
    return Promise.resolve(html);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  exports.DesiraeRenderMarkdown = renderMd.DesiraeRenderMarkdown = renderMd;
 | 
			
		||||
  exports.DesiraeRenderHtml = renderNoop.DesiraeRenderHtml = renderNoop;
 | 
			
		||||
  exports.DesiraeRenderCss = renderNoop.DesiraeRenderCss = renderNoop;
 | 
			
		||||
  exports.DesiraeRenderJs = renderNoop.DesiraeRenderJs = renderNoop;
 | 
			
		||||
  exports.DesiraeRenderJade = renderJade.DesiraeRenderJade = renderJade;
 | 
			
		||||
})(("undefined" !== typeof exports && exports) || window);
 | 
			
		||||
@ -1,210 +0,0 @@
 | 
			
		||||
/*jshint -W054 */
 | 
			
		||||
(function (exports) {
 | 
			
		||||
  "use strict";
 | 
			
		||||
 | 
			
		||||
  var cores = {},
 | 
			
		||||
    Desi = exports.Desirae || require("desirae").Desirae,
 | 
			
		||||
    path = exports.path || require("path");
 | 
			
		||||
  cores.lint = function (desi, env, collection, entity) {
 | 
			
		||||
    // TODO splice
 | 
			
		||||
    //desi.content.collections = desi.content.collections.filter(function (entity) {
 | 
			
		||||
    // TODO throw for any files that don't have a registered renderer
 | 
			
		||||
    if (!entity.yml) {
 | 
			
		||||
      if (!desi.config.empty_frontmatter) {
 | 
			
		||||
        throw new Error(
 | 
			
		||||
          "no frontmatter for " +
 | 
			
		||||
            (entity.path || entity.name) +
 | 
			
		||||
            "." +
 | 
			
		||||
            "Set `config.yml.empty_frontmatter: include|skip` to ignore this error."
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ("include" === desi.config.empty_frontmatter) {
 | 
			
		||||
        entity.yml = {};
 | 
			
		||||
      } else if ("skip" === desi.config.empty_frontmatter) {
 | 
			
		||||
        return false;
 | 
			
		||||
      } else {
 | 
			
		||||
        throw new Error(
 | 
			
		||||
          "unrecognize option " +
 | 
			
		||||
            desi.config.empty_frontmatter +
 | 
			
		||||
            " for `config.yml.empty_frontmatter: include|skip`."
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!entity.body || !entity.body.trim()) {
 | 
			
		||||
      if (!desi.config.empty_body) {
 | 
			
		||||
        throw new Error(
 | 
			
		||||
          "empty content file " +
 | 
			
		||||
            (entity.path || entity.name) +
 | 
			
		||||
            ". Set `config.yml.empty_body: include|skip` to ignore this error."
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ("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) {
 | 
			
		||||
    entity.yml = entity.yml || {};
 | 
			
		||||
 | 
			
		||||
    entity.layout = entity.yml.layout || "__page__";
 | 
			
		||||
 | 
			
		||||
    // _root is not subject to the same permalink rules as collections,
 | 
			
		||||
    // so we just go ahead and define that here
 | 
			
		||||
    if (/^index\.\w+$/.test(entity.path)) {
 | 
			
		||||
      entity.permalink = "/";
 | 
			
		||||
    } else {
 | 
			
		||||
      entity.permalink =
 | 
			
		||||
        entity.yml.permalink || entity.path.replace(/\.\w+$/, "/");
 | 
			
		||||
      entity.redirects = entity.redirects || [];
 | 
			
		||||
      entity.redirects.push(entity.permalink.replace(/\/$/, "/index.html"));
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  cores.normalize = function (desi, env, collection, entity) {
 | 
			
		||||
    entity.title =
 | 
			
		||||
      entity.yml.title || Desi.firstCap(entity.name.replace(/\.\w+$/, ""));
 | 
			
		||||
    entity.date = entity.yml.date;
 | 
			
		||||
 | 
			
		||||
    if (!entity.date) {
 | 
			
		||||
      // TODO tell YAML parser to keep the date a string
 | 
			
		||||
      entity.date = new Date(
 | 
			
		||||
        entity.yml.created_at ||
 | 
			
		||||
          entity.yml.time ||
 | 
			
		||||
          entity.yml.updated_at ||
 | 
			
		||||
          entity.createdDate ||
 | 
			
		||||
          entity.lastModifiedDate
 | 
			
		||||
      ).toISOString();
 | 
			
		||||
    }
 | 
			
		||||
    if ("object" === typeof entity.date) {
 | 
			
		||||
      entity.date = entity.date.toISOString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    entity.updated_at = entity.yml.updated_at || entity.lastModifiedDate;
 | 
			
		||||
 | 
			
		||||
    entity.published_at = Desi.fromLocaleDate(
 | 
			
		||||
      entity.date || entity.lastModifiedDate
 | 
			
		||||
    );
 | 
			
		||||
    entity.year = entity.published_at.year;
 | 
			
		||||
    entity.month = entity.published_at.month;
 | 
			
		||||
    entity.day = entity.published_at.day;
 | 
			
		||||
    entity.hour = entity.published_at.hour;
 | 
			
		||||
    entity.twelve_hour = entity.published_at.twelve_hour;
 | 
			
		||||
    entity.meridian = entity.published_at.meridian;
 | 
			
		||||
    entity.minute = entity.published_at.minute;
 | 
			
		||||
    // let's just agree that that's too far
 | 
			
		||||
    //entity.second = entity.published_at.second;
 | 
			
		||||
 | 
			
		||||
    entity.slug = Desi.slugify(entity.title);
 | 
			
		||||
    entity.slug_path = Desi.slugifyPath(entity.relativePath);
 | 
			
		||||
    entity.slugPath = Desi.slugifyPath(entity.relativePath);
 | 
			
		||||
    // TODO type checking like below
 | 
			
		||||
    entity.redirects =
 | 
			
		||||
      (Array.isArray(entity.yml.redirects) && entity.yml.redirects) || [];
 | 
			
		||||
 | 
			
		||||
    // categories
 | 
			
		||||
    if (Array.isArray(entity.yml.categories)) {
 | 
			
		||||
      entity.categories = entity.yml.categories;
 | 
			
		||||
    } else if ("string" === typeof entity.yml.categories) {
 | 
			
		||||
      entity.categories = [entity.yml.categories];
 | 
			
		||||
    } else if ("string" === typeof entity.yml.category) {
 | 
			
		||||
      entity.categories = [entity.yml.category];
 | 
			
		||||
    } else {
 | 
			
		||||
      entity.categories = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // tags
 | 
			
		||||
    if (Array.isArray(entity.yml.tags)) {
 | 
			
		||||
      entity.tags = entity.yml.tags;
 | 
			
		||||
    } else if ("string" === typeof entity.yml.tags) {
 | 
			
		||||
      entity.tags = [entity.yml.tags];
 | 
			
		||||
    } else {
 | 
			
		||||
      entity.tags = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    entity.permalink = entity.permalink || entity.yml.permalink;
 | 
			
		||||
 | 
			
		||||
    if (!entity.permalink) {
 | 
			
		||||
      // try the fallback_permalink first (because we're looking at files that don't have yml)
 | 
			
		||||
      // then try the normal permalink (because :filename -> :title and whatnot, so it'll work)
 | 
			
		||||
      entity.permalink = Desi.permalinkify(
 | 
			
		||||
        desi,
 | 
			
		||||
        collection.fallback_permalink || collection.permalink,
 | 
			
		||||
        entity
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    if (!/\.x?html?$/.test(entity.permalink)) {
 | 
			
		||||
      entity.htmllink = path.join(entity.permalink, 'index.html');
 | 
			
		||||
    }
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    // relative to the site
 | 
			
		||||
    entity.relative_file = path
 | 
			
		||||
      .join(env.base_path, entity.permalink)
 | 
			
		||||
      .replace(/\/$/, "/index.html");
 | 
			
		||||
    entity.relative_href = path
 | 
			
		||||
      .join(env.base_path, entity.permalink)
 | 
			
		||||
      .replace(/\/index\.html$/, "/");
 | 
			
		||||
    entity.relative_link = entity.relative_href;
 | 
			
		||||
 | 
			
		||||
    entity.url =
 | 
			
		||||
      env.base_url +
 | 
			
		||||
      path.join(env.base_path, entity.permalink).replace(/\/index\.html$/, "/");
 | 
			
		||||
    entity.canonical_url =
 | 
			
		||||
      env.base_url +
 | 
			
		||||
      path.join(env.base_path, entity.permalink).replace(/\/index\.html$/, "/");
 | 
			
		||||
    entity.production_url =
 | 
			
		||||
      desi.site.base_url +
 | 
			
		||||
      path
 | 
			
		||||
        .join(desi.site.base_path, entity.permalink)
 | 
			
		||||
        .replace(/\/index\.html$/, "/");
 | 
			
		||||
    entity.relative_url = path
 | 
			
		||||
      .join(env.base_path, entity.permalink)
 | 
			
		||||
      .replace(/\/index\.html$/, "/");
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
      env.explicitIndexes ||
 | 
			
		||||
      env.explicitIndices ||
 | 
			
		||||
      env.explicit_indexes ||
 | 
			
		||||
      env.explicit_indices
 | 
			
		||||
    ) {
 | 
			
		||||
      // NOTE: file_url is NOT replaced
 | 
			
		||||
      ["url", "canonical_url", "production_url", "relative_url"].forEach(
 | 
			
		||||
        function (url) {
 | 
			
		||||
          entity[url] = entity[url].replace(/\/$/, "/index.html");
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // i.e. bootstrap, hero page, darkly
 | 
			
		||||
    entity.theme = entity.theme || entity.yml.theme;
 | 
			
		||||
    entity.layout = entity.layout || entity.yml.layout;
 | 
			
		||||
    entity.swatch = entity.swatch || entity.yml.swatch;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  cores.disqus = function (desi, env, collection, entity) {
 | 
			
		||||
    var yml = entity.yml;
 | 
			
		||||
    if (yml.uuid) {
 | 
			
		||||
      entity.disqus_identifier = yml.uuid;
 | 
			
		||||
    }
 | 
			
		||||
    entity.disqus_url = entity.production_url;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  exports.DesiraeTransformCore = cores.DesiraeTransformCore = cores;
 | 
			
		||||
})(("undefined" !== typeof exports && exports) || window);
 | 
			
		||||
							
								
								
									
										106
									
								
								lib/utils.js
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								lib/utils.js
									
									
									
									
									
								
							@ -1,7 +1,9 @@
 | 
			
		||||
(function (exports) {
 | 
			
		||||
  "use strict";
 | 
			
		||||
;(function (exports) {
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
  var path  = exports.path  || require('path')
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
  var path = exports.path || require("path");
 | 
			
		||||
  function escapeRegExp(str) {
 | 
			
		||||
    return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
 | 
			
		||||
  }
 | 
			
		||||
@ -9,8 +11,10 @@
 | 
			
		||||
  function safeResolve(basename, target) {
 | 
			
		||||
    basename = path.resolve(basename);
 | 
			
		||||
 | 
			
		||||
    var targetname = path.resolve(basename, target),
 | 
			
		||||
      re = new RegExp("^" + escapeRegExp(basename) + "(/|$)");
 | 
			
		||||
    var targetname = path.resolve(basename, target)
 | 
			
		||||
      , re = new RegExp('^' + escapeRegExp(basename) + '(/|$)')
 | 
			
		||||
      ;
 | 
			
		||||
 | 
			
		||||
    return re.test(targetname) && targetname;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -18,24 +22,29 @@
 | 
			
		||||
  exports.escapeRegExp = escapeRegExp;
 | 
			
		||||
 | 
			
		||||
  function create(Desi) {
 | 
			
		||||
    var fsapi = Desi.fsapi || require("./node-adapters").fsapi;
 | 
			
		||||
    var fsapi = Desi.fsapi || require('./node-adapters').fsapi
 | 
			
		||||
      ;
 | 
			
		||||
 | 
			
		||||
    fsapi.getConfigs = function (confs) {
 | 
			
		||||
      var opts = {
 | 
			
		||||
        extensions: ["yml", "yaml", "json"],
 | 
			
		||||
        dotfiles: false,
 | 
			
		||||
        contents: true,
 | 
			
		||||
        sha1sum: true,
 | 
			
		||||
      };
 | 
			
		||||
      var opts = { extensions: ['yml', 'yaml', 'json'], dotfiles: false, contents: true, sha1sum: true }
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
      return fsapi.getMeta(confs, opts).then(function (collections) {
 | 
			
		||||
        var obj = {};
 | 
			
		||||
        var obj = {}
 | 
			
		||||
          ;
 | 
			
		||||
 | 
			
		||||
        Object.keys(collections).forEach(function (key) {
 | 
			
		||||
          var files = collections[key],
 | 
			
		||||
            keyname = key.replace(/\.(json|ya?ml|\/)$/i, "");
 | 
			
		||||
          var files = collections[key]
 | 
			
		||||
            , keyname = key.replace(/\.(json|ya?ml|\/)$/i, '')
 | 
			
		||||
            ;
 | 
			
		||||
 | 
			
		||||
          obj[keyname] = obj[keyname] || {};
 | 
			
		||||
 | 
			
		||||
          files.forEach(function (file) {
 | 
			
		||||
            var filename = file.name.replace(/\.(json|ya?ml)$/i, ""),
 | 
			
		||||
              data = {};
 | 
			
		||||
            var filename = file.name.replace(/\.(json|ya?ml)$/i, '')
 | 
			
		||||
              , data = {}
 | 
			
		||||
              ;
 | 
			
		||||
 | 
			
		||||
            if (file.error) {
 | 
			
		||||
              console.error(file);
 | 
			
		||||
              console.error(file.error);
 | 
			
		||||
@ -48,16 +57,17 @@
 | 
			
		||||
                if ("undefined" === data) {
 | 
			
		||||
                  data = {};
 | 
			
		||||
                }
 | 
			
		||||
              } catch (e) {
 | 
			
		||||
              } catch(e) {
 | 
			
		||||
                data = { error: e };
 | 
			
		||||
                console.error("Could not parse yaml for " + filename);
 | 
			
		||||
                console.error(file);
 | 
			
		||||
                console.error(e);
 | 
			
		||||
              }
 | 
			
		||||
            } else if (/\.(json)$/i.test(file.name)) {
 | 
			
		||||
            }
 | 
			
		||||
            else if (/\.(json)$/i.test(file.name)) {
 | 
			
		||||
              try {
 | 
			
		||||
                data = JSON.parse(file.contents) || {};
 | 
			
		||||
              } catch (e) {
 | 
			
		||||
              } catch(e) {
 | 
			
		||||
                data = { error: e };
 | 
			
		||||
                console.error("Could not parse json for " + filename);
 | 
			
		||||
                console.error(file);
 | 
			
		||||
@ -68,10 +78,7 @@
 | 
			
		||||
              console.error(file);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            data = obj[keyname][filename] = data || obj[keyname][filename];
 | 
			
		||||
            if (data) {
 | 
			
		||||
              data.filename = file.name;
 | 
			
		||||
            }
 | 
			
		||||
            obj[keyname][filename] = data || obj[keyname][filename];
 | 
			
		||||
            /*
 | 
			
		||||
            if (!obj[keyname][filename]) {
 | 
			
		||||
              obj[keyname][filename] = {};
 | 
			
		||||
@ -89,34 +96,36 @@
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    fsapi.getAllPartials = function () {
 | 
			
		||||
      return fsapi
 | 
			
		||||
        .getConfigs(["partials", "partials.yml"])
 | 
			
		||||
        .then(function (results) {
 | 
			
		||||
          var partials = {};
 | 
			
		||||
          Object.keys(results.partials).forEach(function (key) {
 | 
			
		||||
            var partial = results.partials[key];
 | 
			
		||||
            Object.keys(partial).forEach(function (prop) {
 | 
			
		||||
              if (partials[prop]) {
 | 
			
		||||
                console.warn("partial '" + prop + "' overwritten by " + key);
 | 
			
		||||
              }
 | 
			
		||||
      return fsapi.getConfigs(['partials', 'partials.yml']).then(function (results) {
 | 
			
		||||
        var partials = {}
 | 
			
		||||
          ;
 | 
			
		||||
 | 
			
		||||
              partials[prop] = partial[prop];
 | 
			
		||||
            });
 | 
			
		||||
        Object.keys(results.partials).forEach(function (key) {
 | 
			
		||||
          var partial = results.partials[key]
 | 
			
		||||
            ;
 | 
			
		||||
 | 
			
		||||
          Object.keys(partial).forEach(function (prop) {
 | 
			
		||||
            if (partials[prop]) {
 | 
			
		||||
              console.warn('partial \'' + prop + '\' overwritten by ' + key);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            partials[prop] = partial[prop];
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          return partials;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return partials;
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    fsapi.getAllConfigFiles = function () {
 | 
			
		||||
      return fsapi
 | 
			
		||||
        .getConfigs(["config.yml", "site.yml", "authors"])
 | 
			
		||||
        .then(function (results) {
 | 
			
		||||
          var authors = results.authors,
 | 
			
		||||
            config = results.config.config,
 | 
			
		||||
            site = results.site.site;
 | 
			
		||||
          return { config: config, authors: authors, site: site };
 | 
			
		||||
        });
 | 
			
		||||
      return fsapi.getConfigs(['config.yml', 'site.yml', 'authors']).then(function (results) {
 | 
			
		||||
        var authors = results.authors
 | 
			
		||||
          , config = results.config.config
 | 
			
		||||
          , site = results.site.site
 | 
			
		||||
          ;
 | 
			
		||||
 | 
			
		||||
        return { config: config, authors: authors, site: site };
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return exports;
 | 
			
		||||
@ -124,7 +133,8 @@
 | 
			
		||||
 | 
			
		||||
  if (exports.Desirae) {
 | 
			
		||||
    create(exports.Desirae);
 | 
			
		||||
  } else {
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    exports.create = create;
 | 
			
		||||
  }
 | 
			
		||||
})(("undefined" !== typeof exports && exports) || window);
 | 
			
		||||
}('undefined' !== typeof exports && exports || window));
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
(function (exports) {
 | 
			
		||||
  "use strict";
 | 
			
		||||
;(function (exports) {
 | 
			
		||||
  'use strict';
 | 
			
		||||
 | 
			
		||||
  exports.verifyConfig = function (conf) {
 | 
			
		||||
    if (!conf.NuhohSpec) {
 | 
			
		||||
@ -28,18 +28,14 @@
 | 
			
		||||
 | 
			
		||||
    if (!Array.isArray(conf.collections)) {
 | 
			
		||||
      if (conf.posts) {
 | 
			
		||||
        console.error(
 | 
			
		||||
          "Please indent and nest 'posts' under the key 'collection' to continue"
 | 
			
		||||
        );
 | 
			
		||||
        console.error("Please indent and nest 'posts' under the key 'collection' to continue");
 | 
			
		||||
      }
 | 
			
		||||
      throw new Error("missing key 'collections'.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!conf.themes) {
 | 
			
		||||
      if (conf.twitter) {
 | 
			
		||||
        console.error(
 | 
			
		||||
          "Please indent and nest 'twitter' under the key 'themes' to continue"
 | 
			
		||||
        );
 | 
			
		||||
        console.error("Please indent and nest 'twitter' under the key 'themes' to continue");
 | 
			
		||||
      }
 | 
			
		||||
      throw new Error("missing key 'themes'");
 | 
			
		||||
    }
 | 
			
		||||
@ -59,4 +55,4 @@
 | 
			
		||||
      throw new Error("missing key root");
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
})(("undefined" !== typeof exports && exports) || window);
 | 
			
		||||
}('undefined' !== typeof exports && exports || window));
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										175
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										175
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -1,175 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "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="
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								package.json
									
									
									
									
									
								
							@ -1,15 +1,14 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "desirae",
 | 
			
		||||
  "version": "0.12.4",
 | 
			
		||||
  "version": "0.9.4",
 | 
			
		||||
  "description": "An in-browser static blog library and static site generator. Similar to Jekyll, Octopress, Nanoc, etc",
 | 
			
		||||
  "main": "desirae.js",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "prettier": "prettier --write './**/*.{js,css,html,json,md,py,xml}'",
 | 
			
		||||
    "test": "echo \"Error: no test specified\" && exit 1"
 | 
			
		||||
  },
 | 
			
		||||
  "repository": {
 | 
			
		||||
    "type": "git+https",
 | 
			
		||||
    "url": "https://git.coolaj86.com/coolaj86/desirae.js.git"
 | 
			
		||||
    "type": "git",
 | 
			
		||||
    "url": "git@github.com:DearDesi/desirae.git"
 | 
			
		||||
  },
 | 
			
		||||
  "keywords": [
 | 
			
		||||
    "dear",
 | 
			
		||||
@ -23,19 +22,21 @@
 | 
			
		||||
    "octopress",
 | 
			
		||||
    "nanoc"
 | 
			
		||||
  ],
 | 
			
		||||
  "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
 | 
			
		||||
  "license": "MPL-2.0",
 | 
			
		||||
  "author": "AJ ONeal",
 | 
			
		||||
  "license": "Apache2",
 | 
			
		||||
  "bugs": {
 | 
			
		||||
    "url": "https://git.coolaj86.com/coolaj86/desirae.js/issues"
 | 
			
		||||
    "url": "https://github.com/DearDesi/desirae/issues"
 | 
			
		||||
  },
 | 
			
		||||
  "homepage": "https://git.coolaj86.com/coolaj86/desirae.js",
 | 
			
		||||
  "homepage": "https://github.com/DearDesi/desirae",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "bluebird": "^2.5.3",
 | 
			
		||||
    "escape-string-regexp": "^1.0.2",
 | 
			
		||||
    "foreachasync": "^5.0.5",
 | 
			
		||||
    "fs.extra": "^1.3.2",
 | 
			
		||||
    "js-yaml": "^3.2.5",
 | 
			
		||||
    "markdown-it": "^3.0.2",
 | 
			
		||||
    "mustache": "^2.3.2",
 | 
			
		||||
    "mkdirp": "^0.5.0",
 | 
			
		||||
    "mustache": "^1.0.0",
 | 
			
		||||
    "node-uuid": "^1.4.2",
 | 
			
		||||
    "secret-utils": "^1.0.2",
 | 
			
		||||
    "walk": "^2.3.9"
 | 
			
		||||
 | 
			
		||||
@ -1,88 +0,0 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
// http://ruhoh.com/docs/2/pages/#toc_41
 | 
			
		||||
/*
 | 
			
		||||
  If a page has a permalink, that permalink should be respected.
 | 
			
		||||
  Otherwise it should use the the permalink for that collection.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
var tags,
 | 
			
		||||
  permalinkTransforms,
 | 
			
		||||
  cases,
 | 
			
		||||
  path = /*exports.path ||*/ require("path");
 | 
			
		||||
tags = {
 | 
			
		||||
  year: "Year from the page’s filename",
 | 
			
		||||
  month: "Month from the page’s filename",
 | 
			
		||||
  day: "Day from the page’s filename",
 | 
			
		||||
  path: "The page file's path relative to the base of your website.",
 | 
			
		||||
  relative_path: "The page file's path relative to its name-spaced directory.",
 | 
			
		||||
  filename: "The page file's filename (path is not included).",
 | 
			
		||||
  categories:
 | 
			
		||||
    "The specified categories for this page. If more than one category is set, only the first one is used. If no categories exist, the URL omits this parameter.",
 | 
			
		||||
  i_month: "Month from the page’s filename without leading zeros.",
 | 
			
		||||
  i_day: "Day from the page’s filename without leading zeros.",
 | 
			
		||||
  title: "The title, as a slug.",
 | 
			
		||||
  slug: "alias of title",
 | 
			
		||||
  name: "alias of title",
 | 
			
		||||
  collection: "i.e. posts/ or essays/ or whatever/",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function pad(str, n) {
 | 
			
		||||
  str = str.toString();
 | 
			
		||||
  if (str.length < n) {
 | 
			
		||||
    str = "0" + str;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return str;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://www.youtube.com/watch?v=1NryFD9_hR0&list=RDOeLUK4a6Ojc&index=2
 | 
			
		||||
cases = {
 | 
			
		||||
  "/:title.html": "/my-title.html",
 | 
			
		||||
  ":title/": "/my-title/",
 | 
			
		||||
  "/:bad/:title/": "/:bad/my-title/",
 | 
			
		||||
  "/:slug/": "/my-title/",
 | 
			
		||||
  "/:path/:name.html": "/posts/fun/my-title.html",
 | 
			
		||||
  "/:relative_path/:name/": "/fun/my-title/",
 | 
			
		||||
  "/:year-:month-:day/:name": "/2015-07-04/my-title/",
 | 
			
		||||
  "/:year/:i_month/:i_day/:name": "/2015/7/4/my-title/",
 | 
			
		||||
  "/:filename.html": "/my-file-name.html",
 | 
			
		||||
  "/:filename": "/my-file-name/",
 | 
			
		||||
  "/:filename/": "/my-file-name/",
 | 
			
		||||
  "/:collection/:title/": "/posts/my-title/",
 | 
			
		||||
  "/:collection/:filename": "/posts/my-file-name/",
 | 
			
		||||
  "/:something/:or/:other": "/:something/:or/:other/",
 | 
			
		||||
  "/:categories/:title/": "/desi/my-title/",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Object.keys(cases).forEach(function (tpl) {
 | 
			
		||||
  var entity, tpld;
 | 
			
		||||
 | 
			
		||||
  entity = {
 | 
			
		||||
    year: "2015",
 | 
			
		||||
    month: "07",
 | 
			
		||||
    day: "04",
 | 
			
		||||
    title: "My Title",
 | 
			
		||||
    slug: "my-title",
 | 
			
		||||
    name: "My-File-Name.html",
 | 
			
		||||
    relativePath: "posts/fun",
 | 
			
		||||
    path: "posts/fun/My-File-Name.html",
 | 
			
		||||
    collection: "posts",
 | 
			
		||||
    yml: { categories: ["desi"] },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  tpld = permalinker(tpl, entity);
 | 
			
		||||
 | 
			
		||||
  if (cases[tpl] !== tpld) {
 | 
			
		||||
    console.error("[ERROR]");
 | 
			
		||||
    console.error(tpl + " " + tpld + " " + cases[tpl]);
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      "Did not template permalink correctly. " +
 | 
			
		||||
        tpl +
 | 
			
		||||
        " " +
 | 
			
		||||
        tpld +
 | 
			
		||||
        " " +
 | 
			
		||||
        cases[tpl]
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user