Quellcode durchsuchen

Add support for multiple paths on inputs and templates

Craig Fletcher vor 11 Monaten
Ursprung
Commit
c1d966cfb9
8 geänderte Dateien mit 148 neuen und 65 gelöschten Zeilen
  1. 26 6
      README.md
  2. 33 23
      eslint.config.js
  3. 7 1
      package.json
  4. 10 6
      src/defaults.js
  5. 2 2
      src/index.js
  6. 20 20
      src/lib.js
  7. 11 7
      src/processors.js
  8. 39 0
      src/util.js

+ 26 - 6
README.md

@@ -45,7 +45,9 @@ You can also build a self-contained executable if you like chunky binaries with
 A working example of how to configure can be found in `src/defaults.js`, with the processors separated out to
 `src/processors.js` for tidiness. 
 
-Your `rhedyn.config.js` should export an object with "tasks" and "opts". 
+Your `rhedyn.config.js` should export an object with "tasks" and "opts". These are detailed below.
+
+If you want to extend the default config, it can be imported as `defaultConfig` from this package.
 
 #### opts
 
@@ -53,6 +55,19 @@ The `opts` object in your config is for anything related to the project as a who
 means things like the base output directory, where to find templates for the renderer, etc. It'll be passed to the
 processor function as a key on the `meta` object.
 
+`opts` can also have an `include` property, an object containing task keys (e.g. "styles") and paths to include for
+that task, such as additional styles or templates. The path can be anywhere, not just local to the project - I use it to
+include basic typography styles that I know I'll want in every project, but it could just as easily be used to produce a
+sort-of "theme" that could be shared across multiple projects.
+
+  ```
+    opts: {
+      baseDir: 'dist/',
+      templatesDirs: ['templates/'],
+      defaultTemplate: 'default',
+      include: { styles: ['~/.rhedyn/styles/'] }
+    },
+  ```
 
 #### tasks
 
@@ -61,7 +76,7 @@ Tasks should be an array of objects that look something like this:
   ```
   {
     name: "styles",
-    inputDir: "styles/",
+    inputDirs: ["styles/"],
     outputDir: "static/styles/",
     inputFileExtension: ".scss",
     outputFileExtension: ".css",
@@ -70,7 +85,10 @@ Tasks should be an array of objects that look something like this:
   ```
 
 All properties are mandatory, and simply specify where we're reading the files in from and their extensions then where
-to put them once we've processed them. `processor` is a function that takes a file path as it's first argument, which is
+to put them once we've processed them. `inputDirs` is an array of paths, so you could include multiple paths here if you
+wish (some SCSS from a node module, for example).
+
+`processor` is a function that takes a file path as it's first argument, which is
 the file path to process, and `meta` as the second argument. `meta` contains the `opts` object, along with the
 already-processed filepaths from other tasks:
 
