├── .editorconfig ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .travis.yml ├── .verb.md ├── LICENSE ├── README.md ├── cli.js ├── index.js ├── package.json └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | insert_final_newline = false 15 | 16 | [test/**] 17 | trim_trailing_whitespace = false 18 | insert_final_newline = false 19 | 20 | [templates/**] 21 | trim_trailing_whitespace = false 22 | insert_final_newline = false 23 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "modules": true, 4 | "experimentalObjectRestSpread": true 5 | }, 6 | "env": { 7 | "browser": false, 8 | "es6": true, 9 | "node": true, 10 | "mocha": true 11 | }, 12 | "globals": { 13 | "document": false, 14 | "navigator": false, 15 | "window": false 16 | }, 17 | "rules": { 18 | "accessor-pairs": 2, 19 | "arrow-spacing": [ 20 | 2, 21 | { 22 | "before": true, 23 | "after": true 24 | } 25 | ], 26 | "block-spacing": [ 27 | 2, 28 | "always" 29 | ], 30 | "brace-style": [ 31 | 2, 32 | "1tbs", 33 | { 34 | "allowSingleLine": true 35 | } 36 | ], 37 | "comma-dangle": [ 38 | 2, 39 | "never" 40 | ], 41 | "comma-spacing": [ 42 | 2, 43 | { 44 | "before": false, 45 | "after": true 46 | } 47 | ], 48 | "comma-style": [ 49 | 2, 50 | "last" 51 | ], 52 | "constructor-super": 2, 53 | "curly": [ 54 | 2, 55 | "multi-line" 56 | ], 57 | "dot-location": [ 58 | 2, 59 | "property" 60 | ], 61 | "eol-last": 2, 62 | "eqeqeq": [ 63 | 2, 64 | "allow-null" 65 | ], 66 | "generator-star-spacing": [ 67 | 2, 68 | { 69 | "before": true, 70 | "after": true 71 | } 72 | ], 73 | "handle-callback-err": [ 74 | 2, 75 | "^(err|error)$" 76 | ], 77 | "indent": [ 78 | 2, 79 | 2, 80 | { 81 | "SwitchCase": 1 82 | } 83 | ], 84 | "key-spacing": [ 85 | 2, 86 | { 87 | "beforeColon": false, 88 | "afterColon": true 89 | } 90 | ], 91 | "new-cap": [ 92 | 2, 93 | { 94 | "newIsCap": true, 95 | "capIsNew": false 96 | } 97 | ], 98 | "new-parens": 2, 99 | "no-array-constructor": 2, 100 | "no-caller": 2, 101 | "no-class-assign": 2, 102 | "no-cond-assign": 2, 103 | "no-const-assign": 2, 104 | "no-control-regex": 2, 105 | "no-debugger": 2, 106 | "no-delete-var": 2, 107 | "no-dupe-args": 2, 108 | "no-dupe-class-members": 2, 109 | "no-dupe-keys": 2, 110 | "no-duplicate-case": 2, 111 | "no-empty-character-class": 2, 112 | "no-empty-label": 2, 113 | "no-eval": 2, 114 | "no-ex-assign": 2, 115 | "no-extend-native": 2, 116 | "no-extra-bind": 2, 117 | "no-extra-boolean-cast": 2, 118 | "no-extra-parens": [ 119 | 2, 120 | "functions" 121 | ], 122 | "no-fallthrough": 2, 123 | "no-floating-decimal": 2, 124 | "no-func-assign": 2, 125 | "no-implied-eval": 2, 126 | "no-inner-declarations": [ 127 | 2, 128 | "functions" 129 | ], 130 | "no-invalid-regexp": 2, 131 | "no-irregular-whitespace": 2, 132 | "no-iterator": 2, 133 | "no-label-var": 2, 134 | "no-labels": 2, 135 | "no-lone-blocks": 2, 136 | "no-mixed-spaces-and-tabs": 2, 137 | "no-multi-spaces": 2, 138 | "no-multi-str": 2, 139 | "no-multiple-empty-lines": [ 140 | 2, 141 | { 142 | "max": 1 143 | } 144 | ], 145 | "no-native-reassign": 2, 146 | "no-negated-in-lhs": 2, 147 | "no-new": 2, 148 | "no-new-func": 2, 149 | "no-new-object": 2, 150 | "no-new-require": 2, 151 | "no-new-wrappers": 2, 152 | "no-obj-calls": 2, 153 | "no-octal": 2, 154 | "no-octal-escape": 2, 155 | "no-proto": 0, 156 | "no-redeclare": 2, 157 | "no-regex-spaces": 2, 158 | "no-return-assign": 2, 159 | "no-self-compare": 2, 160 | "no-sequences": 2, 161 | "no-shadow-restricted-names": 2, 162 | "no-spaced-func": 2, 163 | "no-sparse-arrays": 2, 164 | "no-this-before-super": 2, 165 | "no-throw-literal": 2, 166 | "no-trailing-spaces": 0, 167 | "no-undef": 2, 168 | "no-undef-init": 2, 169 | "no-unexpected-multiline": 2, 170 | "no-unneeded-ternary": [ 171 | 2, 172 | { 173 | "defaultAssignment": false 174 | } 175 | ], 176 | "no-unreachable": 2, 177 | "no-unused-vars": [ 178 | 2, 179 | { 180 | "vars": "all", 181 | "args": "none" 182 | } 183 | ], 184 | "no-useless-call": 0, 185 | "no-with": 2, 186 | "one-var": [ 187 | 0, 188 | { 189 | "initialized": "never" 190 | } 191 | ], 192 | "operator-linebreak": [ 193 | 0, 194 | "after", 195 | { 196 | "overrides": { 197 | "?": "before", 198 | ":": "before" 199 | } 200 | } 201 | ], 202 | "padded-blocks": [ 203 | 0, 204 | "never" 205 | ], 206 | "quotes": [ 207 | 2, 208 | "single", 209 | "avoid-escape" 210 | ], 211 | "radix": 2, 212 | "semi": [ 213 | 2, 214 | "always" 215 | ], 216 | "semi-spacing": [ 217 | 2, 218 | { 219 | "before": false, 220 | "after": true 221 | } 222 | ], 223 | "space-after-keywords": [ 224 | 2, 225 | "always" 226 | ], 227 | "space-before-blocks": [ 228 | 2, 229 | "always" 230 | ], 231 | "space-before-function-paren": [ 232 | 2, 233 | "never" 234 | ], 235 | "space-before-keywords": [ 236 | 2, 237 | "always" 238 | ], 239 | "space-in-parens": [ 240 | 2, 241 | "never" 242 | ], 243 | "space-infix-ops": 2, 244 | "space-return-throw-case": 2, 245 | "space-unary-ops": [ 246 | 2, 247 | { 248 | "words": true, 249 | "nonwords": false 250 | } 251 | ], 252 | "spaced-comment": [ 253 | 0, 254 | "always", 255 | { 256 | "markers": [ 257 | "global", 258 | "globals", 259 | "eslint", 260 | "eslint-disable", 261 | "*package", 262 | "!", 263 | "," 264 | ] 265 | } 266 | ], 267 | "use-isnan": 2, 268 | "valid-typeof": 2, 269 | "wrap-iife": [ 270 | 2, 271 | "any" 272 | ], 273 | "yoda": [ 274 | 2, 275 | "never" 276 | ] 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Enforce Unix newlines 2 | * text eol=lf 3 | 4 | # binaries 5 | *.ai binary 6 | *.psd binary 7 | *.jpg binary 8 | *.gif binary 9 | *.png binary 10 | *.jpeg binary 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.sublime-* 3 | _gh_pages 4 | bower_components 5 | node_modules 6 | npm-debug.log 7 | actual 8 | test/actual 9 | temp 10 | tmp 11 | TODO.md 12 | vendor 13 | .idea 14 | benchmark 15 | coverage 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "stable" 5 | - "4" 6 | - "0.12" 7 | - "0.10" 8 | matrix: 9 | fast_finish: true 10 | allow_failures: 11 | - node_js: "0.10" 12 | -------------------------------------------------------------------------------- /.verb.md: -------------------------------------------------------------------------------- 1 | Also see [collapse-object][], for doing the reverse of this library. 2 | 3 | **Examples** 4 | 5 | ```js 6 | expand('a') 7 | //=> {a: ''} 8 | 9 | expand('a.b') 10 | //=> {a: {b: ''}} 11 | 12 | expand('a|b') 13 | //=> {a: '', b: ''} 14 | 15 | expand('a|b', {toBoolean: true}) 16 | //=> {a: true, b: true} 17 | 18 | expand('a:b') 19 | //=> {a: 'b'} 20 | 21 | expand('a,b') 22 | //=> ['a', 'b'] 23 | ``` 24 | 25 | ### Type casting 26 | 27 | Introduced in v0.2.2, some values are cast to their JavaScript type. 28 | 29 | **Booleans** 30 | 31 | If the value is `"true"` or `"false"` it will be coerced to a boolean value. 32 | 33 | ```js 34 | expand('a:true') 35 | //=> {a: true} 36 | expand('a:false') 37 | //=> {a: false} 38 | ``` 39 | 40 | **Numbers** 41 | 42 | If the value is an integer it will be coerced to a number. 43 | 44 | ```js 45 | expand('a:1') 46 | //=> {a: 1} 47 | expand('a:123') 48 | //=> {a: 123} 49 | ``` 50 | 51 | **Regex** 52 | 53 | If the value is a simple regular expression it will be coerced to a `new RegExp()`. 54 | 55 | ```js 56 | expand('a:/foo/') 57 | //=> {a: /foo/} 58 | expand('a.b.c:/^bar/gmi') 59 | //=> {a: {b: {c: /^bar/gmi}}} 60 | ``` 61 | 62 | ## Install 63 | {%= include("install-npm", {save: true}) %} 64 | 65 | ## CLI 66 | 67 | Usage with cli: 68 | 69 | ```sh 70 | ❯ expand-object --help 71 | 72 | Usage: expand-object [options] 73 | 74 | Expand a string into a JavaScript object using a simple notation. 75 | 76 | Options: 77 | 78 | -h, --help output usage information 79 | -V, --version output the version number 80 | -r, --raw Output as raw javascript object - not stringified 81 | 82 | Examples: 83 | 84 | $ expand-object "a:b" 85 | $ expand-object --raw "a:b" 86 | $ echo "a:b" | expand-object 87 | ``` 88 | 89 | ## node.js 90 | 91 | To use as a node.js library: 92 | 93 | ```js 94 | var expand = require('{%= name %}'); 95 | ``` 96 | 97 | ### children 98 | 99 | > Expand dots into **child objects**: 100 | 101 | ```js 102 | expand('a') 103 | //=> {a: ''} 104 | expand('a.b') 105 | //=> {a: {b: ''}} 106 | expand('a.b.c') 107 | //=> {a: {b: {c: ''}}} 108 | expand('a.b.c.d') 109 | //=> {a: {b: {c: {d: ''}}}} 110 | ``` 111 | 112 | ### siblings 113 | 114 | expand-object supports two kinds of siblings, **general** and **adjacent**. It's much easier to understand the difference in the last example. 115 | 116 | #### general siblings 117 | 118 | > Use pipes (`|`) to expand **general siblings**: 119 | 120 | ```js 121 | expand('a|b') 122 | //=> {a: '', b: ''} 123 | expand('a|b|c') 124 | //=> {a: '', b: '', c: ''} 125 | expand('a|b|c|d') 126 | //=> {a: '', b: '', c: '', d: ''} 127 | expand('a:b|c:d') 128 | //=> {a: 'b', c: 'd'} 129 | ``` 130 | 131 | #### adjacent siblings 132 | 133 | > Use plus (`+`) to expand **adjacent siblings**: 134 | 135 | Adjacent siblings are objects that immediately follow one another. 136 | 137 | ```js 138 | expand('a:b+c:d') 139 | //=> {a: 'b', c: 'd'} 140 | expand('a.b:c+d:e') 141 | //=> {a: {b: 'c', d: 'e'}} 142 | ``` 143 | 144 | #### difference between sibling types 145 | 146 | In the example below: 147 | 148 | - **general**: `d` is a sibling to `a` 149 | - **adjacent**: `d` is a sibling to `b` 150 | 151 | ```js 152 | // general siblings 153 | expand('a.b:c|d:e') 154 | //=> { a: { b: 'c' }, d: 'e' } 155 | 156 | // adjacent siblings 157 | expand('a.b:c+d:e') 158 | //=> { a: { b: 'c', d: 'e' } } 159 | ``` 160 | 161 | ### key-value pairs 162 | 163 | > Expand colons into **key-value pairs**: 164 | 165 | ```js 166 | expand('a:b') 167 | //=> {a: 'b'} 168 | expand('a.b:c') 169 | //=> {a: {b: 'c'}} 170 | expand('a.b.c:d') 171 | //=> {a: {b: {c: 'd'}}} 172 | ``` 173 | 174 | ### arrays 175 | 176 | > Expand comma separated values into **arrays**: 177 | 178 | ```js 179 | expand('a,b') 180 | //=> ['a', 'b'] 181 | expand('a,b,c') 182 | //=> ['a', 'b', 'c'] 183 | expand('a:b,c,d|e:f,g,h') 184 | //=> {a: ['b', 'c', 'd'], e: ['f', 'g', 'h']} 185 | ``` 186 | 187 | ## Usage examples 188 | 189 | Expand siblings with comma separated values into arrays: 190 | 191 | ```js 192 | expand('a:b,c,d|e:f,g,h') 193 | //=> {a: ['b', 'c', 'd'], e: ['f', 'g', 'h']} 194 | ``` 195 | 196 | Expand children with comma separated values into arrays: 197 | 198 | ```js 199 | expand('a.b.c:d,e,f|g.h:i,j,k') 200 | //=> {a: { b: {c: ['d', 'e', 'f']}}, g: {h: ['i', 'j', 'k']}} 201 | ``` 202 | 203 | Expand sibling objects into key-value pairs: 204 | 205 | ```js 206 | expand('a:b|c:d') 207 | //=> {a: 'b', c: 'd'} 208 | expand('a:b|c:d|e:f') 209 | //=> {a: 'b', c: 'd', e: 'f'} 210 | expand('a:b|c:d|e:f|g:h') 211 | //=> {a: 'b', c: 'd', e: 'f', g: 'h'} 212 | ``` 213 | 214 | Expand child objects into key-value pairs: 215 | 216 | ```js 217 | expand('a.b:c') 218 | //=> {a: {b: 'c'}} 219 | expand('a.b.c:d') 220 | //=> {a: {b: {c: 'd'}}} 221 | expand('a.b.c.d:e') 222 | //=> {a: {b: {c: {d: 'e'}}}} 223 | ``` 224 | 225 | Expand sibling and child objects into key-value pairs: 226 | 227 | ```js 228 | expand('a:b|c:d') 229 | //=> {a: 'b', c: 'd'} 230 | expand('a.b.c|d.e:f') 231 | //=> {a: {b: {c: ''}}, d: {e: 'f'}} 232 | expand('a.b:c|d.e:f') 233 | //=> {a: {b: 'c'}, d: {e: 'f'}} 234 | expand('a.b.c:d|e.f.g:h') 235 | //=> {a: {b: {c: 'd'}}, e: {f: {g: 'h'}}} 236 | ``` 237 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015, Jon Schlinkert. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # expand-object [![NPM version](https://img.shields.io/npm/v/expand-object.svg)](https://www.npmjs.com/package/expand-object) [![Build Status](https://img.shields.io/travis/jonschlinkert/expand-object.svg)](https://travis-ci.org/jonschlinkert/expand-object) 2 | 3 | > Expand a string into a JavaScript object using a simple notation. Use the CLI or as a node.js lib. 4 | 5 | - [Install](#install) 6 | * [Type casting](#type-casting) 7 | - [Install](#install-1) 8 | - [CLI](#cli) 9 | - [node.js](#nodejs) 10 | * [children](#children) 11 | * [siblings](#siblings) 12 | + [general siblings](#general-siblings) 13 | + [adjacent siblings](#adjacent-siblings) 14 | + [difference between sibling types](#difference-between-sibling-types) 15 | * [key-value pairs](#key-value-pairs) 16 | * [arrays](#arrays) 17 | - [Usage examples](#usage-examples) 18 | - [Related projects](#related-projects) 19 | - [Running tests](#running-tests) 20 | - [Contributing](#contributing) 21 | - [Author](#author) 22 | - [License](#license) 23 | 24 | _(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_ 25 | 26 | ## Install 27 | 28 | Install with [npm](https://www.npmjs.com/) 29 | 30 | ```sh 31 | $ npm i expand-object --save 32 | ``` 33 | 34 | Also see [collapse-object](https://github.com/jonschlinkert/collapse-object), for doing the reverse of this library. 35 | 36 | **Examples** 37 | 38 | ```js 39 | expand('a') 40 | //=> {a: ''} 41 | 42 | expand('a.b') 43 | //=> {a: {b: ''}} 44 | 45 | expand('a|b') 46 | //=> {a: '', b: ''} 47 | 48 | expand('a|b', {toBoolean: true}) 49 | //=> {a: true, b: true} 50 | 51 | expand('a:b') 52 | //=> {a: 'b'} 53 | 54 | expand('a,b') 55 | //=> ['a', 'b'] 56 | ``` 57 | 58 | ### Type casting 59 | 60 | Introduced in v0.2.2, some values are cast to their JavaScript type. 61 | 62 | **Booleans** 63 | 64 | If the value is `"true"` or `"false"` it will be coerced to a boolean value. 65 | 66 | ```js 67 | expand('a:true') 68 | //=> {a: true} 69 | expand('a:false') 70 | //=> {a: false} 71 | ``` 72 | 73 | **Numbers** 74 | 75 | If the value is an integer it will be coerced to a number. 76 | 77 | ```js 78 | expand('a:1') 79 | //=> {a: 1} 80 | expand('a:123') 81 | //=> {a: 123} 82 | ``` 83 | 84 | **Regex** 85 | 86 | If the value is a simple regular expression it will be coerced to a `new RegExp()`. 87 | 88 | ```js 89 | expand('a:/foo/') 90 | //=> {a: /foo/} 91 | expand('a.b.c:/^bar/gmi') 92 | //=> {a: {b: {c: /^bar/gmi}}} 93 | ``` 94 | 95 | ## Install 96 | 97 | Install with [npm](https://www.npmjs.com/) 98 | 99 | ```sh 100 | $ npm i expand-object --save 101 | ``` 102 | 103 | ## CLI 104 | 105 | Usage with cli: 106 | 107 | ```sh 108 | ❯ expand-object --help 109 | 110 | Usage: expand-object [options] 111 | 112 | Expand a string into a JavaScript object using a simple notation. 113 | 114 | Options: 115 | 116 | -h, --help output usage information 117 | -V, --version output the version number 118 | -r, --raw Output as raw javascript object - not stringified 119 | 120 | Examples: 121 | 122 | $ expand-object "a:b" 123 | $ expand-object --raw "a:b" 124 | $ echo "a:b" | expand-object 125 | ``` 126 | 127 | ## node.js 128 | 129 | To use as a node.js library: 130 | 131 | ```js 132 | var expand = require('expand-object'); 133 | ``` 134 | 135 | ### children 136 | 137 | > Expand dots into **child objects**: 138 | 139 | ```js 140 | expand('a') 141 | //=> {a: ''} 142 | expand('a.b') 143 | //=> {a: {b: ''}} 144 | expand('a.b.c') 145 | //=> {a: {b: {c: ''}}} 146 | expand('a.b.c.d') 147 | //=> {a: {b: {c: {d: ''}}}} 148 | ``` 149 | 150 | ### siblings 151 | 152 | expand-object supports two kinds of siblings, **general** and **adjacent**. It's much easier to understand the difference in the last example. 153 | 154 | #### general siblings 155 | 156 | > Use pipes (`|`) to expand **general siblings**: 157 | 158 | ```js 159 | expand('a|b') 160 | //=> {a: '', b: ''} 161 | expand('a|b|c') 162 | //=> {a: '', b: '', c: ''} 163 | expand('a|b|c|d') 164 | //=> {a: '', b: '', c: '', d: ''} 165 | expand('a:b|c:d') 166 | //=> {a: 'b', c: 'd'} 167 | ``` 168 | 169 | #### adjacent siblings 170 | 171 | > Use plus (`+`) to expand **adjacent siblings**: 172 | 173 | Adjacent siblings are objects that immediately follow one another. 174 | 175 | ```js 176 | expand('a:b+c:d') 177 | //=> {a: 'b', c: 'd'} 178 | expand('a.b:c+d:e') 179 | //=> {a: {b: 'c', d: 'e'}} 180 | ``` 181 | 182 | #### difference between sibling types 183 | 184 | In the example below: 185 | 186 | * **general**: `d` is a sibling to `a` 187 | * **adjacent**: `d` is a sibling to `b` 188 | 189 | ```js 190 | // general siblings 191 | expand('a.b:c|d:e') 192 | //=> { a: { b: 'c' }, d: 'e' } 193 | 194 | // adjacent siblings 195 | expand('a.b:c+d:e') 196 | //=> { a: { b: 'c', d: 'e' } } 197 | ``` 198 | 199 | ### key-value pairs 200 | 201 | > Expand colons into **key-value pairs**: 202 | 203 | ```js 204 | expand('a:b') 205 | //=> {a: 'b'} 206 | expand('a.b:c') 207 | //=> {a: {b: 'c'}} 208 | expand('a.b.c:d') 209 | //=> {a: {b: {c: 'd'}}} 210 | ``` 211 | 212 | ### arrays 213 | 214 | > Expand comma separated values into **arrays**: 215 | 216 | ```js 217 | expand('a,b') 218 | //=> ['a', 'b'] 219 | expand('a,b,c') 220 | //=> ['a', 'b', 'c'] 221 | expand('a:b,c,d|e:f,g,h') 222 | //=> {a: ['b', 'c', 'd'], e: ['f', 'g', 'h']} 223 | ``` 224 | 225 | ## Usage examples 226 | 227 | Expand siblings with comma separated values into arrays: 228 | 229 | ```js 230 | expand('a:b,c,d|e:f,g,h') 231 | //=> {a: ['b', 'c', 'd'], e: ['f', 'g', 'h']} 232 | ``` 233 | 234 | Expand children with comma separated values into arrays: 235 | 236 | ```js 237 | expand('a.b.c:d,e,f|g.h:i,j,k') 238 | //=> {a: { b: {c: ['d', 'e', 'f']}}, g: {h: ['i', 'j', 'k']}} 239 | ``` 240 | 241 | Expand sibling objects into key-value pairs: 242 | 243 | ```js 244 | expand('a:b|c:d') 245 | //=> {a: 'b', c: 'd'} 246 | expand('a:b|c:d|e:f') 247 | //=> {a: 'b', c: 'd', e: 'f'} 248 | expand('a:b|c:d|e:f|g:h') 249 | //=> {a: 'b', c: 'd', e: 'f', g: 'h'} 250 | ``` 251 | 252 | Expand child objects into key-value pairs: 253 | 254 | ```js 255 | expand('a.b:c') 256 | //=> {a: {b: 'c'}} 257 | expand('a.b.c:d') 258 | //=> {a: {b: {c: 'd'}}} 259 | expand('a.b.c.d:e') 260 | //=> {a: {b: {c: {d: 'e'}}}} 261 | ``` 262 | 263 | Expand sibling and child objects into key-value pairs: 264 | 265 | ```js 266 | expand('a:b|c:d') 267 | //=> {a: 'b', c: 'd'} 268 | expand('a.b.c|d.e:f') 269 | //=> {a: {b: {c: ''}}, d: {e: 'f'}} 270 | expand('a.b:c|d.e:f') 271 | //=> {a: {b: 'c'}, d: {e: 'f'}} 272 | expand('a.b.c:d|e.f.g:h') 273 | //=> {a: {b: {c: 'd'}}, e: {f: {g: 'h'}}} 274 | ``` 275 | 276 | ## Related projects 277 | 278 | [expand-args](https://www.npmjs.com/package/expand-args): Expand parsed command line arguments using expand-object. | [homepage](https://github.com/jonschlinkert/expand-args) 279 | 280 | ## Running tests 281 | 282 | Install dev dependencies: 283 | 284 | ```sh 285 | $ npm i -d && npm test 286 | ``` 287 | 288 | ## Contributing 289 | 290 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/expand-object/issues/new). 291 | 292 | ## Author 293 | 294 | **Jon Schlinkert** 295 | 296 | * [github/jonschlinkert](https://github.com/jonschlinkert) 297 | * [twitter/jonschlinkert](http://twitter.com/jonschlinkert) 298 | 299 | ## License 300 | 301 | Copyright © 2015 [Jon Schlinkert](https://github.com/jonschlinkert) 302 | Released under the MIT license. 303 | 304 | *** 305 | 306 | _This file was generated by [verb](https://github.com/verbose/verb) on December 23, 2015._ -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | process.title = 'expand-object'; 6 | 7 | var argv = require('minimist')(process.argv.slice(2)); 8 | var stdin = require('get-stdin'); 9 | var expand = require('./index'); 10 | var pkg = require('./package.json'); 11 | 12 | function help() { 13 | console.log(); 14 | console.log(' ' + pkg.description); 15 | console.log(); 16 | console.log(' Usage: cli [options] '); 17 | console.log(); 18 | console.log(' -h, --help output usage information'); 19 | console.log(' -V, --version output the version number'); 20 | console.log(' -r, --raw output as raw javascript object - don\'t stringify'); 21 | console.log(); 22 | console.log(' Examples:'); 23 | console.log(''); 24 | console.log(' $ expand-object "a:b"'); 25 | console.log(' $ expand-object --raw "a:b"'); 26 | console.log(' $ echo "a:b" | expand-object'); 27 | console.log(''); 28 | } 29 | 30 | if (argv._.length === 0 && Object.keys(argv).length === 1 || argv.help) { 31 | help(); 32 | } 33 | 34 | function run(contents) { 35 | var output = expand(contents); 36 | if (!argv.raw) { 37 | output = JSON.stringify(output); 38 | } 39 | 40 | console.log(output); 41 | process.exit(0); 42 | } 43 | 44 | if (!process.stdin.isTTY) { 45 | stdin(function(contents) { 46 | run(contents); 47 | }); 48 | } else { 49 | run(argv._[0] || ''); 50 | } 51 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isNumber = require('is-number'); 4 | var set = require('set-value'); 5 | 6 | /** 7 | * Expand the given string into an object. 8 | * 9 | * @param {String} `str` 10 | * @return {Object} 11 | */ 12 | 13 | function expand(str, opts) { 14 | opts = opts || {}; 15 | 16 | if (typeof str !== 'string') { 17 | throw new TypeError('expand-object expects a string.'); 18 | } 19 | 20 | if (!/[.|:=]/.test(str) && /,/.test(str)) { 21 | return toArray(str); 22 | } 23 | 24 | var m; 25 | if ((m = /(\w+[:=]\w+\.)+/.exec(str)) && !/[|,+]/.test(str)) { 26 | var val = m[0].split(':').join('.'); 27 | str = val + str.slice(m[0].length); 28 | } 29 | 30 | var arr = splitString(str, '|'); 31 | var len = arr.length, i = -1; 32 | var res = {}; 33 | 34 | if (isArrayLike(str) && arr.length === 1) { 35 | return expandArrayObj(str, opts); 36 | } 37 | 38 | while (++i < len) { 39 | var val = arr[i]; 40 | // test for `https://foo` 41 | if (/\w:\/\/\w/.test(val)) { 42 | res[val] = ''; 43 | continue; 44 | } 45 | 46 | var re = /^((?:\w+)\.(?:\w+))[:.]((?:\w+,)+)+((?:\w+):(?:\w+))/; 47 | var m = re.exec(val); 48 | if (m && m[1] && m[2] && m[3]) { 49 | var arrVal = m[2]; 50 | arrVal = arrVal.replace(/,$/, ''); 51 | var prop = arrVal.split(','); 52 | prop = prop.concat(toObject(m[3])); 53 | res = set(res, m[1], prop); 54 | } else if (!/[.,\|:=]/.test(val)) { 55 | res[val] = opts.toBoolean ? true : ''; 56 | } else { 57 | res = expandObject(res, val, opts); 58 | } 59 | } 60 | return res; 61 | } 62 | 63 | function setValue(obj, a, b, opts) { 64 | var val = resolveValue(b, opts); 65 | if (~String(a).indexOf('.')) { 66 | return set(obj, a, typeCast(val)); 67 | } else { 68 | obj[a] = typeCast(val); 69 | } 70 | return obj; 71 | } 72 | 73 | function resolveValue(val, opts) { 74 | opts = opts || {}; 75 | if (typeof val === 'undefined') { 76 | return opts.toBoolean ? true : ''; 77 | } 78 | if (typeof val === 'string' && ~val.indexOf(',')) { 79 | val = toArray(val); 80 | } 81 | 82 | if (Array.isArray(val)) { 83 | return val.map(function (ele) { 84 | if (~String(ele).indexOf('.')) { 85 | return setValue({}, ele, opts.toBoolean ? true : ''); 86 | } 87 | return ele; 88 | }); 89 | } 90 | return val; 91 | } 92 | 93 | function expandArray(str, opts) { 94 | var segs = String(str).split(/[:=]/); 95 | var key = segs.shift(); 96 | var res = {}, val = []; 97 | 98 | segs.forEach(function (seg) { 99 | val = val.concat(resolveValue(toArray(seg), opts)); 100 | }); 101 | 102 | res[key] = val; 103 | return res; 104 | } 105 | 106 | function toArray(str) { 107 | return (str || '').split(',').reduce(function (acc, val) { 108 | if (typeof val !== 'undefined' && val !== '') { 109 | acc.push(typeCast(val)); 110 | } 111 | return acc; 112 | }, []); 113 | } 114 | 115 | function expandSiblings(segs, opts) { 116 | var first = segs.shift(); 117 | var parts = first.split('.'); 118 | var arr = [parts.pop()].concat(segs); 119 | var key = parts.join('.'); 120 | var siblings = {}; 121 | 122 | var val = arr.reduce(function (acc, val) { 123 | expandObject(acc, val, opts); 124 | return acc; 125 | }, {}); 126 | 127 | if (!key) return val; 128 | set(siblings, key, val); 129 | return siblings; 130 | } 131 | 132 | function expandObject(res, str, opts) { 133 | var segs = splitString(str, '+'); 134 | if (segs.length > 1) { 135 | return expandSiblings(segs, opts); 136 | } 137 | 138 | segs = str.split(/[:=]/); 139 | setValue(res, segs[0], segs[1]); 140 | return res; 141 | } 142 | 143 | function expandArrayObj(str, opts) { 144 | var m = /\w+:.*?:/.exec(str); 145 | if (!m) return expandArray(str, opts); 146 | 147 | var i = str.indexOf(':'); 148 | var key = str.slice(0, i); 149 | var val = str.slice(i + 1); 150 | 151 | if (/\w+,\w+,/.test(val)) { 152 | var obj = {}; 153 | obj[key] = toArray(val).map(function (ele) { 154 | return ~ele.indexOf(':') 155 | ? expandObject({}, ele, opts) 156 | : ele; 157 | }); 158 | return obj; 159 | } 160 | 161 | return toArray(str).map(function (ele) { 162 | return expandObject({}, ele, opts); 163 | }); 164 | } 165 | 166 | function typeCast(val) { 167 | if (val === 'true') { 168 | return true; 169 | } 170 | if (val === 'false') { 171 | return false; 172 | } 173 | if (isNumber(val)) { 174 | return +val; 175 | } 176 | return val; 177 | } 178 | 179 | function isArrayLike(str) { 180 | return typeof str === 'string' && /^(?:(\w+[:=]\w+[,:])+)+/.exec(str); 181 | } 182 | 183 | function toObject(val) { 184 | var obj = {}; 185 | var segs = String(val).split(/[:=]/); 186 | obj[segs[0]] = typeCast(segs[1]); 187 | return obj; 188 | } 189 | 190 | function splitString(str, ch) { 191 | str = String(str); 192 | 193 | var segs = str.split(ch); 194 | var len = segs.length; 195 | var res = []; 196 | var i = -1; 197 | 198 | while (++i < len) { 199 | var key = segs[i]; 200 | while (key[key.length - 1] === '\\') { 201 | key = key.slice(0, -1) + ch + segs[++i]; 202 | } 203 | res.push(key); 204 | } 205 | return res; 206 | } 207 | 208 | /** 209 | * Expose `expand` 210 | */ 211 | 212 | module.exports = expand; 213 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expand-object", 3 | "description": "Expand a string into a JavaScript object using a simple notation. Use the CLI or as a node.js lib.", 4 | "version": "0.4.2", 5 | "homepage": "https://github.com/jonschlinkert/expand-object", 6 | "author": "Jon Schlinkert (https://github.com/jonschlinkert)", 7 | "repository": "jonschlinkert/expand-object", 8 | "bugs": { 9 | "bugs": "https://github.com/jonschlinkert/expand-object/issues" 10 | }, 11 | "license": "MIT", 12 | "files": [ 13 | "cli.js", 14 | "index.js" 15 | ], 16 | "main": "index.js", 17 | "engines": { 18 | "node": ">=0.10.0" 19 | }, 20 | "scripts": { 21 | "test": "mocha" 22 | }, 23 | "bin": { 24 | "expand-object": "cli.js" 25 | }, 26 | "dependencies": { 27 | "get-stdin": "^5.0.1", 28 | "is-number": "^2.1.0", 29 | "minimist": "^1.2.0", 30 | "set-value": "^0.3.3" 31 | }, 32 | "devDependencies": { 33 | "gulp-format-md": "^0.1.5", 34 | "mocha": "*" 35 | }, 36 | "keywords": [ 37 | "get", 38 | "has", 39 | "hasown", 40 | "key", 41 | "keys", 42 | "nested", 43 | "notation", 44 | "object", 45 | "prop", 46 | "properties", 47 | "property", 48 | "props", 49 | "set", 50 | "value", 51 | "values" 52 | ], 53 | "verb": { 54 | "related": { 55 | "list": "expand-args" 56 | }, 57 | "reflinks": [ 58 | "collapse-object" 59 | ], 60 | "layout": "default", 61 | "plugins": [ 62 | "gulp-format-md" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * expand-object 3 | * 4 | * Copyright (c) 2015 Jon Schlinkert. 5 | * Licensed under the MIT license. 6 | */ 7 | 8 | 'use strict'; 9 | 10 | require('mocha'); 11 | var assert = require('assert'); 12 | var expand = require('./'); 13 | 14 | function eql(a, b) { 15 | return assert.deepEqual(a, b); 16 | } 17 | 18 | describe('expand', function() { 19 | it('should expand dots into child objects:', function() { 20 | eql(expand('a'), {a: ''}); 21 | eql(expand('a.b'), {a: {b: ''}}); 22 | eql(expand('a.b.c'), {a: {b: {c: ''}}}); 23 | eql(expand('a.b.c.d'), {a: {b: {c: {d: ''}}}}); 24 | }); 25 | 26 | it('should expand pipes into sibling objects:', function() { 27 | eql(expand('a|b'), {a: '', b: ''}); 28 | eql(expand('a|b|c'), {a: '', b: '', c: ''}); 29 | eql(expand('a|b|c|d'), {a: '', b: '', c: '', d: ''}); 30 | }); 31 | 32 | it('should set undefined values as `true` when `toBoolean` is defined:', function() { 33 | eql(expand('a|b', {toBoolean: true}), {a: true, b: true}); 34 | eql(expand('a|b|c', {toBoolean: true}), {a: true, b: true, c: true}); 35 | eql(expand('a|b|c|d', {toBoolean: true}), {a: true, b: true, c: true, d: true}); 36 | }); 37 | 38 | it('should expand colons into key-value pairs:', function() { 39 | eql(expand('a:b'), {a: 'b'}); 40 | eql(expand('a.b:c'), {a: {b: 'c'}}); 41 | eql(expand('a.b.c:d'), {a: {b: {c: 'd'}}}); 42 | }); 43 | 44 | it('should expand sibling objects into key-value pairs:', function() { 45 | eql(expand('a:b|c:d'), {a: 'b', c: 'd'}); 46 | eql(expand('a:b|c:d'), {a: 'b', c: 'd'}); 47 | eql(expand('a:b|c:d|e:f'), {a: 'b', c: 'd', e: 'f'}); 48 | eql(expand('a:b|c:d|e:f|g:h'), {a: 'b', c: 'd', e: 'f', g: 'h'}); 49 | }); 50 | 51 | it('should expand child objects into key-value pairs:', function() { 52 | eql(expand('a.b:c'), {a: {b: 'c'}}); 53 | eql(expand('a.b.c:d'), {a: {b: {c: 'd'}}}); 54 | eql(expand('a.b.c.d:e'), {a: {b: {c: {d: 'e'}}}}); 55 | eql(expand('a:b.c.d:e'), {a: {b: {c: {d: 'e'}}}}); 56 | eql(expand('a:b.c.d.e.f:g'), {a: {b: {c: {d: {e: {f: 'g'}}}}}}); 57 | }); 58 | 59 | it('should expand sibling and child objects into key-value pairs:', function() { 60 | eql(expand('a:b|c:d'), {a: 'b', c: 'd'}); 61 | eql(expand('a.b.c|d.e:f'), {a: {b: {c: ''}}, d: {e: 'f'}}); 62 | eql(expand('a.b:c|d.e:f'), {a: {b: 'c'}, d: {e: 'f'}}); 63 | eql(expand('a.b.c:d|e.f.g:h'), {a: {b: {c: 'd'}}, e: {f: {g: 'h'}}}); 64 | }); 65 | 66 | it('should expand flags:', function() { 67 | eql(expand('a=b|c:d'), {a: 'b', c: 'd'}); 68 | eql(expand('a.b.c=d'), {a: {b: {c: 'd'}}}); 69 | }); 70 | 71 | it('should expand comma separated values into arrays:', function() { 72 | eql(expand('a,b'), ['a', 'b']); 73 | eql(expand('a,b,c'), ['a', 'b', 'c']); 74 | eql(expand('a,b,c,'), ['a', 'b', 'c']); 75 | }); 76 | 77 | it('should expand siblings with comma separated values into arrays:', function() { 78 | eql(expand('a:b,c,d|e:f,g,h'), {a: ['b', 'c', 'd'], e: ['f', 'g', 'h']}); 79 | }); 80 | 81 | it('should expand children with comma separated values into arrays:', function() { 82 | eql(expand('a.b.c:d,e,f|g.h:i,j,k'), { a: { b: { c: [ 'd', 'e', 'f' ] } }, g: { h: [ 'i', 'j', 'k' ] } }); 83 | }); 84 | 85 | it('should expand an array of objects:', function() { 86 | eql(expand('foo:bar,baz,a:b'), {foo: ['bar', 'baz', {a: 'b'}]}); 87 | eql(expand('a:b,c:d,e:f'), [{a: 'b'}, {c: 'd'}, {e: 'f'}]); 88 | eql(expand('a.b:c.d,e.f:g,h:i'), {a: {b: [{c: {d: '' }}, {e: {f: '' } }] }}); 89 | // maybe that last one should be more like this?: 90 | // eql(expand('a:c.d,e:f'), { a: {c: ['d', {e: 'f'}] }}); 91 | }); 92 | 93 | it('should expand objects with array values:', function() { 94 | eql(expand('a:b,'), {a: ['b']}); 95 | eql(expand('a:b,c'), {a: ['b', 'c']}); 96 | eql(expand('a:c.d,e'), { a: [ { c: { d: '' } }, 'e' ]}); 97 | eql(expand('a:b,c,d'), {a: ['b', 'c', 'd']}); 98 | eql(expand('a:b,c,d|e:f,g,i'), {a: ['b', 'c', 'd'], e: ['f', 'g', 'i']}); 99 | }); 100 | 101 | it('should expand objects with sibling:', function() { 102 | eql(expand('a:b+c:d'), {a: 'b', c: 'd'}); 103 | eql(expand('a:b+c:d+e:f'), {a: 'b', c: 'd', e: 'f'}); 104 | eql(expand('a.b:c+d:e+f:g'), {a: {b: 'c', d: 'e', f: 'g'}}); 105 | eql(expand('a.b:c|d:e|f:g'), {a: {b: 'c'}, d: 'e', f: 'g'}); 106 | eql(expand('a.b.c:d+e:f+g:h'), {a: {b: {c: 'd', e: 'f', g: 'h'}}}); 107 | }); 108 | 109 | it('should type-cast booleans:', function() { 110 | eql(expand('a:true'), {a: true}); 111 | eql(expand('a.b:true'), {a: {b: true}}); 112 | eql(expand('a.b:false'), {a: {b: false}}); 113 | 114 | eql(expand('a,true'), ['a', true]); 115 | eql(expand('a,false'), ['a', false]); 116 | }); 117 | 118 | it('should type-cast numbers:', function() { 119 | eql(expand('a:5'), {a: 5}); 120 | eql(expand('a.b:5'), {a: {b: 5}}); 121 | eql(expand('a.b:9'), {a: {b: 9}}); 122 | 123 | eql(expand('a,5'), ['a', 5]); 124 | eql(expand('a,9'), ['a', 9]); 125 | eql(expand('1,2,3,4,5'), [1,2,3,4,5]); 126 | }); 127 | 128 | it('misc:', function() { 129 | eql(expand('a.b.c.d:x,y'), {a: { b: {c: { d: [ 'x', 'y' ] } }}}); 130 | }); 131 | 132 | 133 | it('should throw an error:', function() { 134 | assert.throws(function() { 135 | expand(); 136 | }, 'expand-object expects a string.'); 137 | }); 138 | }); 139 | 140 | describe('urls:', function() { 141 | it('should not split on colon in urls:', function() { 142 | eql(expand('https://foo.com'), {'https://foo.com': ''}); 143 | }); 144 | }); 145 | 146 | describe('escaping', function() { 147 | it('should support escaped pipes:', function() { 148 | eql(expand('a\\|b'), {'a|b': ''}); 149 | eql(expand('a\\|b|c|d'), {'a|b': '', c: '', d: ''}); 150 | }); 151 | 152 | it('should not choke on file paths:', function() { 153 | eql(expand('/a/b/c'), {'/a/b/c': ''}); 154 | eql(expand('/abc'), {'/abc': ''}); 155 | eql(expand('/abc,'), ['/abc']); 156 | }); 157 | 158 | it('should support escape dots:', function() { 159 | eql(expand('a\\.b'), {'a.b': ''}); 160 | eql(expand('a\\.b\\.c'), {'a.b.c': ''}); 161 | eql(expand('a\\.b\\.c\\.d'), {'a.b.c.d': ''}); 162 | 163 | eql(expand('a\\.b.c'), {'a.b': {c: ''}}); 164 | eql(expand('a\\.b.c.d'), {'a.b': {c: {d: ''}}}); 165 | eql(expand('a\\.b.c.d\\.e'), {'a.b': {c: {'d.e': ''}}}); 166 | eql(expand('a\\.b.c\\.d'), {'a.b': {'c.d': ''}}); 167 | }); 168 | }); 169 | --------------------------------------------------------------------------------