Permalink
Browse files

Refactor, update readme, add screenshot

  • Loading branch information...
1 parent 76792ab commit 07b3bf0ceb219a9e8f85416b0a6fcb9e54bb24fc @mixu committed Oct 23, 2014
View
@@ -0,0 +1 @@
+screenshot.png
View
@@ -0,0 +1,12 @@
+GJSLINT := --nojsdoc --exclude_directories=node_modules,lib/require,test,temp --max_line_length=120 --disable=200,201,202,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,230,231,232,233,250,251,252
+
+build:
+ rm -rf ./tmp/
+ node ./bin/ghost-render --settings settings.json
+
+lint:
+ fixjsstyle $(GJSLINT) -r .
+ gjslint $(GJSLINT) -r .
+ jshint .
+
+.PHONY: lint build
View
@@ -15,7 +15,12 @@ if (argv.init) {
return;
}
-if (argv.help) {
+if (argv.version || argv.v) {
+ console.log(require('../package.json').version);
+ process.exit();
+}
+
+if (argv.help || argv.h) {
console.log(fs.readFileSync(__dirname + '/usage.txt').toString());
process.exit();
return;
@@ -27,7 +32,9 @@ if (argv.settings) {
// resolve relative to the config file location
['input', 'output', 'theme'].forEach(function(key) {
- loaded[key] = path.resolve(path.dirname(settingsPath), loaded[key]);
+ if (typeof loaded[key] !== 'undefined') {
+ loaded[key] = path.resolve(path.dirname(settingsPath), loaded[key]);
+ }
});
Object.keys(loaded).forEach(function(key) {
@@ -37,6 +44,8 @@ if (argv.settings) {
}
});
} else {
+ console.log(fs.readFileSync(__dirname + '/usage.txt').toString());
+ console.log();
console.log('The --settings command line argument is required.');
process.exit();
return;
@@ -48,7 +57,13 @@ if (!config.input) {
if (!config.output) {
config.output = path.resolve(process.cwd(), argv.output || './output/');
}
+if (!config.theme && argv.theme) {
+ config.theme = path.resolve(process.cwd(), argv.theme);
+}
+
if (!config.theme) {
+ console.log(fs.readFileSync(__dirname + '/usage.txt').toString());
+ console.log();
console.log('The --theme command line argument is required.');
return;
}
@@ -75,109 +90,124 @@ var meta = { blog: config.blog, authors: config.authors },
var pipelines = require('../index.js').pipelines(theme, config.input, config.output);
+function copy(targetFn) {
+ var seen = {};
+ return through.obj(function(filename, enc, done) {
+ var target = targetFn(filename),
+ copyDir = path.dirname(target);
+ (seen[copyDir] ? function(a, onDone) { onDone(null); } : mkdirp)(
+ copyDir, function(err) {
+ if (err) {
+ throw err;
+ }
+ seen[copyDir] = true;
+ fs.createReadStream(filename)
+ .pipe(fs.createWriteStream(target))
+ .once('finish', done)
+ .once('error', done);
+ });
+ });
+}
+
glob.stream(config.input + '/**')
- .pipe(pi.pipeFirst([
+ .pipe(pi.head([
// fixme: duplicated in stream.read
pi.filter(function(filename) {
var stat = fs.statSync(filename);
return stat.isFile();
}),
- // for non-markdown files: copy the file.
- through.obj(function(filename, enc, onDone) {
- var ext = path.extname(filename);
- if (ext === '.markdown' ||
- ext === '.mdown' ||
- ext === '.mkd' ||
- ext === '.mkdn' ||
- ext === '.md') {
- this.push(filename);
- return onDone();
- }
- var target = path.normalize(filename.replace(config.input, config.output));
- mkdirp(path.dirname(target), function(err) {
- if (err) {
- throw err;
+ pi.match(
+ // for non-markdown files: copy the file.
+ function(filename) {
+ var ext = path.extname(filename);
+ if (ext === '.markdown' ||
+ ext === '.mdown' ||
+ ext === '.mkd' ||
+ ext === '.mkdn' ||
+ ext === '.md') {
+ return false;
}
+ return true;
+ },
+ copy(function(filename) {
+ var target = path.normalize(filename.replace(config.input, config.output));
console.log('Copy non-markdown file', filename, '=>', target);
- fs.createReadStream(filename).pipe(fs.createWriteStream(target));
- return onDone();
- });
- }),
-
- stream.read(),
- md.parseHeader(),
- md.parseMd(),
- md.annotateMdHeadings(),
-
- // add helper + template that uses the headings
-
- md.highlightJs(),
- md.convertMd(),
-
- // this would make a good example of why pipeFirst is not always what you want when you
- // are going from doing one operation to doing two operations, since it
- // would return the first item, which would leave a hanging pipe for the latter item
- // ... what you'd want is to pipe into the first item but pipe out of the last item.
- // Maybe pipe-iterators.pipe should do the right thing given
- // .pipe([ foo, [ first, ..., last], bar)
-
- // make sure that the path extension is adjusted to html before anything else happens
-
- // This creates '/foo.html' style links and files
- // pi.mapKey('path', function(p) { return path.dirname(p) + '/' + path.basename(p, path.extname(p)) + '.html'; }),
- //
- // TODO: document this and make it configurable.
- //
- // This leaves the '.html' part off from links. When the filename generation in the posts pipeline
- // detects no extname, it will append '/index.html', resulting in '/foo/index.html' style output
- // which will match the correct output on servers which support a index page.
- pi.mapKey('path', function(p) { return path.dirname(p) + '/' + path.basename(p, path.extname(p)); }),
-
- stream.fileToPost(meta, config.input),
- stream.parsePublishedAt(),
- stream.parseTags(),
- stream.sortByPublishedAt(),
-
- pi.fork(pipelines.page),
-
- // no drafts, no pages past this point
- pi.filter(function(post) { return !post.draft && !post.page; }),
-
- pi.fork(
- pipelines.post,
- pipelines.index,
- pipelines.postRSS,
-
- pi.pipeFirst([
- stream.groupByTag(),
- pi.fork(
- pipelines.tag,
- pipelines.tagRSS
- )
- ]),
- pi.pipeFirst([
- stream.groupByAuthor(),
- pi.fork(
- pipelines.author,
- pipelines.authorRSS
+ return target;
+ }),
+ // rest:
+ pi.head([
+ stream.read(),
+ md.parseHeader(),
+ md.parseMd(),
+ md.annotateMdHeadings(),
+
+ // add helper + template that uses the headings
+
+ md.highlightJs(),
+ md.convertMd(),
+
+ stream.fileToPost(meta),
+
+ // generate the URL for the post.
+ // This determines:
+ // - the link URLs
+ // - the path on disk (outDir + relativeUrl)
+ //
+ // To prevent conficts between ghost's own "permalink" logic and
+ // the real directory structure on disk, this is the only place where
+ // urls are generated for real for posts and pages.
+ //
+ // As for author pages, tag pages, the blog index pages and rss XML pages,
+ // these have a fixed, non-configurable URL scheme (in Ghost and here).
+ stream.computeUrl(config.input),
+
+ stream.parsePublishedAt(),
+ stream.parseTags(),
+ stream.sortByPublishedAt(),
+
+ pi.match(
+ // pages pipeline: only pages
+ function(post) { return post.page; }, pipelines.page,
+ pi.head([
+ // no drafts, no pages past this point
+ pi.filter(function(post) { return !post.draft && !post.page; }),
+
+ pi.fork(
+ pipelines.post,
+ pipelines.index,
+ pipelines.postRSS,
+ pipelines.tags,
+ pipelines.authors
+ )
+ ])
)
])
)
]));
// copy assets
glob.stream(assetPath + '/**' )
- .pipe(stream.read())
- .pipe(pi.mapKey('path', function(p) { return path.normalize(p.replace(assetPath, config.output + '/assets/')); }))
- .pipe(pi.forEach(function(obj) { console.log('write theme asset:', obj.path); }))
- .pipe(stream.dest());
+ .pipe(pi.filter(function(filename) {
+ var stat = fs.statSync(filename);
+ return stat.isFile();
+ }))
+ .pipe(copy(function(filename) {
+ var target = path.normalize(filename.replace(assetPath, config.output + '/assets/'));
+ console.log('Copy theme asset:', filename, '=>', target);
+ return target;
+ }));
var ghostAssetPath = path.normalize(__dirname + '/../public/assets');
glob.stream(ghostAssetPath + '/**' )
- .pipe(stream.read())
- .pipe(pi.mapKey('path', function(p) { return path.normalize(p.replace(ghostAssetPath + '/', config.output + '/assets/')); }))
- .pipe(pi.forEach(function(obj) { console.log('write base asset:', obj.path); }))
- .pipe(stream.dest());
+ .pipe(pi.filter(function(filename) {
+ var stat = fs.statSync(filename);
+ return stat.isFile();
+ }))
+ .pipe(copy(function(filename) {
+ var target = path.normalize(filename.replace(ghostAssetPath + '/', config.output + '/assets/'));
+ console.log('Copy base asset:', filename, '=>', target);
+ return target;
+ }));
View
@@ -1 +1,25 @@
-TODO
+USAGE
+
+ ghost-render --settings ./settings.json --theme ./path/to/theme
+
+REQUIRED
+
+ --settings <path> Path to blog settings file.
+
+ --theme <path> Path to a Ghost theme.
+
+OPTIONAL
+
+ --init Generate a settings file.
+ For example: `ghost-render --init > settings.json`
+
+ --input <path> Path to use for generating the blog.
+ All files under the path are included.
+ Defaults to `./input`
+
+ --output <path> Path to use for writing the blog.
+ Defaults to `./output`
+
+ --help, -h Show this help.
+
+ --version, -v Version info.
View
@@ -24,8 +24,6 @@ module.exports = function getConfig(meta) {
description: meta.blog.description
},
- permalinks: '/:slug/',
-
fileStorage: false,
apps: false
};
@@ -72,9 +70,16 @@ module.exports = function getConfig(meta) {
if (_.isObject(context) && context.relativeUrl) {
urlPath = context.relativeUrl;
} else if (_.isString(context) && _.indexOf(knownObjects, context) !== -1) {
+
+ if (context === 'post' && (!data.post || !data.post.preComputedRelativeUrl)) {
+ console.error('Post object is missing the preComputedRelativeUrl property! This is expected in ' +
+ 'ghost-render, because we calculate blog urls in advance.');
+ throw new Error('Post object is missing the preComputedRelativeUrl property!');
+ }
+
// trying to create a url for an object
- if (context === 'post' && data.post && data.permalinks) {
- urlPath = urlPathForPost(data.post, data.permalinks);
+ if (context === 'post' && data.post) {
+ urlPath = data.post.preComputedRelativeUrl;
secure = data.post.secure;
} else if (context === 'tag' && data.tag) {
urlPath = '/tag/' + data.tag.slug + '/';
@@ -126,61 +131,16 @@ module.exports = function getConfig(meta) {
return output;
}
- // ## urlPathForPost
- // Always sync
- // Creates the url path for a post, given a post and a permalink
- // Parameters:
- // - post - a json object representing a post
- // - permalinks - a json object containing the permalinks setting
- function urlPathForPost(post, permalinks) {
- if (post.relativeUrl) {
- return post.relativeUrl;
- } else {
- console.error('Post object is missing the relativeUrl property! This is expected in ' +
- 'ghost-render, because we calculate blog urls in advance.');
- throw new Error('Post object is missing the relativeUrl property!');
- }
-
- var output = '',
- tags = {
- year: function() { return moment(post.published_at).format('YYYY'); },
- month: function() { return moment(post.published_at).format('MM'); },
- day: function() { return moment(post.published_at).format('DD'); },
- slug: function() { return post.slug; },
- id: function() { return post.id; }
- };
-
- if (post.page) {
- output += '/:slug/';
- } else {
- output += permalinks.value;
- }
-
- // replace tags like :slug or :year with actual values
- output = output.replace(/(:[a-z]+)/g, function(match) {
- if (_.has(tags, match.substr(1))) {
- return tags[match.substr(1)]();
- }
- });
-
- return output;
- }
-
// ## urlForPost
- // This method is async as we have to fetch the permalinks
- // Get the permalink setting and then get a URL for the given post
+ // Get a URL for the given post
// Parameters
// - settings - passed reference to api.settings
// - post - a json object representing a post
// - absolute (optional, default:false) - boolean whether or not the url should be absolute
config.urlForPost = function urlForPost(settings, post, absolute) {
- //return settings.read('permalinks').then(function (response) {
- // var permalinks = response.settings[0];
- var permalinks = { key: 'permalinks', value: '/:year/:month/:day/:slug.html' }; // default
-
- return config.urlFor('post', {post: post, permalinks: permalinks}, absolute);
- //});
+ var result = config.urlFor('post', { post: post }, absolute);
+ return result;
};
return config;
Oops, something went wrong.

0 comments on commit 07b3bf0

Please sign in to comment.