mirror of
https://github.com/NohamR/pdfy.git
synced 2026-10-10 18:29:55 +00:00
Add config file support
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ extension/Reader-View-Chrome-Web-Store.zip
|
|||||||
/output
|
/output
|
||||||
config/reader-view-preferences.json
|
config/reader-view-preferences.json
|
||||||
config/rules.css
|
config/rules.css
|
||||||
|
.pdfyrc
|
||||||
|
|||||||
4
.pdfyrc.example
Normal file
4
.pdfyrc.example
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"theme": "light",
|
||||||
|
"css": "config/rules.css"
|
||||||
|
}
|
||||||
32
README.md
32
README.md
@@ -35,18 +35,50 @@ Arguments:
|
|||||||
Options:
|
Options:
|
||||||
-t, --theme <theme> Reader View theme (light, dark, sepia, groove-dark, solarized-light, solarized-dark, nord-light, nord-dark)
|
-t, --theme <theme> Reader View theme (light, dark, sepia, groove-dark, solarized-light, solarized-dark, nord-light, nord-dark)
|
||||||
-c, --css <path> path to custom CSS file
|
-c, --css <path> path to custom CSS file
|
||||||
|
--config <path> path to config file (.pdfyrc, .pdfyrc.json, pdfy.config.js)
|
||||||
-p, --prefs <path> path to extension preferences JSON
|
-p, --prefs <path> path to extension preferences JSON
|
||||||
-o, --output <path> output file path (default: ./output/<title>.pdf)
|
-o, --output <path> output file path (default: ./output/<title>.pdf)
|
||||||
-l, --log-level <level> logging level (fatal, error, warn, info, debug)
|
-l, --log-level <level> logging level (fatal, error, warn, info, debug)
|
||||||
-h, --help display help for command
|
-h, --help display help for command
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
pdfy automatically discovers a config file in the current directory. Supported formats:
|
||||||
|
|
||||||
|
- `.pdfyrc` (JSON)
|
||||||
|
- `.pdfyrc.json` (JSON)
|
||||||
|
- `pdfy.config.js` (ESM module exporting a default object)
|
||||||
|
|
||||||
|
Example `.pdfyrc`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"theme": "dark",
|
||||||
|
"css": "config/rules.css",
|
||||||
|
"prefs": "config/reader-view-preferences.json",
|
||||||
|
"output": "./output/article.pdf"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example `pdfy.config.js`:
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
theme: 'dark',
|
||||||
|
css: 'config/rules.css',
|
||||||
|
prefs: 'config/reader-view-preferences.json',
|
||||||
|
output: './output/article.pdf'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
CLI flags always override config file values. Use `--config <path>` to specify a custom config file location.
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node src/index.js https://example.com/article
|
node src/index.js https://example.com/article
|
||||||
node src/index.js https://example.com/article --theme dark --css config/rules.css
|
node src/index.js https://example.com/article --theme dark --css config/rules.css
|
||||||
node src/index.js https://example.com/article --prefs config/reader-view-preferences.json
|
node src/index.js https://example.com/article --prefs config/reader-view-preferences.json
|
||||||
|
node src/index.js https://example.com/article --config .pdfyrc
|
||||||
npm start -- https://example.com/article --theme dark --css config/rules.css
|
npm start -- https://example.com/article --theme dark --css config/rules.css
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ program
|
|||||||
`Reader View theme (${VALID_THEMES.join(', ')})`
|
`Reader View theme (${VALID_THEMES.join(', ')})`
|
||||||
)
|
)
|
||||||
.option('-c, --css <path>', 'path to custom CSS file')
|
.option('-c, --css <path>', 'path to custom CSS file')
|
||||||
|
.option('--config <path>', 'path to config file (.pdfyrc, .pdfyrc.json, pdfy.config.js)')
|
||||||
.option('-p, --prefs <path>', 'path to extension preferences JSON')
|
.option('-p, --prefs <path>', 'path to extension preferences JSON')
|
||||||
.option(
|
.option(
|
||||||
'-o, --output <path>',
|
'-o, --output <path>',
|
||||||
@@ -50,7 +51,8 @@ export function parseArgs () {
|
|||||||
theme: opts.theme || null,
|
theme: opts.theme || null,
|
||||||
css: opts.css || null,
|
css: opts.css || null,
|
||||||
prefs: opts.prefs || null,
|
prefs: opts.prefs || null,
|
||||||
output: opts.output || null
|
output: opts.output || null,
|
||||||
|
config: opts.config || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,54 @@
|
|||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
import { pathToFileURL } from 'node:url'
|
||||||
import logger from './logger.js'
|
import logger from './logger.js'
|
||||||
|
|
||||||
|
const CONFIG_FILE_NAMES = ['.pdfyrc', '.pdfyrc.json', 'pdfy.config.js']
|
||||||
|
|
||||||
|
function normalizeConfig (config) {
|
||||||
|
const result = {}
|
||||||
|
if (config.theme) result.theme = config.theme
|
||||||
|
if (config.css) result.css = config.css
|
||||||
|
if (config.prefs) result.prefs = config.prefs
|
||||||
|
if (config.output) result.output = config.output
|
||||||
|
if (config['log-level']) result.logLevel = config['log-level']
|
||||||
|
if (config.logLevel) result.logLevel = config.logLevel
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function findConfigFile () {
|
||||||
|
for (const name of CONFIG_FILE_NAMES) {
|
||||||
|
const fullPath = path.resolve(name)
|
||||||
|
if (fs.existsSync(fullPath)) return fullPath
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function parseConfigFile (filePath) {
|
||||||
|
if (filePath.endsWith('.js')) {
|
||||||
|
const mod = await import(pathToFileURL(filePath).href)
|
||||||
|
return normalizeConfig(mod.default || {})
|
||||||
|
}
|
||||||
|
const raw = fs.readFileSync(filePath, 'utf-8')
|
||||||
|
return normalizeConfig(JSON.parse(raw))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadConfigFile (configPath) {
|
||||||
|
const target = configPath
|
||||||
|
? path.resolve(configPath)
|
||||||
|
: findConfigFile()
|
||||||
|
|
||||||
|
if (!target || !fs.existsSync(target)) return {}
|
||||||
|
|
||||||
|
logger.debug(`Config loaded from: "${target}"`)
|
||||||
|
try {
|
||||||
|
return await parseConfigFile(target)
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn(`Could not parse config file "${target}": ${err.message}`)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function loadPreferences (prefsPath) {
|
export function loadPreferences (prefsPath) {
|
||||||
if (!prefsPath || !fs.existsSync(prefsPath)) return {}
|
if (!prefsPath || !fs.existsSync(prefsPath)) return {}
|
||||||
try {
|
try {
|
||||||
|
|||||||
24
src/index.js
24
src/index.js
@@ -3,7 +3,7 @@ import process from 'node:process'
|
|||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import readline from 'node:readline/promises'
|
import readline from 'node:readline/promises'
|
||||||
import { parseArgs, VALID_THEMES } from './cli.js'
|
import { parseArgs, VALID_THEMES } from './cli.js'
|
||||||
import { loadPreferences, readCustomCss } from './config.js'
|
import { loadConfigFile, loadPreferences, readCustomCss } from './config.js'
|
||||||
import logger from './logger.js'
|
import logger from './logger.js'
|
||||||
import {
|
import {
|
||||||
checkExtension,
|
checkExtension,
|
||||||
@@ -43,12 +43,18 @@ async function main () {
|
|||||||
|
|
||||||
await checkExtension()
|
await checkExtension()
|
||||||
|
|
||||||
|
const config = await loadConfigFile(opts.config)
|
||||||
|
|
||||||
|
const prefsPath = opts.prefs || config.prefs || null
|
||||||
|
const cssPath = opts.css || config.css || null
|
||||||
|
const outputPath = opts.output || config.output || null
|
||||||
|
|
||||||
let preferences = {}
|
let preferences = {}
|
||||||
if (opts.prefs) {
|
if (prefsPath) {
|
||||||
preferences = loadPreferences(path.resolve(opts.prefs))
|
preferences = loadPreferences(path.resolve(prefsPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
const theme = opts.theme || preferences.mode || 'light'
|
const theme = opts.theme || config.theme || preferences.mode || 'light'
|
||||||
if (!VALID_THEMES.includes(theme)) {
|
if (!VALID_THEMES.includes(theme)) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Invalid theme "${theme}". Valid themes: ${VALID_THEMES.join(', ')}`
|
`Invalid theme "${theme}". Valid themes: ${VALID_THEMES.join(', ')}`
|
||||||
@@ -59,11 +65,11 @@ async function main () {
|
|||||||
logger.info(`Theme: ${theme}`)
|
logger.info(`Theme: ${theme}`)
|
||||||
|
|
||||||
let customCss = null
|
let customCss = null
|
||||||
if (opts.css) {
|
if (cssPath) {
|
||||||
customCss = readCustomCss(path.resolve(opts.css))
|
customCss = readCustomCss(path.resolve(cssPath))
|
||||||
if (customCss !== null) {
|
if (customCss !== null) {
|
||||||
preferences['user-css'] = customCss
|
preferences['user-css'] = customCss
|
||||||
logger.info(`Custom CSS loaded: "${path.resolve(opts.css)}"`)
|
logger.info(`Custom CSS loaded: "${path.resolve(cssPath)}"`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,8 +130,8 @@ async function main () {
|
|||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||||
|
|
||||||
const title = await getArticleTitle(readerPage)
|
const title = await getArticleTitle(readerPage)
|
||||||
const outputFile = opts.output
|
const outputFile = outputPath
|
||||||
? resolveOutputPath(opts.output)
|
? resolveOutputPath(outputPath)
|
||||||
: getOutputFilename(title)
|
: getOutputFilename(title)
|
||||||
|
|
||||||
let contentHtml = await extractRenderedContent(readerPage)
|
let contentHtml = await extractRenderedContent(readerPage)
|
||||||
|
|||||||
Reference in New Issue
Block a user