Initial readings data on gemini capsule.
This commit is contained in:
parent
05d39cc8b5
commit
f49202db85
12 changed files with 2004 additions and 138 deletions
134
scripts/metalsmith-gemini-comments.js
Normal file
134
scripts/metalsmith-gemini-comments.js
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
const debug = require('debug')('metalsmith-gemini:comments')
|
||||
const moment = require('moment')
|
||||
|
||||
module.exports = plugins
|
||||
|
||||
/**
|
||||
* Metalsmith plugins collection to handle comments.
|
||||
*
|
||||
* @return {Function}
|
||||
*/
|
||||
function plugins (helpers) {
|
||||
function store (collection, key, value) {
|
||||
if (collection[key] === undefined) { collection[key] = [] }
|
||||
collection[key].push(value)
|
||||
}
|
||||
|
||||
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 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
|
||||
}
|
||||
|
||||
return {
|
||||
buildMetadata: function (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 (helpers.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', 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()
|
||||
},
|
||||
|
||||
rootIndex: function (files, metalsmith, done) {
|
||||
let body = '# Chroniques\n'
|
||||
|
||||
body += '\n## Par support\n\n'
|
||||
Object.keys(sortedComments(metalsmith, 'medium')).forEach((medium) => {
|
||||
body += '=> ' + helpers.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'
|
||||
})
|
||||
|
||||
helpers.createFile(files, 'chroniques/index.gemini', body)
|
||||
|
||||
done()
|
||||
},
|
||||
|
||||
mediaIndexes: function (files, metalsmith, done) {
|
||||
Object.entries(sortedComments(metalsmith, 'medium')).forEach(([medium, comments]) => {
|
||||
helpers.createFile(files, helpers.filepath(medium, ['chroniques']), createCommentsIndex(medium, comments))
|
||||
})
|
||||
|
||||
done()
|
||||
},
|
||||
|
||||
yearIndexes: function (files, metalsmith, done) {
|
||||
Object.entries(sortedComments(metalsmith, (c) => c.date.year())).forEach(([year, comments]) => {
|
||||
helpers.createFile(files, helpers.filepath('index', ['chroniques', year]), createCommentsIndex(year, comments))
|
||||
})
|
||||
|
||||
done()
|
||||
}
|
||||
}
|
||||
}
|
||||
246
scripts/metalsmith-gemini-readings.js
Normal file
246
scripts/metalsmith-gemini-readings.js
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
const debug = require('debug')('metalsmith-gemini:readings')
|
||||
const path = require('path')
|
||||
|
||||
module.exports = plugins
|
||||
|
||||
/**
|
||||
* Metalsmith plugins collection to handle readings.
|
||||
*
|
||||
* @return {Function}
|
||||
*/
|
||||
function plugins (helpers) {
|
||||
const Categories = Object.freeze({
|
||||
0: 'Fictions',
|
||||
10: 'Essais',
|
||||
20: 'BD',
|
||||
30: 'Poésie',
|
||||
40: 'Théâtre'
|
||||
})
|
||||
|
||||
function getBibAuthors (book) {
|
||||
// TODO: books should have list of authors
|
||||
const authors = book.a
|
||||
|
||||
return authors
|
||||
}
|
||||
|
||||
function getBibPublishing (book, publishers) {
|
||||
const p = publishers[book.p]
|
||||
let published
|
||||
|
||||
if (p != null) {
|
||||
published = p.c + ' : ' + p.n
|
||||
if (book.c != null) {
|
||||
published += ' (' + p.C[book.c] + ')'
|
||||
}
|
||||
published += ', ' + (book.y || '?')
|
||||
} else {
|
||||
published = '?'
|
||||
if (p !== undefined) {
|
||||
console.warn('Unknown publisher "' + book.p + '"')
|
||||
}
|
||||
}
|
||||
|
||||
return published
|
||||
}
|
||||
|
||||
function getBibTitle (book) {
|
||||
let title = book.t
|
||||
if (!'.!?'.includes(title.charAt(title.length - 1))) {
|
||||
title += '.'
|
||||
}
|
||||
if (book.v != null) {
|
||||
title += ' ' + book.v + ' vols.'
|
||||
}
|
||||
|
||||
return title
|
||||
}
|
||||
|
||||
function writeLine (book, publishers) {
|
||||
return '* ' + getBibAuthors(book) + '. ' + getBibTitle(book) + ' ' + getBibPublishing(book, publishers) + '\n'
|
||||
}
|
||||
|
||||
function createIndex (year, books, publishers) {
|
||||
const sections = {}
|
||||
let body = '# ' + year + '\n\n'
|
||||
|
||||
Object.keys(Categories).forEach((key) => { sections[key] = '' })
|
||||
|
||||
books.forEach(b => {
|
||||
const line = writeLine(b, publishers)
|
||||
if (line.length > 0) {
|
||||
sections[b.C || 0] += line
|
||||
}
|
||||
})
|
||||
|
||||
Object.entries(Categories).forEach(([key, title]) => {
|
||||
const section = sections[key]
|
||||
|
||||
if (section.length !== 0) {
|
||||
body += '## ' + title + '\n\n'
|
||||
body += section + '\n'
|
||||
}
|
||||
})
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
function getBy (books, key) {
|
||||
const map = {}
|
||||
|
||||
books.forEach(book => {
|
||||
if (map[book[key]] == null) {
|
||||
map[book[key]] = []
|
||||
}
|
||||
map[book[key]].push(book)
|
||||
})
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
function getByPublisher (books) {
|
||||
return getBy(books, 'p')
|
||||
}
|
||||
|
||||
function getByYear (books) {
|
||||
return getBy(books, 'Y')
|
||||
}
|
||||
|
||||
function isJson (filename) {
|
||||
return /\.json$/.test(path.extname(filename))
|
||||
}
|
||||
|
||||
return {
|
||||
buildMetadata: function (files, metalsmith, done) {
|
||||
const books = []
|
||||
let publishers = {}
|
||||
|
||||
Object.keys(files).forEach(filename => {
|
||||
if (isJson(filename)) {
|
||||
if (filename.endsWith('publishers.json')) {
|
||||
publishers = JSON.parse(files[filename].contents)
|
||||
debug('Identified %d publishers', Object.keys(publishers).length)
|
||||
} else {
|
||||
const year = path.basename(filename, '.json')
|
||||
JSON.parse(files[filename].contents).forEach(b => {
|
||||
b.Y = year
|
||||
books.push(b)
|
||||
})
|
||||
}
|
||||
delete files[filename]
|
||||
}
|
||||
})
|
||||
|
||||
debug('Identified %d books', books.length)
|
||||
|
||||
metalsmith.metadata().books = books
|
||||
metalsmith.metadata().publishers = publishers
|
||||
|
||||
done()
|
||||
},
|
||||
|
||||
authorsIndex: function (files, metalsmith, done) {
|
||||
const authors = {}
|
||||
const publishers = metalsmith.metadata().publishers
|
||||
let body = '# Auteurs\n'
|
||||
|
||||
metalsmith.metadata().books.forEach(book => {
|
||||
if (authors[book.a] == null) {
|
||||
authors[book.a] = []
|
||||
}
|
||||
authors[book.a].push(book)
|
||||
})
|
||||
|
||||
Object.keys(authors).sort().forEach(author => {
|
||||
const lines = []
|
||||
authors[author].sort().forEach(book => {
|
||||
lines.push('* ' + getBibTitle(book) + ' ' + getBibPublishing(book, publishers) + '\n')
|
||||
})
|
||||
body += '\n## ' + author + '\n' + lines.sort().join('')
|
||||
})
|
||||
|
||||
helpers.createFile(files, helpers.filepath('auteurs', ['lectures']), body)
|
||||
|
||||
done()
|
||||
},
|
||||
|
||||
publishersIndex: function (files, metalsmith, done) {
|
||||
const publishers = metalsmith.metadata().publishers
|
||||
let body = '# Éditions\n'
|
||||
|
||||
Object.entries(getByPublisher(metalsmith.metadata().books)).forEach(([pub, books]) => {
|
||||
const p = publishers[pub]
|
||||
|
||||
// TODO: Extract collections in a set
|
||||
if (p != null) {
|
||||
// let collections = {}
|
||||
const lines = []
|
||||
|
||||
books.forEach(book => {
|
||||
lines.push('* ' + getBibAuthors(book) + '. ' + getBibTitle(book) + '\n')
|
||||
})
|
||||
body += '\n## ' + p.n + '\n' + lines.sort().join('')
|
||||
}
|
||||
})
|
||||
|
||||
helpers.createFile(files, helpers.filepath('editeurs', ['lectures']), body)
|
||||
|
||||
done()
|
||||
},
|
||||
|
||||
rootIndex: function (files, metalsmith, done) {
|
||||
let body = '# Lectures\n\n'
|
||||
|
||||
body += '=> auteurs.gemini La liste alphabétique des auteurs lus\n'
|
||||
body += '=> editeurs.gemini La liste alphabétique des éditeurs rencontrés\n'
|
||||
body += '=> stats.gemini Quelques statistiques\n\n'
|
||||
|
||||
body += '## Par année\n\n'
|
||||
Object.keys(getByYear(metalsmith.metadata().books)).forEach(year => {
|
||||
body += '=> /lectures/' + year + '.gemini ' + year + '\n'
|
||||
})
|
||||
|
||||
helpers.createFile(files, helpers.filepath('index', ['lectures']), body)
|
||||
|
||||
done()
|
||||
},
|
||||
|
||||
statisticsIndex: function (files, metalsmith, done) {
|
||||
const stats = { a: new Set(), y: {} }
|
||||
const books = metalsmith.metadata().books
|
||||
let body = '# Quelques statistiques\n\n'
|
||||
|
||||
books.forEach(book => {
|
||||
stats.a.add(book.a)
|
||||
if (stats.y[book.Y] == null) {
|
||||
stats.y[book.Y] = []
|
||||
}
|
||||
stats.y[book.Y].push(book)
|
||||
})
|
||||
|
||||
body += 'Depuis le printemps 2010, ce sont ' + books.length + ' lectures recensées.\n'
|
||||
body += 'Ces lectures couvrent ' + stats.a.size + ' auteurs différents.\n'
|
||||
|
||||
body += '## Par année\n'
|
||||
Object.entries(stats.y).forEach(([year, books]) => {
|
||||
body += '* ' + year + ': ' + books.length + ' livres\n'
|
||||
})
|
||||
|
||||
helpers.createFile(files, helpers.filepath('stats', ['lectures']), body)
|
||||
|
||||
done()
|
||||
},
|
||||
|
||||
yearIndexes: function (files, metalsmith, done) {
|
||||
const publishers = metalsmith.metadata().publishers
|
||||
|
||||
Object.entries(getByYear(metalsmith.metadata().books)).forEach(([year, books]) => {
|
||||
// Sort by category and then by authors
|
||||
const content = createIndex(year, books, publishers)
|
||||
helpers.createFile(files, helpers.filepath(year, ['lectures']), content)
|
||||
})
|
||||
|
||||
done()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
const comments = require('./metalsmith-gemini-comments.js')
|
||||
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 readings = require('./metalsmith-gemini-readings.js')
|
||||
const slug = require('slug')
|
||||
const statistics = require('./metalsmith-statistics-plugin')
|
||||
|
||||
|
|
@ -11,144 +12,33 @@ 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 = {}
|
||||
const helpers = {
|
||||
createFile: function (files, filename, body) {
|
||||
const entry = Object.assign({
|
||||
contents: Buffer.from(body)
|
||||
})
|
||||
|
||||
metalsmith.metadata().comments.forEach(entry => {
|
||||
if (index instanceof Function) {
|
||||
store(sortedComments, index(entry.comment), entry)
|
||||
} else {
|
||||
store(sortedComments, entry.comment[index], entry)
|
||||
}
|
||||
})
|
||||
files[filename] = entry
|
||||
},
|
||||
|
||||
return sortedComments
|
||||
}
|
||||
filepath: function (filename, segments) {
|
||||
const name = slug(filename, { mode: 'rfc3986' }) + '.gemini'
|
||||
return path.join.apply(null, (segments || ['./']).concat([name]))
|
||||
},
|
||||
|
||||
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'
|
||||
isGemText: function (filename) {
|
||||
return /\.gmi$|\.gemini$/.test(path.extname(filename))
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
const commentsGenerator = comments(helpers)
|
||||
const readingsGenerator = readings(helpers)
|
||||
|
||||
module.exports = new Metalsmith(config.paths.projectRoot)
|
||||
.clean(__PROD__)
|
||||
.metadata({
|
||||
|
|
@ -156,11 +46,17 @@ module.exports = new Metalsmith(config.paths.projectRoot)
|
|||
})
|
||||
.source(config.paths.geminiSource)
|
||||
.destination(config.paths.geminiDestination)
|
||||
.use(buildCommentsMetadata)
|
||||
.use(rootIndexPlugin)
|
||||
.use(mediaIndexPlugin)
|
||||
.use(yearsIndexPlugin)
|
||||
.use(commentsGenerator.buildMetadata)
|
||||
.use(commentsGenerator.rootIndex)
|
||||
.use(commentsGenerator.mediaIndexes)
|
||||
.use(commentsGenerator.yearIndexes)
|
||||
// TODO: atom.xml (et lien depuis l'index)
|
||||
.use(readingsGenerator.buildMetadata)
|
||||
.use(readingsGenerator.authorsIndex)
|
||||
.use(readingsGenerator.publishersIndex)
|
||||
.use(readingsGenerator.rootIndex)
|
||||
.use(readingsGenerator.statisticsIndex)
|
||||
.use(readingsGenerator.yearIndexes)
|
||||
.use(layouts({
|
||||
default: 'gemini.njk',
|
||||
pattern: '**/*.gemini',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue