sweeney

A static site generator that cuts the way you want it to

            $ npm install sweeney -g 
          
Getting Started
Current release: v1.1.0
Getting Started
Sweeney has very few built in assumptions. What it tends to do is let the user create the ideal environmnet to fit their use case.

It all starts with a .sweeney file.

module.exports = async function() {
  return {
    render: (type, content) => {
      return content;
    },
    source: './',
    output: './site',
    include: ['./file.css', './path/to/directory']
  };
};
        

render: This is function that will be called before sweeney applies the template and can be used to convert markdown to html or any other templating engine you wish to use.
source: This is the relative source directory from this file, by default it will be the current working directory.
output: This is the relative output directroy from this file, by default it will be the directory site in the current working directory.
include: This is an array of files or directories that will be copied over that contain static assets that you want to include in your project but do not wish to inject using the include template. (This could include, but not limited to, fonts, images, documents, etc)
That's all! Anything else that is added to this config object will be injected at the top level of every component that is rendered.

These options can be accessible via the CLI options.
Templates
It's important to note that files with an .sy extension will only be interpreted as a renderable by sweeney. This is for the primary purpose of making sure template files do not overwrite or interpret unecessary files.

Instead of learning a template language, anything you can do with javascript you can do with sweeney. So what do I mean?

Looping through an array of objects is pretty simple:

['earth', 'saturn', 'pluto'].forEach(() => {...});
            
Using the same syntax you can create html that you can inject in your templates with the following markup:
<ul>
  {{
    ['earth', 'saturn', 'pluto'].map((planet) => `<li>${planet}</li>`).join('')
  }}
</ul>
            
Or if planets was defined in the .sweeney file or in the page options the template would look like:
<ul>
  {{
    planets.map((planet) => `<li>${planet}</li>`).join('')
  }}
</ul>
            
The join is necessary because by default the to string method for a string will add , by default.

Another powerful thing about sweeney is the idea of page options.

---
{
  layout: 'posts',
  collection: 'posts',
  tags: ['hello', 'world'],
  title: 'bool',
  slug: 'testing-this-out'
}
---
            
This is just JSON! What this example shows is that:
  • layout: use the default layout (this is a file that was named default with a type of layout)
  • collection: expose these options at the top level of any file that wants to look at all the posts files and their options at the top level. This let's other templates (the posts page for example) to look like
    <ul>
      {{
        posts.map((post) => `<li>${post.title}</li>`).join('')
      }}
    </ul>
                    
  • tags/title/slug/etc: Any other options that the user wants to expose to the page itself. This allows the template designer or the user to really be creative with what the content and design looks like.

Writing a plugin

Writing a plugin has two components, the parse method. This function usually makes sure everything is properly formatted and if anything that needs to be exposed to the render method. The second part being the render method. The render method is where the content is altered or the desired effect takes place.

I am going to walk you through what a simple include plugin might do when faced with a css entity. To see a fully working include function, go checkout the defaultPlugins in the source
const path = require('path');
const Plugin = require('sweeney/lib/plugin');

module.exports.include = class IncludePlugin extends Plugin {
  static async parse(filePath, content) {
    // we are looking for the pattern {{-- includes ... }} in the template
    // this could be any pattern, it does not need to look like the handlebars syntax it could be [[== includes ... ==]]
    // that is the extensibility of the plugin architecture

    const reg = /{{-- includes (.+?) --}}/g;

    if(content.match(reg)) {
      let found = [];
      let block;
      while ((block = reg.exec(content)) != null) {
        let oldArgument = block[1];
        let newArgument = path.resolve(path.dirname(filePath), oldArgument);

        content = content.replace(`{{-- includes ${oldArgument} --}}`, `{{-- includes ${newArgument} --}}`);
        found.push(newArgument);
      }
      // we pass comtent and found to parseString method (which calls this) to update the content string
      // we changed and to add the items to the global object to be used for later.
      return {
        content,
        found
      };
    }
    return false;
  }
  static async render(plugins, filePath, content, templates, data, found) {
    const start = process.hrtime();
    let ext = found.substring(found.lastIndexOf('.') + 1, found.length);
    let name = found.substring(found.lastIndexOf('/') + 1, found.length - 3);
    let depends = [];

    if(ext === 'css') {
      let _content = await readFile(found, 'utf8');

      content = content.replace(`{{-- includes ${found} --}}`, ``);

      // ensure this content gets added to the dependency tree
      // the depends object is a little more involved then this internally
      // but for the sake of the plugin, sweeney only expects filePath and time to be passed back
      depends.push({
        filePath: found,
        time: process.hrtime(start)[1]/1000000
      });
    }

    return {
      // the depends object is something that is maintained internally to sweeney
      // but templates can add other items to this to show the
      // user what has effected the render time and when it happened.
      depends,
      content
    };
  }
}
          

That is a simple include plugin!

Plugins don't necessarily have to alter the content. In the future plugins will be able to happen during certain life cycle events.

The data passed back in found, will be set to the config[' name of plugin'] for example, if you have a plugin named include then config['include'] would have the information for the found data during parse. This can be used in templates or even in other plugins to be able to chain plugins together. Using other data gathered through parse and altered during render.