@@ -78,8 +96,9 @@ already-processed filepaths from other tasks:
   {
     opts: {
       baseDir: 'dist/',
-      templatesDir: 'templates/',
-      defaultTemplate: 'default'
+      templateDirs: ['templates/'],
+      defaultTemplate: 'default',
+      include: { styles: ['~/.rhedyn/styles/'] }
     },
     resources: { styles: [ 
       { path: 'static/styles/main.css', detail: {} } 
@@ -93,4 +112,5 @@ A processor is just a function that takes a filepath and meta, returns an object
 optionally `detail`. `detail` can contain any properties you like, and is generally for annotating things like "date" or
 "author" that might be extracted from the source file (think frontmatter and the like).
 
-Some examples can be found in `src/processors.js`.
+Some examples can be found in `src/processors.js`, and you can find some utility functions exported from this package as
+`utils`. The sample processors are also exported from this module, as `processors`.

+ 33 - 23
eslint.config.js

@@ -1,37 +1,47 @@
-import js from '@eslint/js'
+import globals from "globals"
+import js from "@eslint/js"
 
 export default [
   js.configs.recommended,
   {
+    languageOptions: {
+      globals: {
+        ...globals.node,
+      },
+    },
     rules: {
-      /**
-       * Custom rules.
-       * docs: https://eslint.org/docs/rules
-       */
-      'quotes': ['error', 'double'],
-      'indent': [
-        'error', 2, { 'SwitchCase': 1 },
-      ],
-      'no-multi-spaces': 'error',
-      'prefer-const': 'error',
-      'array-bracket-newline': [
-        'error', {
+      "array-bracket-newline": [
+        "error",
+        {
           minItems: 3,
           multiline: true,
         },
       ],
-      'array-element-newline': ['error', 'consistent'],
-      'comma-dangle': ['error', 'always-multiline'],
-      'no-process-env': 'off',
-      'no-undef': 'error',
-      'no-multiple-empty-lines': [
-        'error', {
-          'max': 1,
+      "array-element-newline": ["error", "consistent"],
+      "comma-dangle": ["error", "always-multiline"],
+      indent: [
+        "error", 2, { SwitchCase: 1 },
+      ],
+      "no-console": "off",
+      "no-multi-spaces": "error",
+      "no-multiple-empty-lines": [
+        "error",
+        {
+          max: 1,
         },
       ],
-      'no-var': 'error',
-      'object-curly-spacing': ['error', 'always'],
-      'semi': ['error', 'never'],
+      "no-process-env": "off",
+      "no-undef": "error",
+      "no-var": "error",
+      "object-curly-spacing": ["error", "always"],
+      "prefer-const": "error",
+      quotes: ["error", "double"],
+      semi: ["error", "never"],
+      "sort-imports": ["error", { ignoreCase: true }],
+      "sort-keys": [
+        "error", "asc", { caseSensitive: true, natural: true },
+      ],
+      "sort-vars": ["error", { ignoreCase: true }],
     },
   },
 ]

+ 7 - 1
package.json

@@ -5,11 +5,17 @@
   "main": "src/index.js",
   "scripts": {
     "generate": "node src/index.js",
-    "build": "nexe -i src/index.js -o dist/rhedyn --build --python=$(which python3)"
+    "build": "nexe -i src/index.js -o dist/rhedyn --build --python=$(which python3)",
+    "lint-fix": "eslint --fix src/*.js"
   },
   "bin": {
     "rhedyn": "src/index.js"
   },
+  "exports": {
+    "./utils": "./src/util.js",
+    "./defaultConfig": "./src/defaults.js",
+    "./processors": "./src/processors.js"
+  },
   "keywords": [],
   "author": "",
   "license": "ISC",

+ 10 - 6
src/defaults.js

@@ -1,18 +1,19 @@
 import { compileSass, renderMarkdownWithTemplate } from "./processors.js"
+
 export const tasks = [
   {
+    inputDirs: ["styles/"],
+    inputFileExtension: ".scss",
     name: "styles",
-    inputDir: "styles/",
     outputDir: "static/styles/",
-    inputFileExtension: ".scss",
     outputFileExtension: ".css",
     processor: compileSass,
   },
   {
+    inputDirs: ["markdown/"],
+    inputFileExtension: ".md",
     name: "pages",
-    inputDir: "markdown/",
     outputDir: "./",
-    inputFileExtension: ".md",
     outputFileExtension: ".html",
     processor: renderMarkdownWithTemplate,
   },
@@ -20,13 +21,16 @@ export const tasks = [
 
 export const opts = {
   baseDir: "dist/",
-  templatesDir: "templates/",
   defaultTemplate: "default",
+  include: {
+    styles: ["~/.rhedyn/styles/"],
+  },
+  templateDirs: ["templates/", "~/.rhedyn/templates/"],
 }
 
 const defaults = {
-  tasks,
   opts,
+  tasks,
 }
 
 export default defaults

+ 2 - 2
src/index.js

@@ -1,9 +1,9 @@
 #!/usr/bin/env node
 
+import * as defaultConfig from "./defaults.js"
 import { getConfig, processFiles } from "./lib.js"
-import defaults from "./defaults.js"
 
-const { opts, tasks } = await getConfig() || { ...defaults }
+const { opts, tasks } = await getConfig() || { ...defaultConfig }
 
 console.log(`Processing ${tasks.length} tasks`)
 tasks.reduce(

+ 20 - 20
src/lib.js

@@ -1,5 +1,10 @@
-import path from "path"
+import {
+  readDirectoryRecursively,
+  removeBasePaths,
+  resolvePath,
+} from "./util.js"
 import fs from "fs"
+import path from "path"
 import process from "node:process"
 
 export async function getConfig() {
@@ -10,35 +15,30 @@ export async function getConfig() {
       return config.default || config
     } catch (err) {
       console.error("Error reading rhedyn.config.js:", err)
-      return
+      throw new Error("Failed reading config file")
     }
   } else {
     return
   }
 }
 
-export function readDirectoryRecursively(dir, files = []) {
-  const contents = fs.readdirSync(dir, { withFileTypes: true })
-  for (const item of contents) {
-    const itemPath = path.join(dir, item.name)
-    if (item.isDirectory()) {
-      readDirectoryRecursively(itemPath, files)
-    } else {
-      files.push(itemPath)
-    }
-  }
-  return files
-}
-
 export function processFiles(config, meta) {
-  const filesToProcess = readDirectoryRecursively(config.inputDir)
+  const includes = meta.opts?.include?.[config.name] || []
+  const pathsToInclude = [...config.inputDirs, ...includes].map(resolvePath)
+  const filesToProcess = pathsToInclude
+    .map(dirPath => {
+      return readDirectoryRecursively(dirPath)
+    })
+    .flat()
+
   return filesToProcess.map(filePath => {
     const fileOutputPath = path.join(
       meta.opts.baseDir,
       config.outputDir,
-      filePath
-        .replace(config.inputDir, "")
-        .replace(config.inputFileExtension, config.outputFileExtension),
+      removeBasePaths(pathsToInclude, filePath).replace(
+        config.inputFileExtension,
+        config.outputFileExtension,
+      ),
     )
     const fileOutputDir = path.dirname(fileOutputPath)
     if (!fs.existsSync(fileOutputDir)) {
@@ -49,8 +49,8 @@ export function processFiles(config, meta) {
     fs.writeFileSync(fileOutputPath, result)
 
     return {
-      path: fileOutputPath.replace(meta.opts.baseDir, ""),
       detail,
+      path: fileOutputPath.replace(meta.opts.baseDir, ""),
     }
   })
 }

+ 11 - 7
src/processors.js

@@ -1,10 +1,10 @@
+import * as sass from "sass"
+import { firstFound } from "./util.js"
 import fs from "fs"
-import path from "path"
-import matter from "gray-matter"
-import { marked } from "marked"
 import handlebars from "handlebars"
-import * as sass from "sass"
+import { marked } from "marked"
 import markedCodePreview from "marked-code-preview"
+import matter from "gray-matter"
 
 const markedRenderer = marked.use({ gfm: true }).use(markedCodePreview)
 
@@ -12,9 +12,10 @@ export function renderMarkdownWithTemplate(filePath, meta) {
   const content = fs.readFileSync(filePath, "utf8")
   const { data, content: markdown } = matter(content)
   const templateName = data.template || meta.opts.defaultTemplate
+
   const template = handlebars.compile(
     fs.readFileSync(
-      path.join(meta.opts.templatesDir, `${templateName}.hbs`),
+      firstFound(meta.opts.templateDirs, `${templateName}.hbs`),
       "utf8",
     ),
   )
@@ -23,10 +24,13 @@ export function renderMarkdownWithTemplate(filePath, meta) {
     ...meta,
     content: markedRenderer(markdown),
   })
-  return { result: html, detail: data }
+  return {
+    detail: data,
+    result: html,
+  }
 }
 
-export function compileSass(filePath, meta) {
+export function compileSass(filePath) {
   return {
     result: sass.compile(filePath, { style: "compressed" }).css.toString(),
   }

+ 39 - 0
src/util.js

@@ -0,0 +1,39 @@
+import fs from "fs"
+import os from "node:os"
+import path from "path"
+
+export function readDirectoryRecursively(dir, files = []) {
+  if (!fs.existsSync(dir)) {
+    return files
+  }
+  const contents = fs.readdirSync(dir, { withFileTypes: true })
+  for (const item of contents) {
+    const itemPath = path.join(dir, item.name)
+    if (item.isDirectory()) {
+      readDirectoryRecursively(itemPath, files)
+    } else {
+      files.push(itemPath)
+    }
+  }
+  return files
+}
+
+export function resolvePath(unresolvedPath) {
+  return path.resolve(unresolvedPath.replace(/^~/, os.homedir()))
+}
+
+export function firstFound(dirs, fileName) {
+  for (const dir of dirs) {
+    const filePath = resolvePath(path.join(dir, fileName))
+    if (fs.existsSync(filePath)) {
+      return filePath
+    }
+  }
+  return null
+}
+
+export function removeBasePaths(baseDirs, fullPath) {
+  return baseDirs.reduce((cleanedPath, dir) => {
+    return cleanedPath.replace(dir, "")
+  }, fullPath)
+}