docvars 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,25 +1,17 @@
1
1
  # docvars
2
2
 
3
- A CLI tool to replace `{{variables}}` in document templates with values from a YAML file.
4
-
5
- Supports any text-based files: Markdown, HTML, TXT, and more.
6
-
7
- ## Installation
3
+ ![docvars logo](./logo.jpeg)
8
4
 
9
- ```bash
10
- npm install -g docvars
11
- ```
5
+ [日本語版 README はこちら](./README_ja.md)
12
6
 
13
- Or use with npx:
7
+ A CLI tool to replace `{{variables}}` in document templates with values from a YAML file.
14
8
 
15
- ```bash
16
- npx docvars ./templates ./output
17
- ```
9
+ Supports any text-based files: Markdown, HTML, TXT, and more.
18
10
 
19
11
  ## Usage
20
12
 
21
13
  ```bash
22
- docvars <input> <output> [options]
14
+ npx docvars <input> <output> [options]
23
15
  ```
24
16
 
25
17
  ### Arguments
@@ -37,8 +29,8 @@ docvars <input> <output> [options]
37
29
  | `--only` | `-o` | `**/*` | Glob pattern to filter files (e.g. **/*.md) |
38
30
  | `--exclude` | `-e` | - | Glob pattern to exclude specific files |
39
31
  | `--watch` | `-w` | `false` | Watch for file changes and rebuild automatically |
40
- | `--rename-from` | `-r` | - | Variable name to rename from (use with --rename-to) |
41
- | `--rename-to` | `-t` | - | Variable name to rename to (use with --rename-from) |
32
+ | `--rename-from` | `-F` | - | Variable name to rename from (use with --rename-to) |
33
+ | `--rename-to` | `-T` | - | Variable name to rename to (use with --rename-from) |
42
34
  | `--list-vars` | `-l` | `false` | List all variables used in templates |
43
35
  | `--dry-run` | `-d` | `false` | Preview changes without writing files |
44
36
 
@@ -47,29 +39,29 @@ docvars <input> <output> [options]
47
39
  ### Basic usage
48
40
 
49
41
  ```bash
50
- docvars ./templates ./output
42
+ npx docvars ./templates ./output
51
43
  ```
52
44
 
53
45
  ### Custom variables file
54
46
 
55
47
  ```bash
56
- docvars ./templates ./output --vars production.yaml
48
+ npx docvars ./templates ./output --vars production.yaml
57
49
  ```
58
50
 
59
51
  ### Filter files
60
52
 
61
53
  ```bash
62
54
  # Process only markdown files
63
- docvars ./templates ./output --only "**/*.md"
55
+ npx docvars ./templates ./output --only "**/*.md"
64
56
 
65
57
  # Process multiple file types
66
- docvars ./templates ./output --only "**/*.{md,html,txt}"
58
+ npx docvars ./templates ./output --only "**/*.{md,html,txt}"
67
59
 
68
60
  # Process only files matching pattern
69
- docvars ./templates ./output --only "api-*.md"
61
+ npx docvars ./templates ./output --only "api-*.md"
70
62
 
71
63
  # Exclude files matching pattern
72
- docvars ./templates ./output --exclude "draft-*.md"
64
+ npx docvars ./templates ./output --exclude "draft-*.md"
73
65
  ```
74
66
 
75
67
  By default, all text files are processed (binary files like images are automatically excluded).
@@ -77,7 +69,7 @@ By default, all text files are processed (binary files like images are automatic
77
69
  ### Watch mode
78
70
 
79
71
  ```bash
80
- docvars ./templates ./output --watch
72
+ npx docvars ./templates ./output --watch
81
73
  ```
82
74
 
83
75
  Output:
@@ -111,26 +103,33 @@ Rename a variable across all template files and the variables YAML file:
111
103
 
112
104
  ```bash
113
105
  # Simple rename
114
- docvars ./templates ./output --rename-from "name" --rename-to "title"
106
+ npx docvars ./templates ./output --rename-from "name" --rename-to "title"
115
107
 
116
108
  # Rename nested variable
117
- docvars ./templates ./output --rename-from "database.host" --rename-to "db.host"
109
+ npx docvars ./templates ./output --rename-from "database.host" --rename-to "db.host"
110
+
111
+ # Prefix rename - renames all variables with matching prefix
112
+ # {{database}} → {{db}}
113
+ # {{database.host}} → {{db.host}}
114
+ # {{database.port}} → {{db.port}}
115
+ npx docvars ./templates ./output --rename-from "database" --rename-to "db"
118
116
  ```
119
117
 
120
118
  Output:
121
119
 
122
120
  ```
123
121
  ✏️ Rename complete
124
- database.host → db.host
122
+ database → db
125
123
 
126
124
  ┌────────────────┬───────────┐
127
125
  │ File │ Status │
128
126
  ├────────────────┼───────────┤
129
127
  │ variables.yaml │ ✓ updated │
130
128
  │ README.md │ ✓ updated │
129
+ │ config.md │ ✓ updated │
131
130
  └────────────────┴───────────┘
132
131
 
133
- Updated: 2 file(s)
132
+ Updated: 3 file(s)
134
133
  ```
135
134
 
136
135
  ### List variables
@@ -138,7 +137,7 @@ Updated: 2 file(s)
138
137
  Show all variables used in templates and their status:
139
138
 
140
139
  ```bash
141
- docvars ./templates ./output --list-vars
140
+ npx docvars ./templates ./output --list-vars
142
141
  ```
143
142
 
144
143
  Output:
@@ -165,7 +164,7 @@ Summary: 1 defined · 1 undefined · 1 unused
165
164
  Preview what files would be created or updated without actually writing them:
166
165
 
167
166
  ```bash
168
- docvars ./templates ./output --dry-run
167
+ npx docvars ./templates ./output --dry-run
169
168
  ```
170
169
 
171
170
  Output:
@@ -237,6 +236,36 @@ database:
237
236
  Database: localhost:5432
238
237
  ```
239
238
 
239
+ ### Array Variables
240
+
241
+ You can use arrays in your variables file and access them with index notation:
242
+
243
+ **Template:**
244
+ ```markdown
245
+ ## Features
246
+
247
+ 1. {{features.0}}
248
+ 2. {{features.1}}
249
+ 3. {{features.2}}
250
+ ```
251
+
252
+ **Variables (variables.yaml):**
253
+ ```yaml
254
+ features:
255
+ - User authentication
256
+ - Data analytics
257
+ - API integration
258
+ ```
259
+
260
+ **Output:**
261
+ ```markdown
262
+ ## Features
263
+
264
+ 1. User authentication
265
+ 2. Data analytics
266
+ 3. API integration
267
+ ```
268
+
240
269
  ## Error Handling
241
270
 
242
271
  | Case | Behavior |
@@ -6,11 +6,16 @@ export interface ListVariablesOptions {
6
6
  }
7
7
  export interface VariableUsage {
8
8
  name: string;
9
+ value?: string;
9
10
  files: string[];
10
11
  isDefined: boolean;
11
12
  }
13
+ export interface UnusedVariable {
14
+ name: string;
15
+ value: string;
16
+ }
12
17
  export interface ListVariablesResult {
13
18
  variables: VariableUsage[];
14
- unusedVariables: string[];
19
+ unusedVariables: UnusedVariable[];
15
20
  }
16
21
  export declare function listVariables(options: ListVariablesOptions): Promise<ListVariablesResult>;
@@ -34,6 +34,7 @@ export async function listVariables(options) {
34
34
  for (const [name, files] of variableUsageMap) {
35
35
  variables.push({
36
36
  name,
37
+ value: definedVars[name],
37
38
  files: Array.from(files).sort(),
38
39
  isDefined: definedVarNames.has(name),
39
40
  });
@@ -43,6 +44,7 @@ export async function listVariables(options) {
43
44
  // Find unused variables (defined but not used)
44
45
  const unusedVariables = Array.from(definedVarNames)
45
46
  .filter((name) => !usedVarNames.has(name))
46
- .sort();
47
+ .sort()
48
+ .map((name) => ({ name, value: definedVars[name] }));
47
49
  return { variables, unusedVariables };
48
50
  }
@@ -16,7 +16,8 @@ export async function renameVariable(options) {
16
16
  };
17
17
  // Rename in template files
18
18
  const templateFiles = await scanTemplates(input, { only, exclude });
19
- const pattern = new RegExp(`\\{\\{${escapeRegex(from)}(\\}\\}|\\|)`, "g");
19
+ // Match exact variable or prefix (e.g., "database" matches "{{database}}" and "{{database.host}}")
20
+ const pattern = new RegExp(`\\{\\{${escapeRegex(from)}(\\.|\\}\\}|\\|)`, "g");
20
21
  for (const file of templateFiles) {
21
22
  const content = readFileSync(file, "utf-8");
22
23
  const newContent = content.replace(pattern, `{{${to}$1`);
@@ -2,8 +2,8 @@ import { z } from "zod";
2
2
  declare const PrimitiveValueSchema: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>;
3
3
  type NestedValue = z.infer<typeof PrimitiveValueSchema> | {
4
4
  [key: string]: NestedValue;
5
- };
5
+ } | NestedValue[];
6
6
  export declare const VariablesSchema: z.ZodRecord<z.ZodString, z.ZodType<NestedValue, z.ZodTypeDef, NestedValue>>;
7
7
  export type Variables = Record<string, string>;
8
- export declare function flattenVariables(obj: Record<string, NestedValue>, prefix?: string): Variables;
8
+ export declare function flattenVariables(obj: Record<string, NestedValue> | NestedValue[], prefix?: string): Variables;
9
9
  export {};
@@ -1,16 +1,30 @@
1
1
  import { z } from "zod";
2
2
  const PrimitiveValueSchema = z.union([z.string(), z.number(), z.boolean()]);
3
- const NestedValueSchema = z.lazy(() => z.union([PrimitiveValueSchema, z.record(z.string(), NestedValueSchema)]));
3
+ const NestedValueSchema = z.lazy(() => z.union([PrimitiveValueSchema, z.array(NestedValueSchema), z.record(z.string(), NestedValueSchema)]));
4
4
  export const VariablesSchema = z.record(z.string(), NestedValueSchema);
5
5
  export function flattenVariables(obj, prefix = "") {
6
6
  const result = {};
7
- for (const [key, value] of Object.entries(obj)) {
8
- const fullKey = prefix ? `${prefix}.${key}` : key;
9
- if (typeof value === "object" && value !== null) {
10
- Object.assign(result, flattenVariables(value, fullKey));
7
+ if (Array.isArray(obj)) {
8
+ for (let i = 0; i < obj.length; i++) {
9
+ const value = obj[i];
10
+ const fullKey = prefix ? `${prefix}.${i}` : String(i);
11
+ if (typeof value === "object" && value !== null) {
12
+ Object.assign(result, flattenVariables(value, fullKey));
13
+ }
14
+ else {
15
+ result[fullKey] = String(value);
16
+ }
11
17
  }
12
- else {
13
- result[fullKey] = String(value);
18
+ }
19
+ else {
20
+ for (const [key, value] of Object.entries(obj)) {
21
+ const fullKey = prefix ? `${prefix}.${key}` : key;
22
+ if (typeof value === "object" && value !== null) {
23
+ Object.assign(result, flattenVariables(value, fullKey));
24
+ }
25
+ else {
26
+ result[fullKey] = String(value);
27
+ }
14
28
  }
15
29
  }
16
30
  return result;
@@ -3,6 +3,11 @@ import { resolve } from "node:path";
3
3
  import pc from "picocolors";
4
4
  import Table from "cli-table3";
5
5
  import { watch } from "chokidar";
6
+ function truncate(str, maxLength) {
7
+ if (str.length <= maxLength)
8
+ return str;
9
+ return str.slice(0, maxLength - 1) + "…";
10
+ }
6
11
  import { processTemplates } from "../../../application/use-cases/process-templates.js";
7
12
  import { renameVariable } from "../../../application/use-cases/rename-variable.js";
8
13
  import { listVariables } from "../../../application/use-cases/list-variables.js";
@@ -111,12 +116,12 @@ export const mainCommand = defineCommand({
111
116
  },
112
117
  "rename-from": {
113
118
  type: "string",
114
- alias: "r",
119
+ alias: "F",
115
120
  description: "Variable name to rename from (use with --rename-to)",
116
121
  },
117
122
  "rename-to": {
118
123
  type: "string",
119
- alias: "t",
124
+ alias: "T",
120
125
  description: "Variable name to rename to (use with --rename-from)",
121
126
  },
122
127
  "list-vars": {
@@ -212,22 +217,29 @@ export const mainCommand = defineCommand({
212
217
  }
213
218
  if (result.variables.length > 0) {
214
219
  const table = new Table({
215
- head: [pc.bold("Variable"), pc.bold("Status"), pc.bold("Used in")],
220
+ head: [pc.bold("Variable"), pc.bold("Value"), pc.bold("Used in")],
216
221
  style: { head: [], border: [] },
217
222
  wordWrap: true,
223
+ colWidths: [null, 30, null],
218
224
  });
219
225
  for (const v of result.variables) {
220
- const status = v.isDefined ? pc.green(" defined") : pc.red("✗ undefined");
226
+ const value = v.isDefined ? pc.green(truncate(v.value || "", 25)) : pc.red("✗ undefined");
221
227
  const files = v.files.map((f) => pc.gray(f)).join("\n");
222
- table.push([v.name, status, files]);
228
+ table.push([v.name, value, files]);
223
229
  }
224
230
  console.log(table.toString());
225
231
  }
226
232
  if (result.unusedVariables.length > 0) {
227
233
  console.log(pc.bold(pc.yellow("\n⚠ Unused variables (defined but not used):\n")));
228
- for (const name of result.unusedVariables) {
229
- console.log(pc.gray(` ${name}`));
234
+ const unusedTable = new Table({
235
+ head: [pc.bold("Variable"), pc.bold("Value")],
236
+ style: { head: [], border: [] },
237
+ colWidths: [null, 40],
238
+ });
239
+ for (const v of result.unusedVariables) {
240
+ unusedTable.push([pc.gray(v.name), pc.gray(truncate(v.value, 35))]);
230
241
  }
242
+ console.log(unusedTable.toString());
231
243
  }
232
244
  console.log();
233
245
  const defined = result.variables.filter((v) => v.isDefined).length;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "docvars",
3
3
  "author": "Shunta Toda",
4
- "version": "0.3.2",
4
+ "version": "0.4.0",
5
5
  "description": "Replace {{variables}} in document templates with YAML values",
6
6
  "type": "module",
7
7
  "main": "./dist/application/use-cases/process-templates.js",