yini-cli 1.1.1-beta → 1.2.1-beta

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,21 +1,26 @@
1
1
  # YINI-CLI
2
- **Command-line tool for validating, inspecting, and converting YINI configuration files to JSON.**
2
+ > **Readable configuration without indentation pitfalls or JSON verbosity.**
3
3
 
4
- *YINI is an INI-inspired configuration format designed for clarity and predictability. It supports nesting, comments, and a formally defined syntax, so configuration files stay easy to read and reason about as they grow.*
4
+ **The official terminal / command-line (CLI) tool for validating, inspecting, and converting YINI configuration files to JSON or JavaScript.**
5
+
6
+ *YINI is an INI-inspired and human-readable text format for representing structured information. It is designed to be clear, predictable, and easy for humans to read and write. It supports nesting, comments, and a formally defined syntax. It is suitable for configuration files, application settings, and general data-storage use cases.*
5
7
 
6
8
  [![npm version](https://img.shields.io/npm/v/yini-cli.svg)](https://www.npmjs.com/package/yini-cli) [![All Test Suites](https://github.com/YINI-lang/yini-cli/actions/workflows/run-all-tests.yml/badge.svg)](https://github.com/YINI-lang/yini-cli/actions/workflows/run-all-tests.yml) [![Regression Tests](https://github.com/YINI-lang/yini-cli/actions/workflows/run-regression-tests.yml/badge.svg)](https://github.com/YINI-lang/yini-cli/actions/workflows/run-regression-tests.yml) [![CLI Test CI](https://github.com/YINI-lang/yini-cli/actions/workflows/run-cli-test.yml/badge.svg)](https://github.com/YINI-lang/yini-cli/actions/workflows/run-cli-test.yml)
7
9
 
8
- This tool is useful if you work with human-edited configuration files and want predictable structure without indentation-based rules.
10
+ This tool is designed for teams and developers working with human-edited configuration files who require explicit structure without indentation-based semantics.
9
11
 
10
12
  ---
11
13
 
14
+ ## Example of YINI code
15
+ > A basic YINI configuration example, showing a section, nested section, comments:
16
+ ![YINI Config Example](./samples/basic.yini.png)
17
+ Source: [basic.yini](./samples/basic.yini)
18
+
12
19
  ## Quick Start
13
20
 
14
21
  ### Requirements
15
22
  YINI CLI requires Node.js **v20 or later**.
16
23
 
17
- (It has also been tested with Node.js v13+, but v20+ is recommended for best compatibility.)
18
-
19
24
  ### Installation
20
25
 
21
26
  1. **Install it globally from npm — (requires Node.js)**
@@ -29,7 +34,7 @@ YINI CLI requires Node.js **v20 or later**.
29
34
  ```bash
30
35
  yini --version
31
36
  ```
32
- Should print the version (e.g., 1.0.0).
37
+ This should print the installed version (e.g., 1.0.0).
33
38
 
34
39
  Then you may try:
35
40
  ```bash
@@ -53,21 +58,17 @@ YINI CLI requires Node.js **v20 or later**.
53
58
  ```
54
59
 
55
60
  Expected result, your CLI should output a parsed version of the config and output something similar to:
56
- ```js
61
+ ```json
57
62
  {
58
- App: {
59
- name: 'My App Title',
60
- version: '1.2.3',
61
- pageSize: 25,
62
- darkTheme: false
63
+ "App": {
64
+ "name": "My App Title",
65
+ "version": "1.2.3",
66
+ "pageSize": 25,
67
+ "darkTheme": false
63
68
  }
64
69
  }
65
70
  ```
66
71
 
67
- ⭐ If this was useful, [star it on GitHub](https://github.com/YINI-lang/yini-cli) — it helps a lot, thank you!
68
-
69
- ---
70
-
71
72
  ### Typical use cases
72
73
 
73
74
  - Validating configuration files during development or CI.
@@ -76,44 +77,55 @@ YINI CLI requires Node.js **v20 or later**.
76
77
 
77
78
  ---
78
79
 
80
+ ## Example 2
81
+ > A real-world YINI configuration example, showing sections, nesting, comments, and multiple data types:
82
+ ![YINI Config Example](./samples/config.yini.png)
83
+ Source: [config.yini](./samples/config.yini)
84
+
85
+ ---
86
+
79
87
  ## 🙋‍♀️ Why YINI?
80
- - **Indentation-independent structure:** The YINI config format is indentation-independent, meaning any space or tab never changes meaning.
81
- - **Explicit nesting:** It uses clear header markers (`^`, `^^`, `^^^`) to define hierarchy (like in Markdown), without long dotted keys.
88
+ - **Indentation-independent structure:** YINI is indentation-independent whitespace never alters structural meaning.
89
+ - **Explicit nesting & refactoring safety:** It uses clear header markers (`^`, `^^`, `^^^`) to define hierarchy (like in Markdown), without long dotted keys.
82
90
  - **Multiple data types:** Supports boolean literals (`true` / `false`, `Yes` / `No`, etc), numbers, arrays (lists), and JS-style objects natively, with explicit string syntax.
83
91
  - **Comments support:** YINI supports multiple comment styles (`#`, `//`, `/* ... */`, and `;`) allowing one to document config directly in the file.
84
92
  - **Predictable parsing rules:** Well-defined rules with optional strict and lenient modes, for different use-requirements.
85
93
 
86
94
  ---
87
95
 
88
- ## Usage of command `yini`
96
+ ## Usage
97
+
98
+ ### Quick Examples
99
+
100
+ ```bash
101
+ yini parse config.yini
102
+ ```
103
+ → Parse and print formatted JSON (default).
104
+
105
+ ```bash
106
+ yini parse config.yini --compact
107
+ ```
108
+ → Output compact JSON (no whitespace).
109
+
110
+ ```bash
111
+ yini parse config.yini --js
112
+ ```
113
+ → Output as JavaScript-style object.
114
+
115
+ ```bash
116
+ yini parse config.yini -o out.json
117
+ ```
118
+ → Write formatted JSON to a file.
119
+
120
+ ```bash
121
+ yini validate --strict config.yini
122
+ ```
123
+ → Validate using strict mode.
124
+
125
+ For help with a specific command:
89
126
 
90
127
  ```bash
91
- Usage: yini [options] [command]
92
-
93
- CLI for parsing and validating YINI config files.
94
-
95
- Options:
96
- -v, --version Output the version number.
97
- -i, --info Show extended information (details, links, etc.).
98
- -s, --strict Enable strict parsing mode.
99
- -f, --force Continue parsing even if errors occur.
100
- -q, --quiet Reduce output (show only errors).
101
- --silent Suppress all output (even errors, exit code only).
102
- -h, --help Display help for command.
103
-
104
- Commands:
105
- parse [options] <file> Parse a YINI file (*.yini) and print the result.
106
- validate [options] <file> Checks if the file can be parsed as valid YINI.
107
- info Deprecated: Use `yini --info` or `yini -i` instead.
108
- help [command] Display help for command.
109
-
110
- Examples:
111
- $ yini parse config.yini
112
- $ yini validate --strict config.yini
113
- $ yini parse config.yini --pretty --output out.json
114
-
115
- For help with a specific command, use -h or --help. For example:
116
- $ yini validate --help
128
+ yini parse --help
117
129
  ```
118
130
 
119
131
  ---
@@ -169,35 +181,56 @@ Here's a small example showing YINI structure and comments:
169
181
 
170
182
  That's it!
171
183
 
172
- - ▶️ Link to [examples/](https://github.com/YINI-lang/yini-parser-typescript/tree/main/examples) files.
184
+ - ▶️ See more on [YINI Homepage](https://yini-lang.org/?utm_source=github&utm_medium=referral&utm_campaign=yini_cli&utm_content=readme_middle).
173
185
  - ▶️ Link to [Demo Apps](https://github.com/YINI-lang/yini-demo-apps/tree/main) with complete basic usage.
174
186
 
175
187
  ---
176
188
 
177
189
  ## 📤 Output Modes for `yini parse`
178
190
 
179
- The `parse` command supports multiple output styles:
191
+ The `parse` command supports multiple output formats:
180
192
 
181
- | Command Example | Output Style | Description |
182
- |----------------------------------------------------|----------------------|------------------------------------------------------------------------------|
183
- | `yini parse config.yini` | JS-style object | Uses Node’s `util.inspect` human-readable, shows types, nesting, etc. |
184
- | `yini parse config.yini --pretty` | Pretty JSON | Formatted and indented with `JSON.stringify(obj, null, 4)`. |
185
- | `yini parse config.yini --json` | Compact JSON | Compact and machine-friendly `JSON.stringify(obj)`. |
186
- | `yini parse config.yini --output out.txt` | File (JS-style) | Default style, written to specified file. |
187
- | `yini parse config.yini --pretty --output out.json`| File (Pretty JSON) | Formatted JSON written to file. |
193
+ | Command Example | Output Format | Description |
194
+ |------------------------------------------|----------------------|------------|
195
+ | `yini parse config.yini` | Pretty JSON (default) | Formatted JSON with indentation (4 spaces). |
196
+ | `yini parse config.yini --json` | Pretty JSON | Explicit pretty JSON output. |
197
+ | `yini parse config.yini --compact` | Compact JSON | Minified JSON (no whitespace). |
198
+ | `yini parse config.yini --js` | JavaScript object | JavaScript-style object (unquoted keys, single quotes). |
199
+ | `yini parse config.yini -o out.json` | File output | Writes formatted JSON to file (default format). |
188
200
 
201
+ >💡 `--js` and `--compact` are mutually exclusive.
189
202
  >💡 Tip: You can combine --output with any style flag to control both formatting and destination.
190
203
 
191
204
  ---
192
205
 
206
+ ## 📁 Output File Handling
207
+
208
+ When using `-o, --output <file>`, YINI CLI applies safe write rules:
209
+
210
+ | Scenario | Result |
211
+ |----------|--------|
212
+ | File does not exist | File is written |
213
+ | File exists and is **older** than the input YINI file | File is overwritten |
214
+ | File exists and is **newer** than the input YINI file | Command fails |
215
+ | `--overwrite` is used | File is always overwritten |
216
+ | `--no-overwrite` is used | Command fails if file exists |
217
+
218
+ This prevents accidental overwriting of newer generated files.
219
+
220
+ Use:
221
+ `--overwrite` to force replacement.
222
+
223
+ ---
224
+
193
225
  ## 🛠 Roadmap
194
226
  Areas of planned and possible future expansion:
195
227
 
196
228
  1. **Improve existing commands** — Continued functionality improvements, better diagnostics, and expanded QA for `parse` and `validate` and their options.
197
- 2. Command `format`: Pretty-print or normalize a `.yini` file.
198
- 3. Command `lint`: Stricter stylistic checks (like `validate`, but opinionated).
199
- 4. Command `diff`: Compare two YINI files and show structural/config differences.
200
- 5. Command `convert`: Convert a `JSON` or `XML` file into YINI format.
229
+ 2. Command `convert`: Batch convert YINI files to JSON or JavaScript.
230
+ 3. Command `format`: Pretty-print or normalize a `.yini` file.
231
+ 4. Command `lint`: Stricter stylistic checks (like `validate`, but opinionated).
232
+ 5. Command `diff`: Compare two YINI files and show structural/config differences.
233
+ 6. Import JSON or XML into YINI format.
201
234
 
202
235
  ---
203
236
 
@@ -206,13 +239,12 @@ Areas of planned and possible future expansion:
206
239
  *Install and view package details.*
207
240
 
208
241
  - ➡️ [YINI Project](https://github.com/YINI-lang)
209
- *YINI home on gitHub.*
242
+ *YINI home on GitHub.*
210
243
 
211
244
  ---
212
245
 
213
246
  ## Contribution & Involvement
214
- Interested in contributing or trying ideas?
215
- Issues, feedback, and experiments are welcome — even small ones.
247
+ Contributions, issues, and feedback are welcome. Even small improvements or suggestions are appreciated.
216
248
 
217
249
  ---
218
250
 
@@ -223,9 +255,7 @@ In this project on GitHub, the `libs` directory contains third party software an
223
255
 
224
256
  ---
225
257
 
226
- If you found this useful, a GitHub star helps the project a lot ⭐
227
-
228
258
  **^YINI ≡**
229
259
  > YINI — Clear, Structured Configuration Files.
230
260
 
231
- [yini-lang.org](https://yini-lang.org) · [YINI on GitHub](https://github.com/YINI-lang)
261
+ [yini-lang.org](https://yini-lang.org/?utm_source=github&utm_medium=referral&utm_campaign=yini_cli&utm_content=readme_footer) · [YINI on GitHub](https://github.com/YINI-lang)
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function enableHelpAll(program: Command): void;
@@ -0,0 +1,23 @@
1
+ export function enableHelpAll(program) {
2
+ const originalFormatHelp = program.createHelp().formatHelp;
3
+ program.configureHelp({
4
+ formatHelp: (cmd, helper) => {
5
+ // Call the original formatter (not the overridden one)
6
+ let output = originalFormatHelp.call(helper, cmd, helper);
7
+ // Only expand the top-level help
8
+ if (cmd !== program)
9
+ return output;
10
+ for (const sub of program.commands) {
11
+ output += '\n\n';
12
+ output +=
13
+ '--------------------------------------------------------\n';
14
+ output += `* Command: ${sub.name()}\n`;
15
+ output +=
16
+ '--------------------------------------------------------\n';
17
+ output += originalFormatHelp.call(helper, sub, helper);
18
+ // output += '--------------------------------------------------------\n'
19
+ }
20
+ return output;
21
+ },
22
+ });
23
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * NOTE: Keep contributors in acknowledged in README or --credits.
3
+ */
4
+ export declare const printInfo: () => void;
@@ -0,0 +1,44 @@
1
+ import { createRequire } from 'module';
2
+ import { removeSuffix, toColRow } from '../utils/string.js';
3
+ // import pkg from '../../package.json' with { type: 'json' } // NOTE: Must use { type: 'json' } when in ESM mode.
4
+ const require = createRequire(import.meta.url);
5
+ const pkg = require('../../package.json');
6
+ /*
7
+ YINI CLI — Environment Information
8
+ ──────────────────────────────────
9
+
10
+ CLI Version: 1.1.1-beta
11
+ Parser Version: 1.3.2-beta
12
+
13
+ if verbose(
14
+ Node.js: v20.18.0
15
+ Platform: win32 x64
16
+ Working Directory: D:\Sources\YINI-lang-WORK\yini-cli
17
+ )
18
+
19
+ Author: Marko K. Seppänen
20
+ License: Apache-2.0
21
+
22
+ Repository: https://github.com/YINI-lang/yini-cli
23
+ Homepage: https://yini-lang.org
24
+
25
+ ---
26
+
27
+ */
28
+ /**
29
+ * NOTE: Keep contributors in acknowledged in README or --credits.
30
+ */
31
+ export const printInfo = () => {
32
+ const size = 16;
33
+ console.log();
34
+ console.log('YINI CLI — Environment Information');
35
+ console.log('==================================');
36
+ console.log();
37
+ console.log(toColRow(size, 'CLI Version:', pkg.version));
38
+ console.log(toColRow(size, 'Parser Version:', pkg.dependencies['yini-parser'].replace('^', '')));
39
+ console.log(toColRow(size, 'Author:', pkg.author));
40
+ console.log(toColRow(size, 'License:', pkg.license));
41
+ console.log();
42
+ console.log(toColRow(size, 'Repository:', 'https://github.com/YINI-lang/yini-cli'));
43
+ console.log(toColRow(size, 'Homepage:', removeSuffix(pkg.homepage, '/')));
44
+ };
@@ -1,7 +1,14 @@
1
1
  import { IGlobalOptions } from '../types.js';
2
+ /**
3
+ * @deprecated pretty Deprecated since 2026 Feb! Use `json` instead.
4
+ */
2
5
  export interface IParseCommandOptions extends IGlobalOptions {
3
6
  pretty?: boolean;
4
7
  json?: boolean;
8
+ compact?: boolean;
9
+ js?: boolean;
5
10
  output?: string;
11
+ bestEffort?: boolean;
12
+ overwrite?: boolean;
6
13
  }
7
14
  export declare const parseFile: (file: string, commandOptions: IParseCommandOptions) => void;
@@ -1,72 +1,131 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import util from 'util';
4
3
  import YINI from 'yini-parser';
5
- import { debugPrint, printObject, toPrettyJSON } from '../utils/print.js';
4
+ import { debugPrint, printObject, toPrettyJS, toPrettyJSON, } from '../utils/print.js';
6
5
  // -------------------------------------------------------------------------
6
+ /*
7
+ TODO / SHOULD-DO:
8
+
9
+ yini parse <file> [options]
10
+
11
+ Options
12
+ -------
13
+
14
+ Parsing mode:
15
+ --strict
16
+ --lenient (default)
17
+
18
+ Output format:
19
+ --to <json|json-compact> (default, --to json)
20
+ --json (alias for --to json, default)
21
+ --compact (alias for --to json-compact)
22
+
23
+ Output handling:
24
+ -o, --output <file> (default) No overwrite if dest is more recent than source file (override with --overwrite)
25
+ --overwrite
26
+ --no-overwrite
27
+
28
+ Execution control:
29
+ --fail-fast
30
+ --best-effort = ignore-errors within a file, attempt recovery and still emit outp
31
+ --No for parse, --keep-going = continue to the next file when one fails
32
+ --max-errors <n>
33
+ --verbose
34
+ --checks (default)
35
+ --no-checks
36
+
37
+ Policy control (advanced):
38
+ --duplicates-policy <error|warn|allow>
39
+ --reserved-policy <error|warn|allow>
40
+ */
7
41
  export const parseFile = (file, commandOptions) => {
8
42
  const outputFile = commandOptions.output || '';
9
- const isStrictMode = !!commandOptions.strict;
10
- let outputStyle = 'JS-style';
43
+ // const isStrictMode = !!commandOptions.strict
44
+ const outputStyle = resolveOutputStyle(commandOptions);
11
45
  debugPrint('file = ' + file);
12
46
  debugPrint('output = ' + commandOptions.output);
13
47
  debugPrint('commandOptions:');
14
48
  printObject(commandOptions);
15
- if (commandOptions.pretty) {
16
- outputStyle = 'Pretty-JSON';
49
+ doParseFile(file, commandOptions, outputStyle, outputFile);
50
+ };
51
+ const resolveOutputStyle = (options) => {
52
+ if (options.js && options.compact) {
53
+ throw new Error('--js and --compact cannot be combined.');
17
54
  }
18
- else if (commandOptions.json) {
19
- outputStyle = 'JSON-compact';
55
+ if (options.compact)
56
+ return 'JSON-compact';
57
+ if (options.js)
58
+ return 'JS-style';
59
+ if (options.pretty) {
60
+ console.warn('Warning: --pretty is deprecated. Use --json instead.');
20
61
  }
21
- else {
22
- outputStyle = 'JS-style';
62
+ return 'Pretty-JSON';
63
+ };
64
+ const renderOutput = (parsed, style) => {
65
+ switch (style) {
66
+ case 'JS-style':
67
+ return toPrettyJS(parsed);
68
+ case 'JSON-compact':
69
+ return JSON.stringify(parsed);
70
+ case 'Pretty-JSON':
71
+ default:
72
+ return toPrettyJSON(parsed);
23
73
  }
24
- doParseFile(file, commandOptions, outputStyle, outputFile);
25
74
  };
26
75
  const doParseFile = (file, commandOptions, outputStyle, outputFile = '') => {
27
76
  // let strictMode = !!commandOptions.strict
28
- let preferredFailLevel = 'auto';
77
+ let preferredFailLevel = commandOptions.bestEffort
78
+ ? 'ignore-errors'
79
+ : 'auto';
29
80
  let includeMetaData = false;
30
81
  debugPrint('File = ' + file);
31
82
  debugPrint('outputStyle = ' + outputStyle);
32
83
  const parseOptions = {
33
84
  strictMode: commandOptions.strict ?? false,
34
- // failLevel: 'errors',
35
85
  failLevel: preferredFailLevel,
36
- // failLevel: 'ignore-errors',
37
86
  includeMetadata: includeMetaData,
38
87
  };
39
- // If --force then override fail-level.
40
- if (commandOptions.force) {
88
+ // If --best-effort then override fail-level.
89
+ if (commandOptions.bestEffort) {
41
90
  parseOptions.failLevel = 'ignore-errors';
42
91
  }
43
92
  try {
44
93
  const parsed = YINI.parseFile(file, parseOptions);
45
- let output = '';
46
- switch (outputStyle) {
47
- case 'Pretty-JSON':
48
- output = toPrettyJSON(parsed);
49
- break;
50
- case 'Console.log':
51
- output = '<todo>';
52
- break;
53
- case 'JSON-compact':
54
- output = JSON.stringify(parsed);
55
- break;
56
- default:
57
- output = util.inspect(parsed, { depth: null, colors: false });
58
- }
94
+ const output = renderOutput(parsed, outputStyle);
59
95
  if (outputFile) {
96
+ const resolved = path.resolve(outputFile);
97
+ enforceWritePolicy(file, resolved, commandOptions.overwrite);
60
98
  // Write JSON output to file instead of stdout.
61
- fs.writeFileSync(path.resolve(outputFile), output, 'utf-8');
62
- console.log(`Output written to file: "${outputFile}"`);
99
+ fs.writeFileSync(resolved, output, 'utf-8');
100
+ if (commandOptions.verbose) {
101
+ console.log(`Output written to file: "${outputFile}"`);
102
+ }
63
103
  }
64
104
  else {
65
105
  console.log(output);
66
106
  }
67
107
  }
68
108
  catch (err) {
69
- console.error(`Error: ${err.message}`);
109
+ const message = err instanceof Error ? err.message : String(err);
110
+ console.error(`Error: ${message}`);
70
111
  process.exit(1);
71
112
  }
72
113
  };
114
+ const enforceWritePolicy = (srcPath, destPath, overwrite) => {
115
+ if (!fs.existsSync(destPath)) {
116
+ return; // File does not exist, OK to write.
117
+ }
118
+ const srcStat = fs.statSync(srcPath);
119
+ const destStat = fs.statSync(destPath);
120
+ const destIsNewer = destStat.mtimeMs >= srcStat.mtimeMs;
121
+ if (overwrite === true) {
122
+ return; // Explicit overwrite, OK.
123
+ }
124
+ if (overwrite === false) {
125
+ throw new Error(`File "${destPath}" already exists. Overwriting disabled (--no-overwrite).`);
126
+ }
127
+ // Default policy (overwrite undefined).
128
+ if (destIsNewer) {
129
+ throw new Error(`Destination file "${destPath}" is newer than source. Use --overwrite to force.`);
130
+ }
131
+ };
@@ -1,6 +1,5 @@
1
1
  import { IGlobalOptions } from '../types.js';
2
2
  export interface IValidateCommandOptions extends IGlobalOptions {
3
- report?: boolean;
4
- details?: boolean;
3
+ stats?: boolean;
5
4
  }
6
5
  export declare const validateFile: (file: string, commandOptions?: IValidateCommandOptions) => never;