Updated scripts to produce also a gemini capsule.

This commit is contained in:
David Soulayrol 2022-11-06 15:30:58 +01:00
parent 9420adb44a
commit 578cad3351
7 changed files with 229 additions and 34 deletions

View file

@ -0,0 +1,10 @@
{% extends "gemini.njk" %}{% block content %}# {{ book.title }}
{{ contents | safe }}
({{ comment.size }} signes. Première publication {% if comment.issue %}sur {{ comment.medium | safe }} n°{{ comment.issue }}){% else %}le {{ comment.date.format('LL')}} sur {{ comment.medium | safe }}){% endif %}
{% if comment.source %}=> {{ comment.source }} Document original{% endif %}
=> {{ filepath(comment.medium, ['/chroniques']) }} Toutes les chroniques sur {{ comment.medium | safe }}
=> /chroniques Toutes les chroniques
{% endblock %}

1
layouts/gemini.njk Normal file
View file

@ -0,0 +1 @@
{% block content %}{{ contents | safe }}{% endblock %}

View file

@ -5,11 +5,11 @@
"description": "The sources for <http://david.soulayrol.name>", "description": "The sources for <http://david.soulayrol.name>",
"main": "", "main": "",
"scripts": { "scripts": {
"build": "DEBUG=metalsmith* npm run clean && npm run build:metalsmith", "build": "npm run clean && npm run build:metalsmith",
"build:prod": "NODE_ENV=production npm run build", "build:prod": "NODE_ENV=production npm run build",
"build:metalsmith": "node scripts/run.js build", "build:metalsmith": "node scripts/run.js build",
"clean": "rimraf dist", "clean": "rimraf dist",
"dev": "npm run build && DEBUG=metalsmith* nodemon scripts/run.js serve", "dev": "npm run build && nodemon scripts/run.js serve",
"server": "npm run build && http-server dist", "server": "npm run build && http-server dist",
"lint": "npm run lint:js && npm run lint:css", "lint": "npm run lint:js && npm run lint:css",
"lint:js": "eslint scripts", "lint:js": "eslint scripts",
@ -45,7 +45,7 @@
"moment": "^2.29.1", "moment": "^2.29.1",
"nodemon": "^2.0.12", "nodemon": "^2.0.12",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"slug": "^5.1.0", "slug": "^8.0.0",
"stylelint": "^13.13.1", "stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0" "stylelint-config-standard": "^22.0.0"
}, },

View file

@ -11,10 +11,11 @@ module.exports = {
projectRoot, projectRoot,
/* Nodes */ /* Nodes */
nodeModules: join(projectRoot, 'node_modules'), nodeModules: join(projectRoot, 'node_modules'),
/* Metalsmith */ /* Gemini Distribution */
metalsmithSource: 'content', geminiSource: 'gemini',
metalsmithDestination: distribution, geminiDestination: join(distribution, 'public_gemini'),
/* Server */ /* Web Distribution */
serverRoot: distribution webSource: 'content',
webDestination: join(distribution, 'public_html')
} }
} }

View file

@ -0,0 +1,173 @@
const config = require('./config.js')
const debug = require('debug')('gemini')
const Metalsmith = require('metalsmith')
const layouts = require('@metalsmith/layouts')
const moment = require('moment')
const path = require('path')
const slug = require('slug')
const statistics = require('./metalsmith-statistics-plugin')
const __PROD__ = process.env.NODE_ENV === 'production'
moment.locale('fr')
function store (collection, key, value) {
if (collection[key] === undefined) { collection[key] = [] }
collection[key].push(value)
}
function isGemText (filename) {
return /\.gmi$|\.gemini$/.test(path.extname(filename))
}
function filepath (filename, segments) {
const name = slug(filename, { mode: 'rfc3986' }) + '.gemini'
return path.join.apply(null, (segments || ['./']).concat([name]))
}
function sortedComments (metalsmith, index) {
const sortedComments = {}
metalsmith.metadata().comments.forEach(entry => {
if (index instanceof Function) {
store(sortedComments, index(entry.comment), entry)
} else {
store(sortedComments, entry.comment[index], entry)
}
})
return sortedComments
}
function createIndex (files, filename, body) {
const entry = Object.assign({
contents: Buffer.from(body)
})
files[filename] = entry
}
function createCommentsIndex (index, entries) {
let body = '# ' + index + '\n\n'
entries.forEach(entry => {
body += '=> /' + entry.filename + ' ' + entry.book.title + ', ' + entry.book.author
if (index === entry.comment.medium) {
body += ' (' + entry.comment.date.year() + ')\n'
} else {
body += ' (sur ' + entry.comment.medium + ')\n'
body += entry.comment.excerpt
}
})
body += '\n=> /chroniques Retour à la liste complète\n'
return body
}
function rootIndexPlugin (files, metalsmith, done) {
let body = '# Chroniques\n'
body += '\n## Par support\n\n'
Object.keys(sortedComments(metalsmith, 'medium')).forEach((medium) => {
body += '=> ' + filepath(medium, ['/chroniques']) + ' ' + medium + '\n'
})
body += '\n## Par année\n\n'
Object.keys(sortedComments(metalsmith, (c) => c.date.year())).forEach((year) => {
body += '=> /chroniques/' + year + ' ' + year + '\n'
})
createIndex(files, 'chroniques/index.gemini', body)
done()
}
function mediaIndexPlugin (files, metalsmith, done) {
Object.entries(sortedComments(metalsmith, 'medium')).forEach(([medium, comments]) => {
createIndex(files, filepath(medium, ['chroniques']), createCommentsIndex(medium, comments))
})
done()
}
function yearsIndexPlugin (files, metalsmith, done) {
Object.entries(sortedComments(metalsmith, (c) => c.date.year())).forEach(([year, comments]) => {
createIndex(files, filepath('index', ['chroniques', year]), createCommentsIndex(year, comments))
})
done()
}
function buildCommentsMetadata (files, metalsmith, done) {
const EXCERPT_MAX_LENGTH = 300
const comments = []
// TODO: The excerpt should take care of words boundaries
const buildExcerpt = function (contents) {
let length = contents.indexOf('\n')
if (length === -1) {
length = contents.length
}
let excerpt = contents.substr(0, Math.min(length, EXCERPT_MAX_LENGTH))
if (length > EXCERPT_MAX_LENGTH) {
excerpt += '…'
}
return excerpt + '\n'
}
Object.keys(files).forEach(filename => {
if (isGemText(filename)) {
const data = files[filename]
if (data.comment !== undefined) {
data.comment.date = moment(data.comment.date)
data.comment.excerpt = buildExcerpt(data.contents.toString())
data.comment.size = data.contents.length
data.layout = 'gemini-comment.njk'
comments.push({
book: data.book,
comment: data.comment,
filename
})
}
}
})
debug('Identified %d comments: %s', comments.length)
/* Comment are ordered by title by default. */
comments.sort((a, b) => a.book.title.localeCompare(b.book.title))
metalsmith.metadata().comments = comments
done()
}
module.exports = new Metalsmith(config.paths.projectRoot)
.clean(__PROD__)
.metadata({
filepath
})
.source(config.paths.geminiSource)
.destination(config.paths.geminiDestination)
.use(buildCommentsMetadata)
.use(rootIndexPlugin)
.use(mediaIndexPlugin)
.use(yearsIndexPlugin)
// TODO: atom.xml (et lien depuis l'index)
.use(layouts({
default: 'gemini.njk',
pattern: '**/*.gemini',
engineOptions: {
globals: {
production: __PROD__
}
}
}))
.use(statistics())

