properties-comparator 1.0.2 → 1.0.4

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/DOCUMENTATION.md CHANGED
@@ -1,13 +1,13 @@
1
-
2
- # Node.js Properties File Comparison Utility
1
+ # Node.js YAML and Properties File Comparison Utility
3
2
 
4
3
  ## Overview
5
4
 
6
- This utility provides functionality to parse and compare properties files in the format of key-value pairs. It reads properties files, compares the values for each key across multiple files, and logs the results.
5
+ This utility parses and compares **.properties** and **.yml or .yaml (YAML)** files. It reads each file as key-value pairs, compares the values for each key across multiple files, and logs the results.
7
6
 
8
7
  ### Features:
9
- - Parse properties files into key-value objects.
10
- - Compare key values across multiple properties files.
8
+ - Parse **.properties** files into key-value objects.
9
+ - Parse **.yml or .yaml** (YAML) files into flattened key-value objects (supports nested keys).
10
+ - Compare key values across multiple files (both **.properties** and **.yml or .yaml**).
11
11
  - Log detailed comparison results, including mismatches.
12
12
 
13
13
  ---
@@ -16,7 +16,7 @@ This utility provides functionality to parse and compare properties files in the
16
16
 
17
17
  ### Prerequisites
18
18
  - Node.js installed on your system.
19
- - Properties files in a valid key-value format.
19
+ - Files in valid .properties or .yaml or .yml format.
20
20
 
21
21
  ---
22
22
 
@@ -53,23 +53,11 @@ Parses a properties file and returns an object representation of the key-value p
53
53
 
54
54
  #### Example:
55
55
  ```javascript
56
- // Assuming properties file contains:
57
- // key1=value1
58
- // key2=value2
59
-
60
56
  const properties = parsePropertiesFile('/path/to/properties/file');
61
57
  console.log(properties);
62
58
  // Output: { key1: 'value1', key2: 'value2' }
63
59
  ```
64
60
 
65
- #### Implementation:
66
- - Reads the file content using `fs.readFileSync`.
67
- - Splits lines by `
68
- ` or `
69
- `.
70
- - Filters out empty lines and lines starting with `#`.
71
- - Processes valid lines into key-value pairs.
72
-
73
61
  ---
74
62
 
75
63
  ### `compareProperties(filePaths)`
@@ -82,15 +70,9 @@ Compares properties across multiple properties files and logs the results.
82
70
  #### Returns:
83
71
  - None. Outputs comparison results to the console.
84
72
 
85
- #### Behavior:
86
- - Parses each properties file into an object.
87
- - Collects all unique keys across files.
88
- - Compares values for each key across all files.
89
- - Logs whether values match or are mismatched. For mismatches, displays values for each file.
90
-
91
73
  #### Example:
92
74
  ```bash
93
- node script.js file1.properties file2.properties file3.properties
75
+ node script.js file1.properties file2.yaml file3.yml
94
76
  ```
95
77
 
96
78
  #### Output Example:
@@ -121,6 +103,31 @@ The script expects file paths as command-line arguments:
121
103
 
122
104
  ---
123
105
 
106
+ ### Additional Functions
107
+
108
+ #### `parseFile(filePath)`
109
+ Detects file extension and parses `.properties`, `.yaml`, or `.yml` files. Returns an object with flattened key-value pairs or `{}` if unsupported.
110
+
111
+ #### `parseYamlFile(filePath)`
112
+ Parses a `.yml` or `.yaml` file into a flat key-value map. Returns an object with dot-notation keys for nested values.
113
+
114
+ #### `compareFileData(filePaths)`
115
+ Internally compares parsed data from multiple files. Returns an object with `mismatchCount` and detailed info for each key.
116
+
117
+ #### `checkIfAllValuesMatch(filePaths)`
118
+ Checks if all keys match across all provided files. Returns a boolean.
119
+
120
+ #### `getMismatchFields(filePaths)`
121
+ Returns an array of keys whose values differ across files.
122
+
123
+ #### `compareFiles(filePaths)`
124
+ Logs detailed comparison of key-value pairs across files. Prints a summary indicating mismatched keys.
125
+
126
+ #### `run()`
127
+ CLI entry point. Reads file paths, checks existence, and calls `compareFiles`.
128
+
129
+ ---
130
+
124
131
  ## Error Handling
