瀏覽代碼

Refactor: break up utils

Craig Fletcher 5 月之前
父節點
當前提交
d580801284
共有 10 個文件被更改,包括 232 次插入225 次删除
  1. 2 2
      package.json
  2. 1 1
      src/cache.js
  3. 1 1
      src/lib.js
  4. 1 1
      src/processors.js
  5. 0 220
      src/util.js
  6. 79 0
      src/util/file-system.js
  7. 8 0
      src/util/general-utils.js
  8. 4 0
      src/util/index.js
  9. 68 0
      src/util/object-utils.js
  10. 68 0
      src/util/path-utils.js

+ 2 - 2
package.json

@@ -6,13 +6,13 @@
   "scripts": {
     "generate": "node src/index.js",
     "build": "nexe -i src/index.js -o dist/rhedyn --build --python=$(which python3)",
-    "lint-fix": "eslint --fix src/*.js"
+    "lint-fix": "eslint --fix src/**/*.js"
   },
   "bin": {
     "rhedyn": "src/index.js"
   },
   "exports": {
-    "./utils": "./src/util.js",
+    "./utils": "./src/util/index.js",
     "./defaultConfig": "./src/defaults.js",
     "./processors": "./src/processors.js"
   },

+ 1 - 1
src/cache.js

@@ -9,7 +9,7 @@ import {
   getValueAtPath,
   removeCwd,
   getDeepestPropertiesForKey,
-} from "./util.js"
+} from "./util/index.js"
 
 export function hashObject(obj) {
   const str = stableStringify(obj)

+ 1 - 1
src/lib.js

@@ -10,7 +10,7 @@ import {
   removeCwd,
   replaceFileExtension,
   getValueAtPath,
-} from "./util.js"
+} from "./util/index.js"
 import path from "path"
 import process from "node:process"
 import { getLogger } from "./logging.js"

+ 1 - 1
src/processors.js

@@ -6,7 +6,7 @@ import {
   getHref,
   slugifyString,
   writeFile,
-} from "./util.js"
+} from "./util/index.js"
 import fs from "fs/promises"
 import handlebars from "handlebars"
 import { marked } from "marked"

+ 0 - 220
src/util.js

