# 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>.

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 is a static webpage compiler written in JavaScript.

It can run entirely in the browser
(with a little help from a minimal just to serve to read and save files).

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).

# Installation

```bash
bower install --save desirae

npm install --save desirae
```

# Why

Because I hate ruby.

Well, I don't hate it, but it hates me. Or at least it moves too fast and has
too many breaking changes between updates.

Anyway, I had to drop [ruhoh](http://ruhoh.com) because I made a new year's resolution to blog
more (last year I made dozens of gists and 0 blog posts) and I just couldn't get the right ruby
versions and gems and whatnot... so I gave up and wrote my own (because I needed something
compatible with ruhoh).

# Usage Overview

### Before we get started

(disclaimers)

**Browser**: The default fs adapter will request the config files from `/api/fs/`.

**Node**: Remember that desirae is built browser-first, and optimized to reduce the number of round-trips
(in case someone actually desides to host a Desi service, y'know?), so... the node adapter is built with
the server in mind. If you can't respect that, don't look at the code. :-)

**NOTE**: The mixing of `snake_case` with `camelCase` is somewhat intentional.
It's an artifact of this project being born out of the ashes of my
[ruhoh](http://ruhoh.com/) blog, which is built in ruby and uses YAML.

### Getting Started

First off you need to declare a state object that will be used in every _desirae_ action.

```javascript
var desi = {};
```

After that you'll load any plugins you need.

Here's how you would load all of the common plugins:

```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 }
);

//
// 2. Register Aggregators (rss, categories, tags, etc)
//
Desi.registerAggregator(dload('desirae/lib/aggregate-core', 'DesiraeAggregateCore').collate);

//
// 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");
});
```

Using the paths specified in the environment it will read the appropriate
`config.yml`, `site.yml`, and `authors/*.yml` files to initialize itself.

Then you can specify to build the static blog. You'll need to pass the environment again.

```
Desirae.buildAll(desi, env).then(function () {
  console.log('Desirae built your blog!');
});
```

Finally, you save the built out to disk.

```
Desirae.write(desi, env).then(function () {
  console.log('Desirae pushd all files to the appropriate fs adapter!');
});
```

### 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

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.

If any of these files change, the entire site needs to be retemplated.

# 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)
and still pass the string along to the 'real' markdown parser afterwards.

But here's what I've got so far:

## Rendering Engines

### (html, markdown, jade)

- `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));
}

Desirae.registerRenderer(".slim", render);
```

## Data Mapping

### (desirae, ruhoh, etc)

- `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
(i.e. it wants `{{ page.name }}` instead of `{{ entity.title }}`), you can use a
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 ?_

```javascript
Desirae.registerDataMapper("ruhoh@3.0", function (view) {
  return {
    page: {
      name: view.entity.title,
    },
    author: {
      nickname: view.author.twitter,
    },
    // ...
  };
});
```

The default datamapper is `ruhoh@2.6`

(note that that might be misnamed, I'm a little confused as to how Ruhoh's template versions correspond
to Ruhoh proper versions).

## Adapters

### (fs, http, dropbox)

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

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 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`.

```json
[
  {
    "name": "happy-new-year.md",
    "createdDate": "2015-01-05T18:19:30.000Z",
    "lastModifiedDate": "2015-01-05T18:19:30.000Z",
    "size": 2121,
    "relativePath": "posts/2015"
  },

  {
    "name": "tips-for-the-ages.jade",
    "createdDate": "2014-06-16T18:19:30.000Z",
    "lastModifiedDate": "2014-06-16T18:19:30.000Z",
    "size": 389,
    "relativePath": "posts"
  },
  {
    "name": "my-first-post.html",
    "createdDate": "2013-08-01T22:47:37.000Z",
    "lastModifiedDate": "2013-08-01T22:47:37.000Z",
    "size": 4118,
    "relativePath": "posts/2013"
  }
]
```

To retrieve multiple dir listings at once:

- 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.

```
POST http://local.dear.desi:8080/api/fs/walk?dotfiles=true&extensions=md,markdown,jade,htm,html&_method=GET

{ "dirs": [ "old", "2013,12", "2013,11" ] }
```

```javascript
{
  "posts/2015": [ { "name": ... }, { ... } ]
, "posts/2013": [ ... ]
}
```

## 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"
}
```

To retrieve multiple files at once:

- 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.

```
POST http://local.dear.desi:8080/api/fs/files?dotfiles=true&extensions=md,markdown,jade,htm,html&_method=GET

{ "paths": [ "posts/foo.md", "posts/2013,11,30.md" ] }
```

```json
[
  { "path": "posts/foo.md"
  , "lastModifiedDate": "2013-08-01T22:47:37.000Z"
  , "contents": "..."
  , "sha1": "6eae3a5b062c6d0d79f070c26e6d62486b40cb46"
  }
, ...
]
```

## 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).

`_method=PUT` is just for funzies.

Including `sha1` is optional, but recommended.

`lastModifiedDate` is optional and may or may not make any difference.

`strict` (not yet implemented) fail immediately and completely on any error

```json
POST http://local.dear.desi:8080/api/fs/files?compiled=true&_method=PUT

{
  "files": [
    { "path": "posts/foo.md"
    , "name": "foo.md"
    , "relativePath": "posts"
    , "createdDate": "2013-08-01T22:47:37.000Z"
    , "lastModifiedDate": "2013-08-01T22:47:37.000Z"
    , "contents": "..."
    , "sha1": "6eae3a5b062c6d0d79f070c26e6d62486b40cb46"
    , "delete": false
    }
  , ...
  ]
}
```

The response may include errors of all shapes and sizes.

```json
{ "error": { message: "any top-level error", ... }
, "errors": [
    { "type": "file|directory"
    , "message": "maybe couldn't create the directory, but maybe still wrote the file. Maybe not"
    }
  , ...
  ]
}
```

## POST /api/fs/copy

```json
{ "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/.