125
132
 
126
133
  - **No File Paths Provided**: Logs an error and exits.
@@ -167,6 +174,7 @@ Key: key2 - Mismatched values:
167
174
  ## Dependencies
168
175
 
169
176
  - `fs` module (Node.js File System)
177
+ - `js-yaml` module (Node.js YAML library)
170
178
 
171
179
  ---
172
180
 
package/PUBLISH.MD CHANGED
@@ -1,3 +1,16 @@
1
+
2
+ ## Link your package locally for testing:
3
+
4
+ ### Test Locally
5
+ `npm link`
6
+
7
+
8
+ ## Now you can run the script globally using:
9
+
10
+ ```properties-comparator <filePath1> <filePath2>```
11
+
12
+
13
+
1
14
  ## Publish the Package
2
15
  ### Login to npm (if you haven't already):
3
16
  `npm login`
package/README.md CHANGED
@@ -1,18 +1,20 @@
1
1
  # properties-comparator
2
- This utility provides functionality to parse and compare properties files in the format of key-value pairs. It reads properties files, compares the values for each key across multiple files, and logs the results.
2
+ This utility provides functionality to parse and compare **.properties** and **.yml or .yaml(YAML)** files in the format of key-value pairs. It reads these files, compares the values for each key across multiple files, and logs the results.
3
3
 
4
- Utility is available as NPM Package [https://www.npmjs.com/package/properties-comparator](https://www.npmjs.com/package/properties-comparator)
5
4
 
5
+ Utility is available as NPM Package [https://www.npmjs.com/package/properties-comparator](https://www.npmjs.com/package/properties-comparator)
6
6
 
7
- ## Link your package locally for testing:
8
7
 
9
- ### Test Locally
10
- `npm link`
8
+ ### Install the Package
9
+ `npm install -g properties-comparator`
11
10
 
12
11
 
13
12
  ## Now you can run the script globally using:
14
13
 
15
14
  ```properties-comparator <filePath1> <filePath2>```
16
15
 
16
+ ## Compiled on npm@11.1.0 & node v22.13.0
17
+ `npm install -g npm@11.1.0`
18
+
17
19
 
18
20
  ### Check [Documentation](DOCUMENTATION.md) for more details
package/TEST.md ADDED
@@ -0,0 +1,15 @@
1
+ # Automated testing with Jest
2
+
3
+
4
+ ## Run Your Tests
5
+ `npm init -y # if you haven't created a package.json yet`
6
+ `npm install --save-dev jest`
7
+
8
+ `npm test`
9
+ or
10
+ `npx jest`
11
+
12
+
13
+ ### Check jest.config.js for testing configuration
14
+
15
+ `npm test -- --coverage`
package/index.js CHANGED
@@ -1,75 +1,253 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs');
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const yaml = require("js-yaml");
4
6
 
5
7
  /**
6
- * Parses a properties file into an object.
8
+ * Parses a .properties file into an object.
9
+ * Handles any file read/parse errors gracefully.
7
10
  * @param {string} filePath - The path to the properties file.
8
- * @returns {Object} - An object containing key-value pairs.
11
+ * @returns {Object} - Key-value pairs, or {} on error.
9
12
  */
10
13
  function parsePropertiesFile(filePath) {
11
- return fs.readFileSync(filePath, 'utf-8')
12
- .split(/\r?\n/)
13
- .filter(line => line.trim() && !line.startsWith('#'))
14
- .reduce((acc, line) => {
15
- const [key, value] = line.split('=');
16
- if (key && value !== undefined) {
17
- acc[key.trim()] = value.trim();
18
- }
19
- return acc;
20
- }, {});
14
+ try {
15
+ const content = fs.readFileSync(filePath, "utf-8");
16
+ const lines = content.split(/\r?\n/);
17
+ const result = {};
18
+
19
+ for (let line of lines) {
20
+ let trimmedLine = line.trim();
21
+
22
+ // 1) Skip empty lines or lines that *start* with '#'
23
+ if (!trimmedLine || trimmedLine.startsWith("#")) {
24
+ continue;
25
+ }
26
+
27
+ // 2) Remove inline comment: anything after the first '#'
28
+ const hashIndex = trimmedLine.indexOf("#");
29
+ if (hashIndex !== -1) {
30
+ trimmedLine = trimmedLine.slice(0, hashIndex).trim();
31
+ }
32
+
33
+ // 3) Split on the *first* '=' only
34
+ const eqIndex = trimmedLine.indexOf("=");
35
+ if (eqIndex === -1) {
36
+ // No '=' => Not a valid key-value line
37
+ continue;
38
+ }
39
+
40
+ const key = trimmedLine.slice(0, eqIndex).trim();
41
+ const value = trimmedLine.slice(eqIndex + 1).trim();
42
+
43
+ if (key) {
44
+ result[key] = value;
45
+ }
46
+ }
47
+
48
+ return result;
49
+ } catch (err) {
50
+ console.error(
51
+ `Error reading/parsing .properties file (${filePath}):`,
52
+ err.message
53
+ );
54
+ return {};
55
+ }
21
56
  }
22
57
 
23
58
  /**
24
- * Compares properties across multiple files.
59
+ * Flattens a nested object into a single-level object using dot-notation for nested keys.
60
+ * @param {Object} obj - The object to flatten.
61
+ * @param {string} [parentKey=''] - The current parent key (used in recursion).
62
+ * @param {Object} [res={}] - The accumulator object.
63
+ * @returns {Object} - A flattened key-value map.
64
+ */
65
+ function flattenObject(obj, parentKey = "", res = {}) {
66
+ for (const [key, value] of Object.entries(obj || {})) {
67
+ const newKey = parentKey ? `${parentKey}.${key}` : key;
68
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
69
+ flattenObject(value, newKey, res);
70
+ } else {
71
+ // Ensure all values are strings for consistent comparison
72
+ res[newKey] = String(value);
73
+ }
74
+ }
75
+ return res;
76
+ }
77
+
78
+ /**
79
+ * Parses a .yml or .yaml file into a flat key-value map.
80
+ * Handles any file read/parse errors gracefully.
81
+ * @param {string} filePath - The path to the YAML file.
82
+ * @returns {Object} - A flattened key-value map, or {} on error.
83
+ */
84
+ function parseYamlFile(filePath) {
85
+ try {
86
+ const fileContents = fs.readFileSync(filePath, "utf-8");
87
+ const data = yaml.load(fileContents);
88
+ return flattenObject(data);
89
+ } catch (err) {
90
+ console.error(
91
+ `Error reading/parsing YAML file (${filePath}):`,
92
+ err.message
93
+ );
94
+ return {};
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Detects file extension and parses the file content into an object.
100
+ * Currently supports .properties, .yaml, and .yml.
101
+ * If extension is unsupported, logs a warning and returns {}.
102
+ * @param {string} filePath - The path to the file.
103
+ * @returns {Object} - Parsed content as a key-value map, or {} if unsupported.
104
+ */
105
+ function parseFile(filePath) {
106
+ const ext = path.extname(filePath).toLowerCase();
107
+
108
+ switch (ext) {
109
+ case ".properties":
110
+ return parsePropertiesFile(filePath);
111
+ case ".yml":
112
+ case ".yaml":
113
+ return parseYamlFile(filePath);
114
+ default:
115
+ console.error(
116
+ `Warning: Unsupported file extension "${ext}" for file "${filePath}". ` +
117
+ `Only .properties, .yml, or .yaml are supported. This file will be treated as empty.`
118
+ );
119
+ return {};
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Internal helper that compares key-value data from multiple files
125
+ * and returns a structured result (without printing to console).
126
+ *
25
127
  * @param {string[]} filePaths - Array of file paths.
128
+ * @returns {{
129
+ * mismatchCount: number,
130
+ * mismatchDetails: {
131
+ * key: string,
132
+ * values: string[],
133
+ * matched: boolean
134
+ * }[]
135
+ * }}
26
136
  */
27
- function compareProperties(filePaths) {
28
- const propertiesList = filePaths.map(parsePropertiesFile);
29
- const allKeys = new Set(propertiesList.flatMap(Object.keys));
30
-
31
- console.log('Comparing properties across files:\n');
32
-
33
- allKeys.forEach(key => {
34
- const values = propertiesList.map(props => props[key]?.replace(/\s+/g, '') || 'N/A');
35
- const allMatch = values.every(value => value === values[0]);
36
-
37
- if (allMatch) {
38
- console.log(`Key: ${key} - Values match across all files.`);
39
- } else {
40
- console.log(`Key: ${key} - Mismatched values:`);
41
- values.forEach((value, index) => {
42
- console.log(` File ${index + 1}: ${value}`);
43
- });
44
- }
45
- });
137
+ function compareFileData(filePaths) {
138
+ // Parse each file
139
+ const parsedObjects = filePaths.map(parseFile);
140
+
141
+ // Collect all unique keys
142
+ const allKeys = new Set(parsedObjects.flatMap((obj) => Object.keys(obj)));
143
+
144
+ const mismatchDetails = [];
145
+
146
+ // Compare values for each key across files
147
+ allKeys.forEach((key) => {
148
+ const values = parsedObjects.map(
149
+ (obj) => obj[key]?.replace(/\s+/g, "") || "N/A"
150
+ );
151
+ const matched = values.every((value) => value === values[0]);
152
+ mismatchDetails.push({ key, values, matched });
153
+ });
154
+
155
+ // Count mismatches
156
+ const mismatchCount = mismatchDetails.filter((d) => !d.matched).length;
157
+ return { mismatchCount, mismatchDetails };
46
158
  }
47
159
 
48
160
  /**
49
- * CLI entry point for comparing properties files.
161
+ * Helper function: checks if all values match across the provided files.
162
+ *
163
+ * @param {string[]} filePaths - Array of file paths.
164
+ * @returns {boolean} - True if all properties match across all files, false otherwise.
50
165
  */
51
- function run() {
52
- const filePaths = process.argv.slice(2);
166
+ function checkIfAllValuesMatch(filePaths) {
167
+ const { mismatchCount } = compareFileData(filePaths);
168
+ return mismatchCount === 0;
169
+ }
53
170
 
54
- if (filePaths.length === 0) {
55
- console.error('Please provide the paths to the properties files as command-line arguments.');
56
- process.exit(1);
57
- }
171
+ /**
172
+ * Helper function: returns a list of fields (keys) that do not match.
173
+ *
174
+ * @param {string[]} filePaths - Array of file paths.
175
+ * @returns {string[]} - List of mismatched keys.
176
+ */
177
+ function getMismatchFields(filePaths) {
178
+ const { mismatchDetails } = compareFileData(filePaths);
179
+ return mismatchDetails
180
+ .filter((detail) => !detail.matched)
181
+ .map((detail) => detail.key);
182
+ }
183
+
184
+ /**
185
+ * CLI function: compares properties/keys across multiple files,
186
+ * prints details to the console, and provides a summary.
187
+ *
188
+ * @param {string[]} filePaths - Array of file paths.
189
+ */
190
+ function compareFiles(filePaths) {
191
+ console.log("Comparing properties/keys across files:\n");
192
+
193
+ const { mismatchCount, mismatchDetails } = compareFileData(filePaths);
58
194
 
59
- if (filePaths.every(fs.existsSync)) {
60
- compareProperties(filePaths);
195
+ // Print detail for each key
196
+ mismatchDetails.forEach(({ key, values, matched }) => {
197
+ if (matched) {
198
+ console.log(`Key: ${key} - Values match: '${values[0]}'`);
61
199
  } else {
62
- console.error('One or more properties files are missing. Ensure all specified properties files exist.');
200
+ console.log(`Key: ${key} - Mismatched values:`);
201
+ values.forEach((value, idx) => {
202
+ console.log(` File ${idx + 1} (${filePaths[idx]}): ${value}`);
203
+ });
63
204
  }
205
+ });
206
+
207
+ // Summary
208
+ console.log("\n=== Summary ===");
209
+ if (mismatchCount === 0) {
210
+ console.log("All properties match across all files!");
211
+ } else {
212
+ console.log(`${mismatchCount} key(s) have mismatched values.`);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * CLI entry point for comparing .properties and .yml/.yaml files.
218
+ */
219
+ function run() {
220
+ const filePaths = process.argv.slice(2);
221
+
222
+ if (filePaths.length === 0) {
223
+ console.error("Please provide file paths as command-line arguments.");
224
+ process.exit(1);
225
+ }
226
+
227
+ // Optionally, check if all files exist
228
+ const missing = filePaths.filter((fp) => !fs.existsSync(fp));
229
+ if (missing.length > 0) {
230
+ console.error(`The following file(s) do not exist: ${missing.join(", ")}`);
231
+ // We continue anyway, or we can decide to exit.
232
+ // For now, let's exit to avoid unexpected comparisons:
233
+ process.exit(1);
234
+ }
235
+
236
+ compareFiles(filePaths);
64
237
  }
65
238
 
66
239
  module.exports = {
67
- parsePropertiesFile,
68
- compareProperties,
69
- run,
240
+ parsePropertiesFile,
241
+ parseYamlFile,
242
+ parseFile,
243
+ compareFileData,
244
+ checkIfAllValuesMatch,
245
+ getMismatchFields,
246
+ compareFiles,
247
+ run,
70
248
  };
71
249
 
72
250
  // If the script is executed directly, run the CLI
73
251
  if (require.main === module) {
74
- run();
252
+ run();
75
253
  }
package/jest.config.js ADDED
@@ -0,0 +1,22 @@
1
+ // jest.config.js
2
+ module.exports = {
3
+ // Tell Jest to collect coverage information
4
+ collectCoverage: true,
5
+ // Or specify which files to collect coverage from
6
+ collectCoverageFrom: ['src/**/*.js'], // adjust for your source folder
7
+
8
+ // Optionally set coverage thresholds
9
+ coverageThreshold: {
10
+ global: {
11
+ branches: 80,
12
+ functions: 80,
13
+ lines: 80,
14
+ statements: 80,
15
+ },
16
+ },
17
+
18
+ // Customize output directory or coverage reporters if desired
19
+ coverageDirectory: 'coverage',
20
+ coverageReporters: ['json', 'lcov', 'text', 'clover'],
21
+ };
22
+
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "properties-comparator",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "This utility provides functionality to parse and compare properties files in the format of key-value pairs. It reads properties files, compares the values for each key across multiple files, and logs the results.",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "properties-comparator": "./index.js"
8
8
  },
9
9
  "scripts": {
10
- "test": "node index.js"
10
+ "test": "jest",
11
+ "run": "node index.js"
11
12
  },
12
13
  "repository": {
13
14
  "type": "git",
@@ -22,5 +23,11 @@
22
23
  "bugs": {
23
24
  "url": "https://github.com/zackria/properties-comparator/issues"
24
25
  },
25
- "homepage": "https://github.com/zackria/properties-comparator#readme"
26
+ "homepage": "https://github.com/zackria/properties-comparator#readme",
27
+ "dependencies": {
28
+ "js-yaml": "^4.1.0"
29
+ },
30
+ "devDependencies": {
31
+ "jest": "^29.7.0"
32
+ }
26
33
  }