@@ -1,220 +0,0 @@
-import fs from "node:fs/promises"
-import os from "node:os"
-import path from "path"
-import { glob } from "glob"
-
-export async function fileExists(filePath) {
-  try {
-    await fs.stat(filePath)
-    return true
-  } catch (err) {
-    if (err.code === "ENOENT") {
-      return false
-    }
-    throw err // re-throw other errors
-  }
-}
-export async function readDirectoryRecursively(dir, files = []) {
-  const exists = await fileExists(dir)
-  if (!exists) {
-    return files
-  }
-  const contents = await fs.readdir(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
-}
-// type InputConfig
-// {
-//  pattern: String | String[];
-//  ignore:  String | String[];
-// }
-export async function readFilesByGlob(globConfigs) {
-  const matchPromises = globConfigs.reduce(
-    async (existingMatches, globConfig) => {
-      const { pattern, ignore, dot } = {
-        dot: false,
-        ignore: [],
-        ...globConfig,
-      }
-      const matches = await glob(pattern, {
-        ignore,
-        dot,
-      })
-      return [...(await existingMatches), ...matches]
-    },
-    [],
-  )
-  const files = await matchPromises
-  return [...new Set(files)]
-}
-
-export function resolvePath(unresolvedPath) {
-  return path.resolve(unresolvedPath.replace(/^~/, os.homedir()))
-}
-
-export async function firstFound(dirs, fileName) {
-  for (const dir of dirs) {
-    const filePath = resolvePath(path.join(dir, fileName))
-    const exists = await fileExists(filePath)
-    if (exists) {
-      return filePath
-    }
-  }
-  return null
-}
-
-export function removeCwd(paths) {
-  const cwd = `${process.cwd()}/`
-  return paths.map(path => path.replace(cwd, ""))
-}
-
-export function removeBasePaths(baseDirs, fullPath) {
-  return baseDirs.reduce((cleanedPath, dir) => {
-    return cleanedPath.replace(dir, "")
-  }, fullPath)
-}
-
-export function replaceFileExtension(filePath, newExtension) {
-  if (!newExtension) {
-    return filePath
-  }
-  return `${stripFileExtension(filePath)}${newExtension}`
-}
-
-export function stripFileExtension(filePath) {
-  return path.join(
-    path.dirname(filePath),
-    path.basename(filePath, path.extname(filePath)),
-  )
-}
-
-export function getCleanPath(filePath, meta) {
-  return filePath.replace(meta.opts.runDir, "").replace(meta.opts.outDir, "/")
-}
-
-export function getHref(filePath, meta) {
-  const route = getCleanPath(filePath, meta)
-  if (route.includes("index.html")) {
-    return route.replace("index.html", "")
-  }
-  return route.replace(".html", "")
-}
-
-function stringifyPathPart(part) {
-  return typeof part === "symbol" ? part.toString() : String(part)
-}
-
-function trackPropertyAccessDeep(obj, path = [], accessed = new Set()) {
-  return new Proxy(obj, {
-    get(target, prop, receiver) {
-      const fullPath = [...path, prop].map(stringifyPathPart).join(".")
-      const value = Reflect.get(target, prop, receiver)
-
-      if (typeof target === "object" && Object.prototype.hasOwnProperty.call(target, prop)) {
-        accessed.add({ path: fullPath, value })
-      }
-
-      // Recursively wrap if value is an object and not null
-      if (value && typeof value === "object") {
-        return trackPropertyAccessDeep(value, [...path, prop], accessed)
-      }
-
-      return value
-    },
-  })
-}
-
-export function createTrackedObject(obj) {
-  const accessed = new Set()
-  const proxy = trackPropertyAccessDeep(obj, [], accessed)
-  return { proxy, accessed }
-}
-
-export function getDeepestPropertiesForKey(paths, key) {
-  // Sort paths to make prefix comparison easier
-  const sorted = paths.slice().sort((a, b) => {
-    if (a[key] < b[key]) {
-      return -1
-    }
-    if (a[key] > b[key]) {
-      return 1
-    }
-    return 0
-  })
-  const result = []
-
-  for (let i = 0; i < sorted.length; i++) {
-    const current = sorted[i]
-    const next = sorted[i + 1]
-    // If the next path doesn't start with the current + a dot, it's a leaf node
-    const nextKey = next?.[key]
-    const currentKey = current[key]
-    if (nextKey !== currentKey) {
-      if (!next || !next[key].startsWith(current[key] + ".")) {
-        result.push(current)
-      }
-    }
-  }
-
-  return result
-}
-
-export function slugifyString(str) {
-  return str
-    .toLowerCase()
-    .trim()
-    .replace(/[/\\?%@*:|"<>]/g, "-") // Replace invalid filename characters
-    .replace(/\s+/g, "-") // Replace whitespace with dashes
-    .replace(/-+/g, "-") // Collapse multiple dashes
-    .replace(/\./g, "-") // Replace dots with dashes
-    .replace(/^-+|-+$/g, "") // Trim leading/trailing dashes
-}
-
-export function getValueAtPath(obj, path) {
-  const parts = path.split(".")
-  let val = obj
-  for (const part of parts) {
-    val = val?.[part]
-    if (val === undefined) break
-  }
-  return val
-}
-
-export async function checkFilesExist(files, baseDir) {
-  const filesToCheck = Array.isArray(files) ? files : [files]
-  const fileCheckResults = await Promise.all(
-    filesToCheck.map(async file => {
-      const filePath = path.join(baseDir, file)
-      const exists = await fileExists(filePath)
-      return { filePath, exists }
-    }),
-  )
-  return fileCheckResults.reduce((sorted, { filePath, exists }) => {
-    return exists ? { ...sorted, present: [...sorted.present, filePath] } : { ...sorted, absent: [...sorted.absent, filePath] }
-  }, { present: [], absent: [] })
-
-}
-
-export function generateRandomId(length = 8) {
-  const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
-  let result = ""
-  for (let i = 0; i < length; i++) {
-    result += chars.charAt(Math.floor(Math.random() * chars.length))
-  }
-  return result
-}
-
-export async function writeFile(filePath, content) {
-  const fileDir = path.dirname(filePath)
-  await fs.mkdir(fileDir, { recursive: true })
-  return await fs.writeFile(filePath, content, {
-    encoding: "utf8",
-  })
-}

+ 79 - 0
src/util/file-system.js

@@ -0,0 +1,79 @@
+import fs from "node:fs/promises"
+import path from "path"
+import { glob } from "glob"
+
+export async function fileExists(filePath) {
+  try {
+    await fs.stat(filePath)
+    return true
+  } catch (err) {
+    if (err.code === "ENOENT") {
+      return false
+    }
+    throw err // re-throw other errors
+  }
+}
+
+export async function readDirectoryRecursively(dir, files = []) {
+  const exists = await fileExists(dir)
+  if (!exists) {
+    return files
+  }
+  const contents = await fs.readdir(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
+}
+
+// type InputConfig
+// {
+//  pattern: String | String[];
+//  ignore:  String | String[];
+// }
+export async function readFilesByGlob(globConfigs) {
+  const matchPromises = globConfigs.reduce(
+    async (existingMatches, globConfig) => {
+      const { pattern, ignore, dot } = {
+        dot: false,
+        ignore: [],
+        ...globConfig,
+      }
+      const matches = await glob(pattern, {
+        ignore,
+        dot,
+      })
+      return [...(await existingMatches), ...matches]
+    },
+    [],
+  )
+  const files = await matchPromises
+  return [...new Set(files)]
+}
+
+export async function checkFilesExist(files, baseDir) {
+  const filesToCheck = Array.isArray(files) ? files : [files]
+  const fileCheckResults = await Promise.all(
+    filesToCheck.map(async file => {
+      const filePath = path.join(baseDir, file)
+      const exists = await fileExists(filePath)
+      return { filePath, exists }
+    }),
+  )
+  return fileCheckResults.reduce((sorted, { filePath, exists }) => {
+    return exists ? { ...sorted, present: [...sorted.present, filePath] } : { ...sorted, absent: [...sorted.absent, filePath] }
+  }, { present: [], absent: [] })
+}
+
+export async function writeFile(filePath, content) {
+  const fileDir = path.dirname(filePath)
+  await fs.mkdir(fileDir, { recursive: true })
+  return await fs.writeFile(filePath, content, {
+    encoding: "utf8",
+  })
+}

+ 8 - 0
src/util/general-utils.js

@@ -0,0 +1,8 @@
+export function generateRandomId(length = 8) {
+  const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
+  let result = ""
+  for (let i = 0; i < length; i++) {
+    result += chars.charAt(Math.floor(Math.random() * chars.length))
+  }
+  return result
+}

+ 4 - 0
src/util/index.js

@@ -0,0 +1,4 @@
+export * from "./file-system.js"
+export * from "./path-utils.js"
+export * from "./object-utils.js"
+export * from "./general-utils.js"

+ 68 - 0
src/util/object-utils.js

@@ -0,0 +1,68 @@
+function stringifyPathPart(part) {
+  return typeof part === "symbol" ? part.toString() : String(part)
+}
+
+function trackPropertyAccessDeep(obj, path = [], accessed = new Set()) {
+  return new Proxy(obj, {
+    get(target, prop, receiver) {
+      const fullPath = [...path, prop].map(stringifyPathPart).join(".")
+      const value = Reflect.get(target, prop, receiver)
+
+      if (typeof target === "object" && Object.prototype.hasOwnProperty.call(target, prop)) {
+        accessed.add({ path: fullPath, value })
+      }
+
+      // Recursively wrap if value is an object and not null
+      if (value && typeof value === "object") {
+        return trackPropertyAccessDeep(value, [...path, prop], accessed)
+      }
+
+      return value
+    },
+  })
+}
+
+export function createTrackedObject(obj) {
+  const accessed = new Set()
+  const proxy = trackPropertyAccessDeep(obj, [], accessed)
+  return { proxy, accessed }
+}
+
+export function getDeepestPropertiesForKey(paths, key) {
+  // Sort paths to make prefix comparison easier
+  const sorted = paths.slice().sort((a, b) => {
+    if (a[key] < b[key]) {
+      return -1
+    }
+    if (a[key] > b[key]) {
+      return 1
+    }
+    return 0
+  })
+  const result = []
+
+  for (let i = 0; i < sorted.length; i++) {
+    const current = sorted[i]
+    const next = sorted[i + 1]
+    // If the next path doesn't start with the current + a dot, it's a leaf node
+    const nextKey = next?.[key]
+    const currentKey = current[key]
+    if (nextKey !== currentKey) {
+      if (!next || !next[key].startsWith(current[key] + ".")) {
+        result.push(current)
+      }
+    }
+  }
+
+  return result
+}
+
+export function getValueAtPath(obj, path) {
+  const parts = path.split(".")
+  let val = obj
+  for (const part of parts) {
+    val = val?.[part]
+    if (val === undefined) break
+  }
+  return val
+}

+ 68 - 0
src/util/path-utils.js

@@ -0,0 +1,68 @@
+import os from "node:os"
+import path from "path"
+import { fileExists } from "./file-system.js"
+
+export function resolvePath(unresolvedPath) {
+  return path.resolve(unresolvedPath.replace(/^~/, os.homedir()))
+}
+
+export async function firstFound(dirs, fileName) {
+  // Note: This function depends on fileExists from file-system.js
+  // Import it when using this function: import { fileExists } from './file-system.js'
+  for (const dir of dirs) {
+    const filePath = resolvePath(path.join(dir, fileName))
+    const exists = await fileExists(filePath)
+    if (exists) {
+      return filePath
+    }
+  }
+  return null
+}
+
+export function removeCwd(paths) {
+  const cwd = `${process.cwd()}/`
+  return paths.map(path => path.replace(cwd, ""))
+}
+
+export function removeBasePaths(baseDirs, fullPath) {
+  return baseDirs.reduce((cleanedPath, dir) => {
+    return cleanedPath.replace(dir, "")
+  }, fullPath)
+}
+
+export function replaceFileExtension(filePath, newExtension) {
+  if (!newExtension) {
+    return filePath
+  }
+  return `${stripFileExtension(filePath)}${newExtension}`
+}
+
+export function stripFileExtension(filePath) {
+  return path.join(
+    path.dirname(filePath),
+    path.basename(filePath, path.extname(filePath)),
+  )
+}
+
+export function getCleanPath(filePath, meta) {
+  return filePath.replace(meta.opts.runDir, "").replace(meta.opts.outDir, "/")
+}
+
+export function getHref(filePath, meta) {
+  const route = getCleanPath(filePath, meta)
+  if (route.includes("index.html")) {
+    return route.replace("index.html", "")
+  }
+  return route.replace(".html", "")
+}
+
+export function slugifyString(str) {
+  return str
+    .toLowerCase()
+    .trim()
+    .replace(/[/\\?%@*:|"<>]/g, "-") // Replace invalid filename characters
+    .replace(/\s+/g, "-") // Replace whitespace with dashes
+    .replace(/-+/g, "-") // Collapse multiple dashes
+    .replace(/\./g, "-") // Replace dots with dashes
+    .replace(/^-+|-+$/g, "") // Trim leading/trailing dashes
+}