@liascript/exporter
Version:
A generic exporter for LiaScript
349 lines (305 loc) • 8.96 kB
text/typescript
import * as helper from './helper'
const jsonld = require('jsonld')
const fs = require('fs-extra')
import fetch from 'node-fetch'
import * as COLOR from '../colorize'
export function help() {
console.log('')
console.log(COLOR.heading('RDF settings:'), '\n')
COLOR.info(
'RDF (Resource Description Framework) export generates structured metadata for your LiaScript course or your project-yaml in standard linked data formats. This helps with course discovery and enables semantic web applications to understand your content. Available output formats are n-quads and JSON-LD.'
)
console.log('\nLearn more:')
console.log('- RDF: https://www.w3.org/RDF/')
console.log('- N-Quads: https://www.w3.org/TR/n-quads/')
console.log('- JSON-LD: https://json-ld.org/')
console.log('')
COLOR.command(
null,
'--rdf-format',
' Output format n-quads, json-ld (defaults to json-ld).'
)
COLOR.command(
null,
'--rdf-preview',
' Output the result to the console.'
)
COLOR.command(
null,
'--rdf-url',
' Refer to an external URL when parsing a local project.'
)
COLOR.command(
null,
'--rdf-type',
' Course frm schmema.org is applied as default, overwrite this with EducationalResource, etc.'
)
COLOR.command(
null,
'--rdf-license',
' Add a license-URL, otherwise if url was provided as input, this will check for an existing LICENSE file.'
)
COLOR.command(
null,
'--rdf-educationalLevel',
' Typically beginner, intermediate or advanced, and formal sets of level indicators.'
)
COLOR.command(
null,
'--rdf-template',
' Use a URL or json-file as a template.'
)
}
export async function exporter(
argument: {
input: string
readme: string
output: string
format: string
path: string
key?: string
style?: string
// special cases for RDF
'rdf-format'?: string
'rdf-preview'?: string
'rdf-url'?: string
'rdf-type'?: string
'rdf-template'?: string
'rdf-license'?: string
'rdf-educationalLevel'?: string
},
json
) {
let doc = await parse(argument, json)
if (argument['rdf-format'] === 'n-quads') {
const nquads = await jsonld.toRDF(doc, { format: 'application/n-quads' })
if (argument['rdf-preview']) {
console.log(nquads)
} else {
fs.writeFile(argument.output + '.nq', nquads, function (err) {
if (err) console.error(err)
})
}
} else {
doc = clean(doc)
if (argument['rdf-preview']) {
console.log(JSON.stringify(doc, null, 2))
} else {
fs.writeFile(
argument.output + '.jsonld',
JSON.stringify(doc, null, 2),
function (err) {
if (err) console.error(err)
}
)
}
}
}
export async function script(
argument: {
input: string
readme: string
output: string
format: string
path: string
key?: string
style?: string
// special cases for RDF
'rdf-format'?: string
'rdf-preview'?: string
'rdf-url'?: string
'rdf-type'?: string
'rdf-template'?: string
'rdf-license'?: string
'rdf-educationalLevel'?: string
},
json
) {
let doc = await parse(argument, json)
doc = await jsonld.compact(doc, 'http://schema.org/')
doc = clean(doc)
return `<script type="application/ld+json">
${JSON.stringify(doc, null, 2)}
</script>`
}
export async function compact(doc: any) {
return await jsonld.compact(doc, 'http://schema.org/')
}
export async function parse(
argument: {
input: string
readme: string
output: string
format: string
path: string
key?: string
style?: string
// special cases for RDF
'rdf-format'?: string
'rdf-preview'?: string
'rdf-url'?: string
'rdf-type'?: string
'rdf-template'?: string
'rdf-license'?: string
'rdf-educationalLevel'?: string
},
json
) {
let doc = {}
if (argument['rdf-template']) {
if (helper.isURL(argument['rdf-template'])) {
const resp = await fetch(argument['rdf-template'], {})
const data = await resp.json()
if (data) {
doc = await jsonld.expand(data)
} else {
console.warn('could not load template from:', argument['rdf-template'])
}
} else {
const data = fs.readFileSync(argument['rdf-template'], 'utf8')
doc = await jsonld.expand(JSON.parse(data))
}
}
doc['http://schema.org/name'] = json.lia.str_title
doc['http://schema.org/@type'] =
doc['http://schema.org/@type'] || argument['rdf-type'] || 'Course'
let baseURL: string | null = null
// If a Url is defined, this Url is used as the key and to generate
if (helper.isURL(argument.input) || argument['rdf-url']) {
doc['http://schema.org/@id'] =
doc['http://schema.org/@id'] || argument['rdf-url'] || argument.input
doc['http://schema.org/url'] =
doc['http://schema.org/url'] ||
'https://LiaScript.github.io/course/?' +
(argument['rdf-url'] || argument.input)
baseURL = helper.baseURL(argument['rdf-url'] || argument.input)
}
if (argument['rdf-educationalLevel']) {
doc['http://schema.org/educationalLevel'] = argument['rdf-educationalLevel']
}
doc = baseInformation(doc, json.lia.definition)
doc = langInformation(doc, json.lia.definition)
doc = logoInformation(doc, json.lia.definition, baseURL)
doc = await licenseInformation(doc, argument, baseURL)
doc = await jsonld.compact(doc, 'http://schema.org')
return clean(doc)
}
/**
* Adds the following information, if they exist:
*
* - author:
* - name
* - email
* - description
* - keywords
* - version
*/
function baseInformation(doc: any, definition: any) {
if (definition?.author || definition?.email) {
const author = { 'http://schema.org/@type': 'Person' }
if (definition?.author) {
author['http://schema.org/name'] = definition?.author
}
if (definition?.email) {
author['http://schema.org/email'] = definition?.email
}
doc['http://schema.org/author'] = doc['http://schema.org/author'] || author
}
if (definition.macro?.comment) {
doc['http://schema.org/description'] =
doc['http://schema.org/description'] || definition.macro?.comment
}
if (definition.macro?.tags) {
if (typeof doc['http://schema.org/keywords'] === 'string') {
doc['http://schema.org/keywords'] += ', ' + definition.macro.tags
} else {
const tags = definition.macro.tags.split(',').map((e: string) => e.trim())
if (typeof doc['http://schema.org/keywords'] === 'undefined') {
doc['http://schema.org/keywords'] = tags
} else {
doc['http://schema.org/keywords'] =
doc['http://schema.org/keywords'].concat(tags)
}
}
}
if (definition?.version) {
doc['http://schema.org/version'] =
doc['http://schema.org/version'] || definition?.version
}
return doc
}
/**
* Adds language information:
*
* - inLanguage
* - TODO: Translations
* @returns
*/
function langInformation(doc: any, definition: any) {
if (definition?.language) {
doc['http://schema.org/inLanguage'] =
doc['http://schema.org/inLanguage'] || definition.language
}
return doc
}
/**
* Adds the image information.
*
* - image: from LiaScript logo
* - TODO: thumbnailUrl: from LiaScript icon
*/
function logoInformation(doc: any, definition: any, baseURL: null | string) {
if (definition?.logo) {
let imageUrl: string | null = null
if (helper.isURL(definition?.logo)) {
imageUrl = definition?.logo
} else if (baseURL) {
imageUrl = new URL(definition?.logo, baseURL).href
}
if (imageUrl) {
doc['http://schema.org/image'] = doc['http://schema.org/image'] || {
'http://schema.org/@type': 'ImageObject',
'http://schema.org/url': imageUrl,
}
}
}
return doc
}
async function licenseInformation(
doc: any,
argument: any,
baseURL: null | string
) {
let licenseUrl: string | null = null
if (argument['rdf-license']) {
licenseUrl = argument['rdf-license']
} else if (baseURL && (await helper.checkLicense(baseURL))) {
licenseUrl = new URL(baseURL, 'LICENSE').href
}
if (licenseUrl) {
doc['http://schema.org/license'] =
doc['http://schema.org/license'] || licenseUrl
}
return doc
}
/**
* For some reason, jsonld puts to all keys with a "Url" a "schema:"
* to the front. This functions replaces all "schema:.*" by ".*".
*/
function clean(obj: Object) {
if (!obj || typeof obj !== 'object') {
return obj
}
if (Array.isArray(obj)) {
return obj.map((item) => clean(item))
}
return Object.keys(obj).reduce((acc, key) => {
const value = obj[key]
if (key.startsWith('schema:')) {
acc[key.split(':').pop() || key] = value
} else {
acc[key] = clean(value)
}
return acc
}, {})
}