Socket
Socket
Sign inDemoInstall

permalinks

Package Overview
Dependencies
45
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.3.1 to 1.0.0

lib/convert.js

344

index.js

@@ -1,128 +0,274 @@

/*!
* permalinks <https://github.com/assemble/permalinks>
'use strict';
var convert = require('./lib/convert');
var utils = require('./lib/utils');
/**
* Create an instance of `Permalinks` with the given `options`
*
* Copyright (c) 2014, Jon Schlinkert, Brian Woodward, contributors.
* Licensed under the MIT License
*
* ```js
* var permalinks = new Permalinks();
* console.log(permalinks.format(':stem/index.html'), {path: 'src/about.hbs'});
* //=> 'about/index.html'
* ```
* @param {Options|String} `options`
* @api public
*/
var path = require('path');
var Strings = require('strings');
var digits = require('digits');
var randomatic = require('randomatic');
var _str = require('underscore.string');
var _ = require('lodash');
function Permalinks(options) {
if (typeof options === 'string') {
let proto = Object.create(Permalinks.prototype);
Permalinks.call(proto);
return proto.format.apply(proto, arguments);
}
if (!(this instanceof Permalinks)) {
let proto = Object.create(Permalinks.prototype);
Permalinks.call(proto);
return proto;
}
var join = function() {
var filepath = path.join.apply(path, arguments);
return filepath.replace(/\\/g, '/');
this.options = utils.assign({}, options);
this.helpers = this.options.helpers || {};
this.presets = this.options.presets || {};
this.data = this.options.data || {};
}
/**
* Uses [parse-filepath][] to parse the `file.path` on the given file
* object. This method is called by the [format](#format) method, but
* you can use it directly and pass the results as `locals` (the last
* argument) to the `.format` method if you need to override or modify
* any path segments.
*
* ```js
* console.log(permalinks.parse({path: 'foo/bar/baz.md'}));
* // { root: '',
* // dir: 'foo/bar',
* // base: 'baz.md',
* // ext: '.md',
* // name: 'baz',
* // extname: '.md',
* // basename: 'baz.md',
* // dirname: 'foo/bar',
* // stem: 'baz',
* // path: 'foo/bar/baz.md',
* // absolute: [Getter/Setter],
* // isAbsolute: [Getter/Setter] }
* ```
* @param {Object} `file`
* @return {Object}
* @api public
*/
Permalinks.prototype.parse = function(file) {
if (!utils.isObject(file)) {
throw new TypeError('expected file to be an object');
}
if (!file.path) return file;
var data = utils.parse(file.path);
for (var key in file) {
if (file.hasOwnProperty(key) && typeof file[key] === 'string') {
data[key] = file[key];
}
}
return data;
};
/**
* Generate a permalink by replacing `:prop` placeholders in the specified
* `structure` with data from the given `file` and `locals`.
*
* ```js
* var fp = permalinks.format('blog/:stem/index.html', {path: 'src/about.hbs'});
* console.log(fp);
* //=> 'blog/about/index.html'
* ```
* @param {String} `structure` Permalink structure or the name of a registered [preset](#preset).
* @param {Object|String} `file` File object or file path string.
* @param {Object} `locals` Any additional data to use for resolving placeholders.
* @return {String}
* @api public
*/
module.exports = function Permalinks(structure, context, options) {
var args = arguments;
Permalinks.prototype.format = function(structure, file, locals) {
if (typeof structure !== 'string') {
locals = file;
file = structure;
structure = null;
}
if (_.isObject(structure) && args.length === 2) {
options = context;
context = structure;
structure = options.structure || '';
} else if (_.isObject(structure) && args.length === 1) {
options = structure;
context = options.context || {};
structure = options.structure || '';
if (typeof file === 'string') {
file = { path: file };
}
options = options || {};
var permalinks = new Strings(context);
file = utils.formatFile(file, this.options);
var context = this.context(file, locals, this.options);
var pattern = utils.get(file, 'data.permalink.structure') || this.preset(structure);
// Allow user-defined length to be provided (for array of files)
var l = options.length || 3;
var i = options.index || 0;
return this.render(pattern, {
helpers: context.helpers,
data: context.data
});
};
/**
* Define a permalink `preset` with the given `name` and `structure`.
*
* ```js
* permalinks.preset('blog', 'blog/:stem/index.html');
* var url = permalinks.format('blog', {path: 'src/about.hbs'});
* console.log(url);
* //=> 'blog/about/index.html'
* ```
* @param {String} `name` If only the name is passed,
* @param {String} `structure`
* @return {Object} Returns the `Permalinks` instance for chaining
* @api public
*/
Permalinks.prototype.preset = function(name, structure) {
if (arguments.length === 1) {
return this.presets[name] || name;
}
this.presets[name] = structure;
if (!this.helpers[name]) {
this.helper(name, function() {
return this.app.format(structure, this.file);
});
}
return this;
};
permalinks.parser('custom', options.replacements);
permalinks.parser('path', [
{
pattern: /:basename/,
replacement: function() {
return this.basename;
}
},
{
pattern: /:dirname/,
replacement: function() {
return this.dirname;
}
},
{
pattern: /\b:extname\b/,
replacement: function(pattern) {
return this.extname;
}
},
{
pattern: /\b:ext\b/,
replacement: function(pattern) {
return this.ext;
}
}
]);
/**
* Define permalink helper `name` with the given `fn`. Helpers work like any
* other variable on the context, but they can optionally take any number of
* arguments and can be nested to build up the resulting string.
*
* ```js
* permalinks.helper('date', function(file, format) {
* return moment(file.data.date).format(format);
* });
*
* var structure1 = ':date(file, "YYYY/MM/DD")/:stem/index.html';
* var file1 = permalinks.format(structure1, {
* data: {date: '2017-01-01'},
* path: 'src/about.tmpl'
* });
*
* var structure2 = ':name(upper(stem))/index.html';
* var file2 = permalinks.format(structure2, {
* data: {date: '2017-01-01'},
* path: 'src/about.tmpl'
* });
*
* console.log(file1);
* //=> '2017/01/01/about/index.html'
*
* console.log(file2);
* //=> '2017/01/01/about/index.html'
* ```
*
* @param {String} `name` Helper name
* @param {Function} `fn`
* @return {Object} Returns the Permalink instance for chaining.
* @api public
*/
permalinks.parser('date', require('strings-parser-date')());
permalinks.parser('random', {
pattern: /:random\(([^)]+)\)/,
replacement: function (a, b, c) {
var len, chars;
if(b.match(/,/)) {
len = parseInt(b.split(',')[1], 10);
chars = b.split(',')[0];
return randomatic(chars, len);
Permalinks.prototype.helper = function(name, fn) {
this.helpers[name] = fn;
return this;
};
/**
* Create the context to use when rendering permalinks. In addition to creating
* the data object that is used for resolving `:props`, this method also binds
* a context that is exposed as `this` inside helpers. In particular, the `this`
* object in helpers exposes the Permalinks instance as `this.app`, the file
* being rendered as `this.file`, and the [.format](#format) and [.parse](#parse)
* methods, allowing you to create reusable helper functions that can be published
* and shared without requiring a local instance of Permalinks to be used in
* the library.
*
* ```js
* permalinks.context(file, locals, options);
* ```
* @param {Object} `file`
* @param {Object} `locals`
* @param {Object} `options`
* @return {Object}
*/
Permalinks.prototype.context = function(file, locals, options) {
var fileData = utils.assign({}, file.data, file.data.permalink);
var context = utils.assign({}, this.parse(file), this.data, locals, fileData);
var helpers = utils.assign({}, this.helpers);
var ctx = utils.assign({}, {app: this}, {options: options});
var data = {};
for (var key in context) {
if (context.hasOwnProperty(key)) {
var val = context[key];
if (typeof val === 'function') {
helpers[key] = val;
} else {
var len = b.length;
return randomatic(b, len);
data[key] = val;
}
}
});
}
permalinks.parser('digits', {
pattern: /:(0)+/,
replacement: function (match) {
var matchLen = String(match).length - 1;
return digits.pad(i, {digits: matchLen});
}
});
ctx.app.format = this.format.bind(this);
ctx.app.parse = this.parse.bind(this);
ctx.context = data;
ctx.file = file;
permalinks.parser('num', {
pattern: /:\bnum\b/,
replacement: digits.pad(i, {auto: l})
});
helpers = utils.deepBind(helpers, ctx);
if (typeof helpers.file === 'function') {
helpers.file(file, data, locals);
delete helpers.file;
}
permalinks.parser('prop', {
pattern: /:(\w+)/g,
replacement: function(match, prop) {
return this[prop] || prop;
}
});
data.file = file;
return {
options: options,
helpers: helpers,
data: data
};
};
/**
* Calls [handlebars][] to render the specified template `string` using
* the given `options`.
*
* @param {String} `str`
* @param {Object} `options`
* @return {String} Returns the fully resolved permalink string.
*/
// Presets: pre-formatted permalink propstrings. If a preset is specified
// in the options, append it to the user-defined propstring.
permalinks.propstring('numbered', join((structure || ''), ':num-:basename:ext'));
permalinks.propstring('pretty', join((structure || ''), ':basename/index:ext'));
permalinks.propstring('dayname', join((structure || ''), ':YYYY/:MM/:DD/:basename/index:ext'));
permalinks.propstring('monthname', join((structure || ''), ':YYYY/:MM/:basename/index:ext'));
if(options.preset && String(options.preset).length !== 0) {
// Presets are joined to propstrings, so if a preset is
// specified use the preset the new propstring.
structure = permalinks.propstring(options.preset);
Permalinks.prototype.render = function(str, options) {
if (!this.helpers.helperMissing) {
this.helper('helperMissing', helperMissing);
}
var parsers = Object.keys(permalinks._parsers);
var hbs = utils.handlebars.create();
hbs.registerHelper(options.helpers);
var fn = hbs.compile(convert(str));
return fn(options.data);
};
// Process replacement patterns
return permalinks.process(structure, parsers, context);
/**
* Default helper for managing missing variables
*/
function helperMissing() {
var args = [].slice.call(arguments);
var options = args.pop();
throw new Error(`cannot resolve permalink variable: ":${options.name}"`);
}
/**
* Expose `Permalinks`
*/
module.exports = Permalinks;
{
"name": "permalinks",
"description": "Adds permalink or URL routing/URL rewriting logic to any node.js project. Can be used in static site generators, build systems, web applications or anywhere you need to do path transformation or prop-string replacements.",
"version": "0.3.1",
"version": "1.0.0",
"homepage": "https://github.com/jonschlinkert/permalinks",
"author": {
"name": "Jon Schlinkert",
"url": "https://github.com/jonschlinkert"
},
"repository": {
"type": "git",
"url": "git://github.com/jonschlinkert/permalinks.git"
},
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"repository": "jonschlinkert/permalinks",
"bugs": {
"url": "https://github.com/jonschlinkert/permalinks/issues"
},
"licenses": [
{
"type": "MIT",
"url": "https://github.com/jonschlinkert/permalinks/blob/master/LICENSE-MIT"
}
"license": "MIT",
"files": [
"index.js",
"lib"
],
"main": "index.js",
"engines": {
"node": ">=0.8"
},
"scripts": {
"test": "mocha"
},
"dependencies": {
"assign-deep": "^0.4.5",
"deep-bind": "^0.3.0",
"get-value": "^2.0.6",
"handlebars": "^4.0.6",
"isobject": "^3.0.0",
"lazy-cache": "^2.0.2",
"parse-filepath": "^1.0.1",
"vinyl": "^2.0.1"
},
"devDependencies": {
"gulp-format-md": "^0.1.11",
"mocha": "^3.2.0",
"moment": "^2.17.1",
"randomatic": "^1.1.6"
},
"keywords": [

@@ -38,28 +54,26 @@ "blog entry",

"uri",
"url",
"URL rewriting",
"url",
"url-routing"
],
"main": "index.js",
"engines": {
"node": ">=0.8"
},
"scripts": {
"test": "mocha -R spec"
},
"devDependencies": {
"verb": "~0.2.0",
"chai": "~1.9.1",
"mocha": "~1.18.2"
},
"dependencies": {
"digits": "~0.1.4",
"lodash": "~2.4.1",
"parse-filepath": "^0.2.1",
"randomatic": "^0.1.4",
"strings": "^0.3.3",
"strings-parser-date": "^0.1.0",
"strings-parser-url": "^0.1.0",
"underscore.string": "~2.3.3"
"verb": {
"toc": false,
"layout": "default",
"tasks": [
"readme"
],
"plugins": [
"gulp-format-md"
],
"lint": {
"reflinks": true
},
"related": {
"list": []
},
"reflinks": [
"parse-filepath",
"vinyl"
]
}
}
}

@@ -1,41 +0,17 @@

# permalinks [![NPM version](https://badge.fury.io/js/permalinks.png)](http://badge.fury.io/js/permalinks)
# permalinks [![NPM version](https://img.shields.io/npm/v/permalinks.svg?style=flat)](https://www.npmjs.com/package/permalinks) [![NPM monthly downloads](https://img.shields.io/npm/dm/permalinks.svg?style=flat)](https://npmjs.org/package/permalinks) [![NPM total downloads](https://img.shields.io/npm/dt/permalinks.svg?style=flat)](https://npmjs.org/package/permalinks) [![Linux Build Status](https://img.shields.io/travis/jonschlinkert/permalinks.svg?style=flat&label=Travis)](https://travis-ci.org/jonschlinkert/permalinks)
> Adds permalink or URL routing/URL rewriting logic to any node.js project. Can be used in static site generators, build systems, web applications or anywhere you need to do path transformation or prop-string replacements.
For an implementation example, see Assemble's [permalinks middleware](https://github.com/assemble/assemble-middleware-permalinks).
## Install
## Table of Contents
Install with [npm](https://www.npmjs.com/):
<!-- toc -->
* [Install](#install)
* [Usage](#usage)
* [Docs](#docs)
* [How it works](#how-it-works)
* [Special Patterns](#special-patterns)
* [Custom replacement patterns](#custom-replacement-patterns)
* [Date patterns](#date-patterns)
* [Options](#options)
* [structure](#structure)
* [preset](#preset)
* [Usage Examples](#usage-examples)
* [Pretty URLs](#pretty-urls)
* [Using presets](#using-presets)
* [Dest extension](#dest-extension)
* [Path separators](#path-separators)
* [More examples](#more-examples)
* [Contributing](#contributing)
* [Authors](#authors)
* [License](#license)
<!-- toc stop -->
## Install
Install with [npm](npmjs.org):
```bash
npm i permalinks --save
```sh
$ npm install --save permalinks
```
## Usage
You can add permalinks to any JavaScript project using node's `require()` system with the following line of code:
```js

@@ -45,382 +21,297 @@ var permalinks = require('permalinks');

At minimum, you must pass a structure and a context:
To create a permalink, pass a `structure` (strong) with placeholders (like `:prop`) to replace, and the `file` with path information to use:
```js
permalinks( structure, context, options );
```
var structure = ':category/:name/index.html';
var file = {path: 'src/about.hbs'};
var locals = {category: 'foo'};
For example:
```js
var context = {first: "brian", last: "woodward"};
permalinks('people/:last/:first/index.html', context);
// results in:
// => 'people/woodward/brian/index.html'
console.log(permalinks(structure, file, locals));
//=> 'foo/about/index.html'
```
## Docs
<details>
<summary><strong>Constructor usage</strong></summary>
If you need to [register helpers](#helpers) or use any of the `Permalinks` methods,
main export can alternatively be used as a constructor function:
### How it works
Pass a **structure** and a **context**:
```js
var structure = ':a/:b/:c';
var context = {
a: 'foo',
b: 'bar',
c: 'baz'
};
permalinks(structure, context)
//=> foo/bar/baz
```
var Permalinks = require('permalinks');
A more dynamic example would be parsing filepaths using the [node.js path module](http://nodejs.org/api/path.html), passing an object with the parsed values as context:
var options = {};
var permalinks = new Permalinks(options);
```js
// a "source" filepath
var src = 'src/content/interesting-post.md';
var context = {
ext: path.extname(src),
basename: path.basename(src, path.extname(src)),
dirname: path.dirname(src)
};
var structure = 'blog/posts/:YYYY/:MM/:basename.html';
// the resulting ("destination") filepath
var dest = permalinks(structure, context);
// => blog/posts/2014/05/interesting-post.html
var file = new File({path: 'src/about.hbs'});
console.log(permalinks.format(':stem/index.html', file));
//=> 'about/index.html'
```
</details>
### Special Patterns
> A few special replacement patterns were created for this lib.
## Context
#### `:num`
The "context" is an in-memory object that is used for resolving the placeholders in permalink [structures](#structures).
Automatically adds sequential, "padded" numbers, using the provided `length` to determine the amount of padding.
The context object is created by merging the following objects:
For example, given you have `permalinks({structure: ':num-:basename', context: context, length: foo})`
* **file** - all of the [path properties](#file-path-properties) supported by [vinyl](https://github.com/gulpjs/vinyl), and any other user-defined properties defined on the given `file` (if a file is supplied as a string, it will converted to a vinyl file object and used as the `file.path`)
* **file.data** - the `file.data` object, if it exists. This object is often used for caching parsed yaml front-matter.
* **file.data.permalink** - This property can be a string, for defining the permalink to structure to use for a file, or an object with optional `structure` and/or `options` properties, for defining permalink options and data to be used for a specific file.
* **locals** - passed to the [format](#format) method (or the main export if you're not creating an instance of Permalinks)
* **global data** - passed on `options.data` to the contstructor
* if `foo` is 1-9, the result would be `1-foo.html`, `2-bar.html`, `3-baz.html` and so on.
* if `foo` is 1,000 the result would be `0001-foo.html`, `0002-bar.html`, `0003-baz.html`, ... `1000-quux.html`.
### File path properties
#### `:000`
Adds sequential digits. Similar to `:num`, but the number of digits is determined by the number of zeros defined.
As long as a file is provided with at least a `file.path` property, most of the following built-in `file` variables will be automatically available on the context.
Example:
| **variable** | **description** |
| --- | --- |
| `file.cwd` | Gets and sets current working directory. Will always be normalized and have trailing separators removed. Throws when set to any value other than non-empty strings. |
| `file.base` | Gets and sets base directory. Used for created relative paths. When `null` or `undefined`, it simply proxies the `file.cwd` property. Will always be normalized and have trailing separators removed. Throws when set to any value other than non-empty strings or `null`/`undefined`. |
| `file.path` | Gets and sets the absolute pathname string or `undefined`. This value is always normalized and trailing separators are removed. Throws when set to any value other than a string. |
| `file.relative` | Gets the result of `path.relative(file.base, file.path)`. This is a getter and will throws if set or when `file.path` is not set. |
| `file.dirname` | Gets and sets the dirname of `file.path`. Will always be normalized and have trailing separators removed. Throws when `file.dirname` is not exlicitly defined and/or `file.path` is not set. |
| `file.basename` | Gets and sets the basename of `file.path`. Throws when `file.basename` is not exlicitly defined and/or `file.path` is not set. |
| `file.stem` | Gets and sets stem (filename without suffix) of `file.path`. Throws when `file.stem` is not exlicitly defined and/or `file.path` is not set. |
| `file.name` | Alias for `file.stem` |
| `file.extname` | Gets and sets extname of `file.path`. |
* `:00` will result in two-digit numbers
* `:000` will result in three-digit numbers
* `:00000000` will result in eight-digit numbers, and so on...
### Custom data
Any of the built-in `file` variable can be overridden by setting the properties directly.
## Helpers
#### `:random( pattern, number )`
Helpers can be used to resolve placeholders in permalink structures.
Adds randomized characters based on the pattern provided in the parentheses. `pattern` defines the pattern you wish to use, and `number` is an optional parameter to define the number of characters to generate.
### file helper
For example, `:random(A, 4)` (whitespace insenstive) would result in randomized 4-digit uppercase letters, like, `ZAKH`, `UJSL`... and so on.
A special built-in `file` helper is called on every file and then removed from the context before rendering.
**no second parameter**
If a second parameter is not provided, then the `length()` of the characters used in the first parameter will be used to determine the number of digits to output. For example:
* `:random(AAAA)` is equivelant to `:random(A, 4)`
* `:random(AAA0)` and `:random(AA00)` and `:random(A0A0)` are equivelant to `:random(A0, 4)`
**valid characters (and examples)**
* `:random(aa)`: results in double-digit, randomized, lower-case letters (`abcdefghijklmnopqrstuvwxyz`)
* `:random(AAA)`: results in triple-digit, randomized, upper-case letters (`ABCDEFGHIJKLMNOPQRSTUVWXYZ`)
* `:random(0, 6)`: results in six-digit, randomized nubmers (`0123456789`)
* `:random(!, 5)`: results in single-digit randomized, _valid_ non-letter characters (`~!@#$%^&()_+-={}[];\',.`)
* `:random(A!a0, 9)`: results in nine-digit, randomized characters (any of the above)
_The order in which the characters are provided has no impact on the outcome._
### Custom replacement patterns
Adding patterns is easy, just add a `replacements: []` property to the `permalinks` option, then add any number of patterns to the array. For example, let's say we want to add the `:project` variable to our permalinks:
```js
var options = {
structure: ':year/:month/:day/:project/:slug:ext',
replacements: [
// replacement patterns here!
]
};
...
permalinks.helper('file', function(file, data, locals) {
// do stuff with file, data and locals
});
```
Since `:project` is not a built-in variable, we need to add a replacement pattern so that any permalinks that include this variable will actually work:
This is useful for modifying the context or setting properties on files before generating permalinks.
<details>
<summary><strong>Example</strong></summary>
```js
var options = {
structure: ':year/:month/:day/:project/:slug:ext',
replacements: [
{
pattern: ':project',
replacement: require('./package.json').name
}
]
};
```
var file = new File({path: 'foo/bar/baz.hbs'});
var permalinks = new Permalinks();
var count = 0;
If you have some replacement patterns you'd like to implement, if you think they're common enough that they should be built into this plugin, please submit a pull request.
permalinks.helper('file', function(file, data, locals) {
data.num = ++count;
});
#### with custom properties
console.log(permalinks.format(':num-:basename', file));
//=> '1-baz.hbs'
console.log(permalinks.format(':num-:basename', file));
//=> '2-baz.hbs'
console.log(permalinks.format(':num-:basename', file));
//=> '3-baz.hbs'
console.log(count);
//=> 3
```
</details>
Any string pattern is acceptable, as long as a `:` precedes the variable, but don't forget that there must also be a matching property in the context!
<details>
<summary><strong>SEO Recommendations</strong></summary>
### Date patterns
> This plugin uses the incredibly feature rich and flexible [moment.js](http://momentjs.com/) for parsing dates. Please consult the [moment.js documentation](http://momentjs.com/docs/) for usage information and for the full list of available options.
Permalinks are important for SEO, but it's a good idea to spend some time thinking about the strategy you want to use before you decide on a URL structure.
For the date variables to work, a `date` property must exist on the page object.
### Use semantic relevance
```yaml
---
date: 2014-01-29 3:45 PM
---
```
The most important aspect of a URL is that it makes semantic sense to humans. The more interesting the URL is to humans, the more interesting it will be to search engines.
Or
The following are all good permalink structures, in order of [my own](https://github.com/jonschlinkert) personal preference. Pick the one that makes the most sense for your site:
```js
pages: [
{
data: {
title: 'All about permalinks, the novel.',
description: 'This rivoting sequel to War & Peace will have you sleeping in no time.'
date: '2014-01-29 3:45 PM'
},
content: ""
}
]
```
* `/:postname` (a semantic, descriptive, catchy post name is best permalink structure whenever possible)
* `/:category/:postname/`
* `/:author/:postname` (popular with [medium](https://medium.com)-style blogging platforms)
* `/:category/:author/:postname`
#### Common date patterns
It's not unusualy for big sites to use different structures for different parts of the site (blog, products, etc).
* `:year`: The year of the date, four digits, for example `2014`
* `:month`: Month of the year, for example `01`
* `:day`: Day of the month, for example `13`
* `:hour`: Hour of the day, for example `24`
* `:minute`: Minute of the hour, for example `01`
* `:second`: Second of the minute, for example `59`
### Avoid date-based permalinks
For the following examples, let's assume we have a date in the YAML front matter of a page formatted like this:
Contrary to what might seem like an idiomatic pattern, based on the widespread adoption of using dates to categorize blog posts, dates tend to, well... _date_ your content.
```yaml
---
date: 2014-01-29 3:45 PM
---
```
(_note that this property doesn't have to be in YAML front matter, it just needs to be in the `page.data` object, so this works fine with `options.pages` collections as well._)
Date-based URL's tend to _decrease click through rates_ on older articles. Think about it, who prefers reading out of date content? Try to use a URL strategy that doesn't go out of its way to emphasize the date, and you'll keep your posts feeling like fresh content.
#### Full date
* `:date`: Eqivelant to the full date: `YYYY-MM-DD`. Example: `2014-01-29`
There are plenty of valid use cases for using date-based URL's, like for categorizing movies, albums, breaking news, and so on. But in general, if you're writing about topics that aren't more relevant to users _specifically because of the date of the material_, it's recommend that you avoid using a date-based permalink structure for your blog or documentation, because there is a good chance it will do more harm than good over the long term.
#### Year
* `:YYYY`: The full year of the date. Example: `2014`
* `:YY`: The two-digit year of the date. Example: `14`
* `:year`: alias for `YYYY`
### Numeric permalinks
#### Month name
* `:MMMM`: The full name of the month. Example `January`.
* `:MMM`: The name of the month. Example: `Jan`
* `:monthname`: alias for `MMMM`
Numeric or `:id` based permalinks are better than date-based, but they don't really offer much usability or SEO benefit.
#### Month number
* `:MM`: The double-digit number of the month. Example: `01`
* `:M`: The single-digit number of the month. Example: `1`
* `:month`: alias for `MM`
* `:mo`: alias for `M`
## Summary
#### Day of the month
* `:day`: alias for `DD`
* `:DD`: The double-digit day of the month. Example: `29`
* `:D`: The double-digit day of the month. Example: `29`
The best URL structure is one that:
#### Day of the week
* `:dddd`: Day of the week. Example: `monday`
* `:ddd`: Day of the week. Example: `mon`
* `:dd`: Day of the week. Example: `Mo`
* `:d`: Day of the week. Example: `2`
* provides the _highest degree of semantic relevance_ to the content, and
* is _useful to both search engines and humans_
#### Hour
* `:HH`: The double-digit time of day on a 24 hour clock. Example `15`
* `:H`: The single-digit time of day on a 24 hour clock. Example `3`
* `:hh`: The double-digit time of day on a 12 hour clock. Example `03`
* `:h`: The single-digit time of day on a 12 hour clock. Example `3`
* `:hour`: alias for `HH`
Happy blogging!
</details>
#### Minute
* `:mm`: Minutes. Example: `45`.
* `:m`: Minutes. Example: `45`.
* `:min`: Alias for `mm`|`m`.
* `:minute`: Alias for `mm`|`m`.
## API
#### Second
* `:ss`: Seconds. Example: `09`.
* `:s`: Seconds. Example: `9`.
* `:sec`: Alias for `ss`|`s`.
* `:second`: Alias for `ss`|`s`.
### [Permalinks](index.js#L18)
## Options
### structure
Type: `String`
Create an instance of `Permalinks` with the given `options`
Default: `undefined`
**Params**
The permalink pattern to use for building paths and generated files.
* `options` **{Options|String}**
### preset
Type: `String`
**Example**
Default: `undefined`
```js
var permalinks = new Permalinks();
console.log(permalinks.format(':stem/index.html'), {path: 'src/about.hbs'});
//=> 'about/index.html'
```
The following presets are currently available:
### [.parse](index.js#L64)
* `numbered`: expands to `:num-:basename:ext`
* `pretty`: expands to `:basename/index:html`
* `dayname`: expands to `:YYYY/:MM/:DD/:basename/index:ext`
* `monthname`: expands to `:YYYY/:MM/:basename/index:ext`
Uses [parse-filepath](https://github.com/jonschlinkert/parse-filepath) to parse the `file.path` on the given file object. This method is called by the [format](#format) method, but you can use it directly and pass the results as `locals` (the last argument) to the `.format` method if you need to override or modify any path segments.
**Params**
#### how presets work
* `file` **{Object}**
* `returns` **{Object}**
In a nutshell, a preset is simply a pre-defined permalink `structure`, so instead of having to type out `:foo/:bar/:baz/basename:html`, you can just use `pretty`. Presets expand into permalink structures following this pattern:
**Example**
```js
preset
//=> :bar/index:html
console.log(permalinks.parse({path: 'foo/bar/baz.md'}));
// { root: '',
// dir: 'foo/bar',
// base: 'baz.md',
// ext: '.md',
// name: 'baz',
// extname: '.md',
// basename: 'baz.md',
// dirname: 'foo/bar',
// stem: 'baz',
// path: 'foo/bar/baz.md',
// absolute: [Getter/Setter],
// isAbsolute: [Getter/Setter] }
```
Additionally, if a `structure` is also defined, the `preset` will be appended to it.
### [.format](index.js#L94)
```js
structure + preset
//=> :foo + :bar/index:html
```
Generate a permalink by replacing `:prop` placeholders in the specified `structure` with data from the given `file` and `locals`.
_If you would like to see another preset, [please submit an issue](https://github.com/jonschlinkert/permalinks/issues/new)._
**Params**
## Usage Examples
### Pretty URLs
* `structure` **{String}**: Permalink structure or the name of a registered [preset](#preset).
* `file` **{Object|String}**: File object or file path string.
* `locals` **{Object}**: Any additional data to use for resolving placeholders.
* `returns` **{String}**
Pretty links involve saving an `index.html` to each directory, with the tile, file name, slug, or some other variable as the `:basename` of the directory. For example:
**Example**
```js
var url = fs.readdirSync('./').map(function(filepath) {
var ext = path.extname(filepath);
var basename = path.basename(filepath, ext);
return permalinks(':basename/index.:ext', {
basename: basename,
ext: ext
});
});
var fp = permalinks.format('blog/:stem/index.html', {path: 'src/about.hbs'});
console.log(fp);
//=> 'blog/about/index.html'
```
results in something like:
### [.preset](index.js#L130)
```js
['my-node-js-post/index.html', ...]
```
Define a permalink `preset` with the given `name` and `structure`.
### Using presets
**Params**
Presets allow you to achieve certain permalinks structures without having to explicitly define each URL segment. For example, in the previous example we created pretty URLs., Here is how we would do the same with `presets`:
* `name` **{String}**: If only the name is passed,
* `structure` **{String}**
* `returns` **{Object}**: Returns the `Permalinks` instance for chaining
**Example**
```js
var options = {
preset: 'pretty',
context: {
basename: basename,
ext: ext
}
};
permalinks(options);
permalinks.preset('blog', 'blog/:stem/index.html');
var url = permalinks.format('blog', {path: 'src/about.hbs'});
console.log(url);
//=> 'blog/about/index.html'
```
The above example won't necessarily save a whole lot of time, but it's a nice way of ensuring that you're getting pretty links with whatever permalinks structure you define. To some, this might be particularly useful when "stacked" with more complex permalink structures, e.g.:
### [.helper](index.js#L178)
```js
var options = {
preset: 'pretty',
structure: ':archives/:categories'
};
```
Define permalink helper `name` with the given `fn`. Helpers work like any other variable on the context, but they can optionally take any number of arguments and can be nested to build up the resulting string.
which expands to: `:archives/:categories/:basename:/index:ext`, and would result in:
**Params**
```js
archives/categories/foo/index.html
```
* `name` **{String}**: Helper name
* `fn` **{Function}**
* `returns` **{Object}**: Returns the Permalink instance for chaining.
### Dest extension
**Example**
In most cases your generated HTML will have the `.html` extension, then using `:index.html` is probably fine. But if you happen to switch back and forthing between projects that alternate between `.htm` and `.html`, you can use `:index:ext` instead.
```js
permalinks.helper('date', function(file, format) {
return moment(file.data.date).format(format);
});
var structure1 = ':date(file, "YYYY/MM/DD")/:stem/index.html';
var file1 = permalinks.format(structure1, {
data: {date: '2017-01-01'},
path: 'src/about.tmpl'
});
### Path separators
var structure2 = ':name(upper(stem))/index.html';
var file2 = permalinks.format(structure2, {
data: {date: '2017-01-01'},
path: 'src/about.tmpl'
});
You don't have to use slashes (`/`) only in your permalinks, you can use `-` or `_` wherever you need them as well. For example, this is perfectly valid:
console.log(file1);
//=> '2017/01/01/about/index.html'
console.log(file2);
//=> '2017/01/01/about/index.html'
```
:YYYY_:MM-:DD/:slug:category:foo/:bar/index.html
```
**Warning**, this should be obvious, but make sure not to use a `.` in the middle of your paths, especially if you use Windows.
## About
## More examples
### Contributing
Keep in mind that the date is formatted the way you want it, you don't need to follow these examples. Also, some of these variables will only work if you add that property to your pages, and setup the replacement patterns.
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
```js
':YYYY/:MM/:DD/news/:id/index:ext'
//=> dest + '/2014/01/01/news/001/index.html'
### Building docs
':YYYY/:MM/:DD/:mm/:ss/news/:id/index:ext'
//=> dest + '/2014/01/01/40/16/news/001/index.html'
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
':year/:month/:day/:basename:ext'
//=> dest + '/2014/01/01/my-post.html'
To generate the readme, run the following command:
'blog/:year-:month-:day/:basename:ext'
//=> dest + 'blog/2014-01-01/my-post.html'
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
':date/:basename:ext'
//=> dest + '2014-01-01/my-post.html'
### Running tests
':year/:month/:day/:category/index.html'
//=> dest + '/2014/01/01/javascript/index.html'
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
':year/:month/:day/:slug/index.html'
//=> dest + '/2014/01/01/business-finance/index.html'
```sh
$ npm install && npm test
```
## Contributing
Find a bug? Have a feature request? Please [create an Issue](https://github.com/jonschlinkert/permalinks/issues).
### Author
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality,
and run `docs` in the command line to build the docs with [Verb](https://github.com/assemble/verb).
Pull requests are also encouraged, and if you find this project useful please consider "starring" it to show your support! Thanks!
## Authors
**Jon Schlinkert**
+ [github/jonschlinkert](https://github.com/jonschlinkert)
+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert)
* [github/jonschlinkert](https://github.com/jonschlinkert)
* [twitter/jonschlinkert](https://twitter.com/jonschlinkert)
**Brian Woodward**
### License
+ [github/doowb](https://github.com/doowb)
+ [twitter/doowb](http://twitter.com/jonschlinkert)
Copyright © 2017, [Jon Schlinkert](https://github.com/jonschlinkert).
MIT
## License
Copyright (c) 2014 Jon Schlinkert, contributors.
Released under the MIT license
***
_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on May 04, 2014._
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.4.2, on February 14, 2017._
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc