├── .npmignore ├── .gitignore ├── test ├── fixtures │ ├── colons.properties │ ├── unix.properties │ ├── utf8.properties │ ├── teamcity.properties │ ├── doublequoted.properties │ ├── example2.properties │ ├── arrayExample.properties │ ├── boolean.properties │ ├── multivalued.properties │ └── example.properties └── properties_test.ts ├── .travis.yml ├── tsconfig.test.json ├── .babelrc ├── .jshintrc ├── LICENSE-MIT ├── package.json ├── README.md ├── tsconfig.json └── src └── index.ts /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | *.sublime* 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | lib 3 | pkg 4 | *.sublime* 5 | -------------------------------------------------------------------------------- /test/fixtures/colons.properties: -------------------------------------------------------------------------------- 1 | colon.value : value1 2 | colon.additional :value2 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'node' 4 | - '10' 5 | - '8' 6 | - 'lts/*' 7 | -------------------------------------------------------------------------------- /test/fixtures/unix.properties: -------------------------------------------------------------------------------- 1 | #here is the first value 2 | value.1=value 1 3 | value.2=Another Value -------------------------------------------------------------------------------- /test/fixtures/utf8.properties: -------------------------------------------------------------------------------- 1 | utf8.string = \u2601 a string with accent : crédits seront très bientôt épuisés -------------------------------------------------------------------------------- /test/fixtures/teamcity.properties: -------------------------------------------------------------------------------- 1 | teamcity.agent.dotnet.agent_url=http\://localhost\:9090/RPC2 2 | teamcity.auth.userId=TeamCityBuildId\=673 3 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "target": "es2017" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/doublequoted.properties: -------------------------------------------------------------------------------- 1 | double.quoted.string = The first " and the second " should be replaced. ${an.interpolation} 2 | an.interpolation = Can we replace " in interpolation ? -------------------------------------------------------------------------------- /test/fixtures/example2.properties: -------------------------------------------------------------------------------- 1 | extra.property=14.47 2 | referenced.property=${ricola.version.minor} 3 | #this is a comment that should not be parsed 4 | 5 | another.property=444 -------------------------------------------------------------------------------- /test/fixtures/arrayExample.properties: -------------------------------------------------------------------------------- 1 | # THis is an example of a file with array manipulations 2 | arrayKey = first : ${ricola.version.prefixed} 3 | arrayKey = second 4 | arrayKey = third 5 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "6" 8 | } 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/boolean.properties: -------------------------------------------------------------------------------- 1 | boolean.true1=true 2 | boolean.true2=True 3 | boolean.true3=TRUE 4 | boolean.true4=1 5 | boolean.false1=false 6 | boolean.false2=False 7 | boolean.false3=FALSE 8 | boolean.false4=0 9 | boolean.empty= -------------------------------------------------------------------------------- /test/fixtures/multivalued.properties: -------------------------------------------------------------------------------- 1 | multi.value = value1 2 | multi.value = value2 3 | multi.interpolated.value = The value is ${multi.value} 4 | multi.bool.value=true 5 | multi.bool.value=true 6 | multi.int.value=0 7 | multi.int.value=1 -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "boss": true, 12 | "eqnull": true, 13 | "node": true, 14 | "esnext": true 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/example.properties: -------------------------------------------------------------------------------- 1 | ricola.version=${ricola.version.major}.${ricola.version.minor} 2 | ricola.version.major=2.5 3 | ricola.version.minor=7 4 | ricola.version.symlink=${ricola.version.major} 5 | ricola.withSpaces = hello 6 | ricola.version.prefixed=ricola-${ricola.version.major} 7 | ricola.version.postfixed=ricola-${ricola.version.major}-tothemax 8 | ricola.recursive=${ricola.version.prefixed}-recursive 9 | regex.format.date=^(0?[1-9]|1[012])\\/?(0?[1-9]|[12][0-9]|3[01])$ 10 | property.with.equals=some=value 11 | property.emptyString= 12 | withNewline=Welcome to \ 13 | The Monkey House! 14 | withIndentation=Welcome to \ 15 | The Rock. 16 | targetCities=\ 17 | Detroit,\ 18 | Chicago,\ 19 | Los Angeles 20 | with-dashes=With Dashes 21 | with_underscores=With Underscores -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Matt Steele 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "java-properties", 3 | "description": "Reads and interpolates Java .properties files", 4 | "version": "1.0.2", 5 | "homepage": "http://github.com/mattdsteele/java-properties", 6 | "author": { 7 | "email": "orphum@gmail.com", 8 | "name": "Matt Steele" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/mattdsteele/java-properties.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/mattdsteele/java-properties/issues" 16 | }, 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "http://opensource.org/licenses/MIT" 21 | } 22 | ], 23 | "@pika/pack": { 24 | "pipeline": [ 25 | [ 26 | "@pika/plugin-standard-pkg", 27 | { 28 | "exclude": [ 29 | "test/*" 30 | ] 31 | } 32 | ], 33 | [ 34 | "@pika/plugin-build-node" 35 | ], 36 | [ 37 | "@pika/plugin-build-types", 38 | { 39 | "exclude": [ 40 | "test/*" 41 | ] 42 | } 43 | ] 44 | ] 45 | }, 46 | "engines": { 47 | "node": ">= 0.6.0" 48 | }, 49 | "scripts": { 50 | "test": "TS_NODE_PROJECT=\"tsconfig.test.json\" mocha --require ts-node/register \"test/**/*.ts\"", 51 | "build": "pack build" 52 | }, 53 | "devDependencies": { 54 | "@pika/pack": "^0.3.7", 55 | "@pika/plugin-build-node": "^0.4.0", 56 | "@pika/plugin-build-types": "^0.4.0", 57 | "@pika/plugin-standard-pkg": "^0.4.0", 58 | "@pika/plugin-ts-standard-pkg": "^0.4.0", 59 | "@types/chai": "^4.1.7", 60 | "@types/mocha": "^5.2.7", 61 | "@types/node": "^11.13.17", 62 | "chai": "^4.2.0", 63 | "mocha": "^10.2.0", 64 | "ts-node": "^8.3.0", 65 | "typescript": "^3.5.3" 66 | }, 67 | "keywords": [ 68 | "java", 69 | "properties" 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java-properties 2 | 3 | ![travis](https://travis-ci.org/mattdsteele/java-properties.svg) 4 | 5 | Read Java .properties files. Supports adding dynamically some files and array key value (same key multiple times) 6 | 7 | ## Getting Started 8 | 9 | Install the module with: `npm install java-properties` 10 | 11 | ## Documentation 12 | 13 | ```javascript 14 | var properties = require('java-properties'); 15 | 16 | // Reference a properties file 17 | var values = properties.of('values.properties'); 18 | 19 | //Read a value from the properties file 20 | values.get('a.key'); //returns value of a.key 21 | 22 | //Add an additional file's properties 23 | values.add('anotherfile.properties'); 24 | 25 | //Clear out all values 26 | values.reset(); 27 | ... 28 | // returns the value of a.key of 'defaultValue' if key is not found 29 | values.get('a.key', 'defaultValue'); 30 | ... 31 | // returns the value of the a.int.key as an int or 18 32 | values.getInt('a.int.key', 18); 33 | ... 34 | // returns the value of the a.float.key as a float or 18.23 35 | values.getFloat('a.float.key', 18.23); 36 | ... 37 | // returns the value of the a.bool.key as an boolean. Parse true or false with any case or 0 or 1 38 | values.getBoolean('a.bool.key', true); 39 | ... 40 | // returns all the keys 41 | values.getKeys(); 42 | ... 43 | // adds another file the properties list 44 | values.addFile('anotherFile.properties'); 45 | ... 46 | // empty the keys previously loaded 47 | values.reset(); 48 | ... 49 | [ -- .properties file 50 | an.array.key=value1 51 | an.array.key=value2 52 | ] 53 | values.get('an.array.key'); // returns [value1, value2] 54 | 55 | // Multiple contexts 56 | var myFile = new PropertiesFile( 57 | 'example.properties', 58 | 'arrayExample.properties'); 59 | myFile.get('arrayKey'); 60 | 61 | var myOtherFile = new PropertiesFile(); 62 | myOtherFile.addFile('example.properties'); 63 | myOtherFile.addFile('example2.properties'); 64 | ``` 65 | 66 | ## Contributing 67 | 68 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code. 69 | 70 | ## Release History 71 | 72 | - 0.1.0 Initial commit 73 | - 0.1.5 Support empty strings 74 | - 0.1.6 New API: `getKeys` 75 | - 0.1.7 New APIs: `addFile` and `reset` 76 | - 0.1.8 Add array key (the same key many time in files) 77 | - 0.2.0 Wrap features into a class to be able to have multiple running contexts 78 | - 0.2.1 Add default value to get method. Add getInt and getFloat to get an integer or float value 79 | - 0.2.2 Add getBoolean method to get a value as a boolean. Accepted values are true, TRUE, false, FALSE, 0, 1 80 | - 0.2.3 Add getMatchingKeys method 81 | - 0.2.4 Allow multi-line properties 82 | - 0.2.5 Refactorings, no new features 83 | - 0.2.6 FIX interpolation when a property is multivalued 84 | - 0.2.7 Get only last value for int and boolean in case of multivalued attribute 85 | - 0.2.8 FIX unicode \uxxxx char decoding 86 | - 0.2.9 Allow multiple double quotation marks 87 | - 0.2.10 fix bug with escaped : & = (thanks @Drapegnik) 88 | - 1.0.0 Rewrite as Typescript. Support Node 6+ only 89 | 90 | ## License 91 | 92 | Licensed under the MIT license. 93 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es2018" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, 5 | "module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | // "outDir": "./", /* Redirect output structure to the directory. */ 15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true /* Enable all strict type-checking options. */, 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 29 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 31 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 32 | 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | 39 | /* Module Resolution Options */ 40 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 44 | // "typeRoots": [] /* List of folders to include type definitions from. */, 45 | // "types": [], /* Type declaration files to be included in compilation. */ 46 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 47 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 48 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 49 | 50 | /* Source Map Options */ 51 | // "sourceRoot": "" /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 52 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 53 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 54 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 55 | 56 | /* Experimental Options */ 57 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 58 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 59 | }, 60 | "include": ["src"] 61 | } 62 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * properties 3 | * 4 | * Copyright (c) 2013 Matt Steele 5 | * Licensed under the MIT license. 6 | */ 7 | 8 | import fs from 'fs'; 9 | 10 | class PropertiesFile { 11 | objs: { [key: string]: any }; 12 | constructor(...args: string[]) { 13 | this.objs = {}; 14 | if (args.length) { 15 | this.of.apply(this, args); 16 | } 17 | } 18 | 19 | makeKeys(line: string) { 20 | if (line && line.indexOf('#') !== 0) { 21 | //let splitIndex = line.indexOf('='); 22 | let separatorPositions = ['=',':'] 23 | .map((sep) => {return line.indexOf(sep);}) 24 | .filter((index) => {return index > -1;}); 25 | let splitIndex = Math.min(...separatorPositions); 26 | let key = line.substring(0, splitIndex).trim(); 27 | let value = line.substring(splitIndex + 1).trim(); 28 | // if keys already exists ... 29 | if (this.objs.hasOwnProperty(key)) { 30 | // if it is already an Array 31 | if (Array.isArray(this.objs[key])) { 32 | // just push the new value 33 | this.objs[key].push(value); 34 | } else { 35 | // transform the value into Array 36 | let oldValue = this.objs[key]; 37 | this.objs[key] = [oldValue, value]; 38 | } 39 | } else { 40 | // the key does not exists 41 | const escapedValue = value 42 | .replace(/"/g, '\\"') // escape " 43 | .replace(/\\:/g, ':') // remove \ before : 44 | .replace(/\\=/g, '='); // remove \ before = 45 | this.objs[key] = unescape(JSON.parse('"' + escapedValue + '"')); 46 | } 47 | } 48 | } 49 | 50 | addFile(file: string) { 51 | let data = fs.readFileSync(file, 'utf-8'); 52 | let items = data.split(/\r?\n/); 53 | let me = this; 54 | for (let i = 0; i < items.length; i++) { 55 | let line = items[i]; 56 | while (line.substring(line.length - 1) === '\\') { 57 | line = line.slice(0, -1); 58 | let nextLine = items[i + 1]; 59 | line = line + nextLine.trim(); 60 | i++; 61 | } 62 | me.makeKeys(line); 63 | } 64 | } 65 | 66 | of(...args: string[]) { 67 | for (let i = 0; i < args.length; i++) { 68 | this.addFile(args[i]); 69 | } 70 | } 71 | 72 | get(key: string, defaultValue?: string) { 73 | if (this.objs.hasOwnProperty(key)) { 74 | if (Array.isArray(this.objs[key])) { 75 | let ret = []; 76 | for (let i = 0; i < this.objs[key].length; i++) { 77 | ret[i] = this.interpolate(this.objs[key][i]); 78 | } 79 | return ret; 80 | } else { 81 | return typeof this.objs[key] === 'undefined' 82 | ? '' 83 | : this.interpolate(this.objs[key]); 84 | } 85 | } 86 | return defaultValue; 87 | } 88 | 89 | getLast(key: string, defaultValue?: string) { 90 | if (this.objs.hasOwnProperty(key)) { 91 | if (Array.isArray(this.objs[key])) { 92 | var lg = this.objs[key].length; 93 | return this.interpolate(this.objs[key][lg - 1]); 94 | } else { 95 | return typeof this.objs[key] === 'undefined' 96 | ? '' 97 | : this.interpolate(this.objs[key]); 98 | } 99 | } 100 | return defaultValue; 101 | } 102 | 103 | getFirst(key: string, defaultValue?: string) { 104 | if (this.objs.hasOwnProperty(key)) { 105 | if (Array.isArray(this.objs[key])) { 106 | return this.interpolate(this.objs[key][0]); 107 | } else { 108 | return typeof this.objs[key] === 'undefined' 109 | ? '' 110 | : this.interpolate(this.objs[key]); 111 | } 112 | } 113 | return defaultValue; 114 | } 115 | 116 | getInt(key: string, defaultIntValue?: number) { 117 | let val = this.getLast(key); 118 | if (!val) { 119 | return defaultIntValue; 120 | } else { 121 | return parseInt(val, 10); 122 | } 123 | } 124 | 125 | getFloat(key: string, defaultFloatValue?: number) { 126 | let val = this.getLast(key); 127 | if (!val) { 128 | return defaultFloatValue; 129 | } else { 130 | return parseFloat(val); 131 | } 132 | } 133 | 134 | getBoolean(key: string, defaultBooleanValue?: boolean) { 135 | function parseBool(b: string) { 136 | return !/^(false|0)$/i.test(b) && !!b; 137 | } 138 | 139 | let val = this.getLast(key); 140 | if (!val) { 141 | return defaultBooleanValue || false; 142 | } else { 143 | return parseBool(val); 144 | } 145 | } 146 | 147 | set(key: string, value: string) { 148 | this.objs[key] = value; 149 | } 150 | 151 | interpolate(s: string): string { 152 | let me = this; 153 | return s 154 | .replace(/\\\\/g, '\\') 155 | .replace(/\$\{([A-Za-z0-9\.\-\_]*)\}/g, function(match) { 156 | return me.getLast(match.substring(2, match.length - 1))!; 157 | }); 158 | } 159 | 160 | getKeys() { 161 | let keys = []; 162 | for (let key in this.objs) { 163 | keys.push(key); 164 | } 165 | return keys; 166 | } 167 | 168 | getMatchingKeys(matchstr: string) { 169 | let keys = []; 170 | for (let key in this.objs) { 171 | if (key.search(matchstr) !== -1) { 172 | keys.push(key); 173 | } 174 | } 175 | return keys; 176 | } 177 | 178 | reset() { 179 | this.objs = {}; 180 | } 181 | } 182 | 183 | // Retain 'of' from v1 for backward compatibility 184 | let of = function(...args: any[]) { 185 | let globalFile = new PropertiesFile(); 186 | globalFile.of.apply(globalFile, args); 187 | return globalFile; 188 | }; 189 | 190 | export { PropertiesFile, of }; 191 | -------------------------------------------------------------------------------- /test/properties_test.ts: -------------------------------------------------------------------------------- 1 | import { PropertiesFile, of } from '../src/index'; 2 | import { expect } from 'chai'; 3 | let props: PropertiesFile; 4 | 5 | // Shim for NodeUnit tests 6 | const test = { 7 | equal(expected: any, actual: any) { 8 | expect(expected).to.eq(actual); 9 | } 10 | }; 11 | 12 | describe('properties', () => { 13 | beforeEach(() => { 14 | props = of('test/fixtures/example.properties'); 15 | }); 16 | it('basic read from a file', () => { 17 | expect('2.5').to.eq(props.get('ricola.version.major')); 18 | expect('7').to.eq(props.get('ricola.version.minor')); 19 | }); 20 | it('nested values', () => { 21 | expect(props.get('ricola.version.symlink')).to.eq('2.5'); 22 | }); 23 | it('complicated nest', () => { 24 | expect('ricola-2.5').to.eq(props.get('ricola.version.prefixed')); 25 | expect('ricola-2.5-tothemax').to.eq(props.get('ricola.version.postfixed')); 26 | }); 27 | it('recursive nest', () => { 28 | expect('ricola-2.5-recursive').to.eq(props.get('ricola.recursive')); 29 | }); 30 | it('double nest', () => { 31 | expect('2.5.7').to.eq(props.get('ricola.version')); 32 | }); 33 | it('with spaces', () => { 34 | expect('hello').to.eq(props.get('ricola.withSpaces')); 35 | }); 36 | it('second file', () => { 37 | props = of( 38 | 'test/fixtures/example.properties', 39 | 'test/fixtures/example2.properties' 40 | ); 41 | test.equal('14.47', props.get('extra.property')); 42 | test.equal('444', props.get('another.property')); 43 | test.equal('7', props.get('referenced.property')); 44 | }); 45 | it('not found property', () => { 46 | test.equal(undefined, props.get('undefinedValue')); 47 | }); 48 | it('additional property', () => { 49 | test.equal(undefined, props.get('undefinedValue')); 50 | props.set('undefinedValue', '14.8'); 51 | test.equal('14.8', props.get('undefinedValue')); 52 | }); 53 | it('with backslashes', () => { 54 | var key = '^(0?[1-9]|1[012])\\/?(0?[1-9]|[12][0-9]|3[01])$'; 55 | test.equal(key, props.get('regex.format.date')); 56 | }); 57 | it('interpolating', () => { 58 | test.equal( 59 | 'version 7 is the best!', 60 | props.interpolate('version ${ricola.version.minor} is the best!') 61 | ); 62 | }); 63 | it('unix line endings', () => { 64 | props = of('test/fixtures/unix.properties'); 65 | test.equal('value 1', props.get('value.1')); 66 | test.equal('Another Value', props.get('value.2')); 67 | }); 68 | it('includes multiple equals', () => { 69 | test.equal('some=value', props.get('property.with.equals')); 70 | }); 71 | it('empty string', () => { 72 | test.equal('', props.get('property.emptyString')); 73 | }); 74 | it('get keys', () => { 75 | // check that properties are well loaded 76 | expect(props.getKeys()).to.deep.eq([ 77 | 'ricola.version', 78 | 'ricola.version.major', 79 | 'ricola.version.minor', 80 | 'ricola.version.symlink', 81 | 'ricola.withSpaces', 82 | 'ricola.version.prefixed', 83 | 'ricola.version.postfixed', 84 | 'ricola.recursive', 85 | 'regex.format.date', 86 | 'property.with.equals', 87 | 'property.emptyString', 88 | 'withNewline', 89 | 'withIndentation', 90 | 'targetCities', 91 | 'with-dashes', 92 | 'with_underscores' 93 | ]); 94 | }); 95 | it('reset', () => { 96 | props.reset(); 97 | expect(props.getKeys()).to.eql([]); 98 | }); 99 | it('addFile', () => { 100 | // Reset and add manually the 2 first files 101 | props.reset(); 102 | props.addFile('test/fixtures/example.properties'); 103 | props.addFile('test/fixtures/example2.properties'); 104 | test.equal('14.47', props.get('extra.property')); 105 | test.equal('444', props.get('another.property')); 106 | test.equal('7', props.get('referenced.property')); 107 | 108 | // 'value.1' must not be defined cause it is unix.properties file 109 | test.equal(undefined, props.get('value.1')); 110 | // add the unix.properties file 111 | props.addFile('test/fixtures/unix.properties'); 112 | // check that value.1 is now defined 113 | test.equal('value 1', props.get('value.1')); 114 | // and that old values are still there 115 | test.equal('444', props.get('another.property')); 116 | }); 117 | it('array file', () => { 118 | props = of( 119 | 'test/fixtures/example.properties', 120 | 'test/fixtures/arrayExample.properties' 121 | ); 122 | var arrayKey = props.get('arrayKey')!; 123 | test.equal(true, Array.isArray(arrayKey)); 124 | test.equal(3, arrayKey.length); 125 | test.equal('first : ricola-2.5', arrayKey[0]); 126 | test.equal('second', arrayKey[1]); 127 | test.equal('third', arrayKey[2]); 128 | }); 129 | it('array file undefined', () => { 130 | props = of( 131 | 'test/fixtures/example.properties', 132 | 'test/fixtures/arrayExample.properties' 133 | ); 134 | test.equal(undefined, props.get('arrayKeyUndefined')); 135 | }); 136 | it('Using PropertiesFile with files provided', () => { 137 | props.reset(); 138 | var myFile = new PropertiesFile( 139 | 'test/fixtures/example.properties', 140 | 'test/fixtures/arrayExample.properties' 141 | ); 142 | test.equal(3, myFile.get('arrayKey')!.length); 143 | myFile.reset(); 144 | test.equal(undefined, myFile.get('arrayKey')); 145 | }); 146 | it('Using PropertiesFile with 2 different contexts', () => { 147 | props.reset(); 148 | var myFile = new PropertiesFile(); 149 | myFile.of( 150 | 'test/fixtures/example.properties', 151 | 'test/fixtures/arrayExample.properties' 152 | ); 153 | 154 | var myOtherFile = new PropertiesFile(); 155 | myOtherFile.addFile('test/fixtures/example.properties'); 156 | myOtherFile.addFile('test/fixtures/example2.properties'); 157 | 158 | test.equal(3, myFile.get('arrayKey')!.length); 159 | test.equal(undefined, myFile.get('referenced.property')); 160 | test.equal('some=value', myOtherFile.get('property.with.equals')); 161 | test.equal('7', myOtherFile.get('referenced.property')); 162 | }); 163 | it('Using defaut value for get', () => { 164 | var myFile = new PropertiesFile( 165 | 'test/fixtures/example.properties', 166 | 'test/fixtures/arrayExample.properties' 167 | ); 168 | test.equal(undefined, myFile.get('referenced.property')); 169 | test.equal( 170 | 'defaultValue', 171 | myFile.get('referenced.property', 'defaultValue') 172 | ); 173 | test.equal('hello', myFile.get('ricola.withSpaces')); 174 | test.equal('hello', myFile.get('ricola.withSpaces', 'defaultValue')); 175 | }); 176 | it('Using int value with getInt', () => { 177 | var myFile = new PropertiesFile( 178 | 'test/fixtures/example.properties', 179 | 'test/fixtures/arrayExample.properties' 180 | ); 181 | test.equal('7', myFile.get('ricola.version.minor')); 182 | test.equal(7, myFile.getInt('ricola.version.minor')); 183 | test.equal(undefined, myFile.getInt('dont.exists')); 184 | test.equal(12, myFile.getInt('dont.exists', 12)); 185 | test.equal(true, isNaN(myFile.getInt('ricola.withSpaces')!)); 186 | }); 187 | it('Using float value with getFloat', () => { 188 | var myFile = new PropertiesFile( 189 | 'test/fixtures/example.properties', 190 | 'test/fixtures/arrayExample.properties' 191 | ); 192 | test.equal('2.5', myFile.get('ricola.version.major')); 193 | test.equal(2.5, myFile.getFloat('ricola.version.major')); 194 | test.equal(undefined, myFile.getFloat('dont.exists')); 195 | test.equal(12.23, myFile.getFloat('dont.exists', 12.23)); 196 | test.equal(true, isNaN(myFile.getFloat('ricola.withSpaces')!)); 197 | }); 198 | it('Using boolean value with getBoolean', () => { 199 | var myFile = new PropertiesFile('test/fixtures/boolean.properties'); 200 | test.equal(true, myFile.getBoolean('boolean.true1')); 201 | test.equal(true, myFile.getBoolean('boolean.true2')); 202 | test.equal(true, myFile.getBoolean('boolean.true3')); 203 | test.equal(true, myFile.getBoolean('boolean.true4')); 204 | test.equal(false, myFile.getBoolean('boolean.false1')); 205 | test.equal(false, myFile.getBoolean('boolean.false2')); 206 | test.equal(false, myFile.getBoolean('boolean.false3')); 207 | test.equal(false, myFile.getBoolean('boolean.false4')); 208 | 209 | test.equal(false, myFile.getBoolean('boolean.empty', false)); 210 | test.equal(true, myFile.getBoolean('boolean.empty', true)); 211 | test.equal(false, myFile.getBoolean('boolean.empty')); 212 | }); 213 | it('getMatchingKeys', () => { 214 | var myFile = new PropertiesFile('test/fixtures/example.properties'); 215 | var props = myFile.getMatchingKeys('property'); 216 | test.equal(2, props.length); 217 | expect(['property.with.equals', 'property.emptyString']).to.deep.eq(props); 218 | }); 219 | 220 | it('with newline', () => { 221 | test.equal('Welcome to The Monkey House!', props.get('withNewline')); 222 | }); 223 | 224 | it('with indentations', () => { 225 | test.equal('Welcome to The Rock.', props.get('withIndentation')); 226 | }); 227 | 228 | it('multiple backslashes', () => { 229 | test.equal('Detroit,Chicago,Los Angeles', props.get('targetCities')); 230 | }); 231 | it('Multivalued property in interpolation', () => { 232 | var myFile = new PropertiesFile('test/fixtures/multivalued.properties'); 233 | test.equal(myFile.get('multi.value')!.length, 2); 234 | test.equal(myFile.get('multi.value')![0], 'value1'); 235 | test.equal(myFile.get('multi.value')![1], 'value2'); 236 | test.equal(myFile.getLast('multi.value'), 'value2'); 237 | test.equal(myFile.getFirst('multi.value'), 'value1'); 238 | test.equal('The value is value2', myFile.get('multi.interpolated.value')); 239 | }); 240 | it('Multivalued boolean property', () => { 241 | var myFile = new PropertiesFile('test/fixtures/multivalued.properties'); 242 | test.equal(myFile.getBoolean('multi.bool.value'), true); 243 | }); 244 | it('Multivalued int property', () => { 245 | var myFile = new PropertiesFile('test/fixtures/multivalued.properties'); 246 | test.equal(myFile.getInt('multi.int.value'), 1); 247 | }); 248 | it('utf8 strings', () => { 249 | var myFile = new PropertiesFile('test/fixtures/utf8.properties'); 250 | var str = myFile.get('utf8.string'); 251 | if (typeof str === 'string') { 252 | test.equal( 253 | str, 254 | '\u2601 a string with accent : crédits seront très bientôt épuisés' 255 | ); 256 | expect(str.charAt(0)).to.eq(String.fromCharCode(0x2601)); 257 | } 258 | }); 259 | it('double quoted strings', () => { 260 | var myFile = new PropertiesFile('test/fixtures/doublequoted.properties'); 261 | var str = myFile.get('double.quoted.string'); 262 | test.equal( 263 | str, 264 | 'The first " and the second " should be replaced. Can we replace " in interpolation ?' 265 | ); 266 | }); 267 | it(':', () => { 268 | const file = new PropertiesFile('test/fixtures/colons.properties'); 269 | expect(file.get('colon.value')).to.eq('value1'); 270 | expect(file.get('colon.additional')).to.eq('value2'); 271 | }); 272 | it('teamcity unescaped `:` & `=`', () => { 273 | var myFile = new PropertiesFile('test/fixtures/teamcity.properties'); 274 | test.equal( 275 | myFile.get('teamcity.agent.dotnet.agent_url'), 276 | 'http://localhost:9090/RPC2' 277 | ); 278 | test.equal(myFile.get('teamcity.auth.userId'), 'TeamCityBuildId=673'); 279 | }); 280 | it('works with dashes and underscores', () => { 281 | expect(props.get('with-dashes')).to.eq('With Dashes'); 282 | expect(props.get('with_underscores')).to.eq('With Underscores'); 283 | }); 284 | }); 285 | --------------------------------------------------------------------------------