View file

@ -24,12 +24,12 @@ module.exports = new Metalsmith(config.paths.projectRoot)
title: 'David Soulayrol' title: 'David Soulayrol'
} }
}) })
.source(config.paths.metalsmithSource) .source(config.paths.webSource)
.destination(config.paths.metalsmithDestination) .destination(config.paths.webDestination)
.use(cleanCSS({})) .use(cleanCSS({}))
.use(assets({ .use(assets({
source: './assets/' + (process.env.NODE_ENV || 'dev'), source: './assets/' + (process.env.NODE_ENV || 'dev'),
destination: './' destination: config.paths.webDestination
})) }))
.use(groff({ .use(groff({
preprocessors: ['tbl'], preprocessors: ['tbl'],

View file

@ -1,15 +1,14 @@
const bs = require('browser-sync').create('Metalsmith') const bs = require('browser-sync').create('Metalsmith')
const config = require('./config.js') const config = require('./config.js')
const debug = require('debug')('Run') const debug = require('debug')('Run')
const metalsmith = require('./metalsmith') const metalsmithGemini = require('./metalsmith-gemini')
const metalsmithWeb = require('./metalsmith-web')
const path = require('path') const path = require('path')
const strip = require('strip-ansi') const strip = require('strip-ansi')
function build (sync) { function complete (dist, sync, err) {
debug('Building Metalsmith')
metalsmith.build((err) => {
if (err) { if (err) {
debug('Metalsmith build error:') debug(dist + ' build error:')
debug(err) debug(err)
if (sync) { if (sync) {
return bs.sockets.emit('fullscreen:message', { return bs.sockets.emit('fullscreen:message', {
@ -20,17 +19,27 @@ function build (sync) {
} else { } else {
throw err throw err
} }
} else {
debug(dist + ' build finished!')
} }
debug('Metalsmith build finished!')
if (sync) { if (sync) {
bs.reload() bs.reload()
} }
}) }
function buildGemini () {
debug('Building Gemini distribution')
metalsmithGemini.build((err) => { complete('Gemini', false, err) })
}
function buildWeb (sync) {
debug('Building Web distribution')
metalsmithWeb.build((err) => { complete('Web', sync, err) })
} }
function serve () { function serve () {
bs.init({ bs.init({
server: config.paths.serverRoot, server: config.paths.webDestination,
port: 8080, port: 8080,
ui: { ui: {
port: 9000 port: 9000
@ -47,7 +56,7 @@ function serve () {
path.resolve(config.paths.projectRoot, 'layouts', '**', '*.njk') path.resolve(config.paths.projectRoot, 'layouts', '**', '*.njk')
], ],
fn: function (event, file) { fn: function (event, file) {
build(true) buildWeb(true)
}, },
options: { options: {
ignored: ['**/.#*', '**/*~', '**/#*#'] ignored: ['**/.#*', '**/*~', '**/#*#']
@ -61,7 +70,8 @@ const args = process.argv.slice(2)
switch (args[0]) { switch (args[0]) {
case 'build': case 'build':
build() buildGemini()
buildWeb()
break break
case 'serve': case 'serve':
serve() serve()