hocon 1.1.3 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -18,6 +18,12 @@ you may still run into some issues. If you find a problem, feel free to open a g
18
18
  The implementation is intended to be as close to a line-for-line port as the two languages allow,
19
19
  in hopes of making it fairly easy to port over new changesets from the Java code base over time.
20
20
 
21
+ Support
22
+ =======
23
+
24
+ For best results, if you find an issue with this library, please open an issue on our [Jira issue tracker](https://tickets.puppetlabs.com/browse/HC). Issues filed there tend to be more visible to the current maintainers than issues on the Github issue tracker.
25
+
26
+
21
27
  Basic Usage
22
28
  ===========
23
29
 
@@ -72,6 +78,123 @@ Note that a `ConfigDocument` is used primarily for simple configuration manipula
72
78
  whitespace and comments. As such, it is not powerful as the regular `Config` API, and will not resolve
73
79
  substitutions.
74
80
 
81
+ CLI Tool
82
+ ========
83
+ The `hocon` gem comes bundles with a `hocon` command line tool which can be used to get and set values from hocon files
84
+
85
+ ```
86
+ Usage: hocon [options] {get,set,unset} PATH [VALUE]
87
+
88
+ Example usages:
89
+ hocon -i settings.conf -o new_settings.conf set some.nested.value 42
90
+ hocon -f settings.conf set some.nested.value 42
91
+ cat settings.conf | hocon get some.nested.value
92
+
93
+ Subcommands:
94
+ get PATH - Returns the value at the given path
95
+ set PATH VALUE - Sets or adds the given value at the given path
96
+ unset PATH - Removes the value at the given path
97
+
98
+ Options:
99
+ -i, --in-file HOCON_FILE HOCON file to read/modify. If omitted, STDIN assumed
100
+ -o, --out-file HOCON_FILE File to be written to. If omitted, STDOUT assumed
101
+ -f, --file HOCON_FILE File to read/write to. Equivalent to setting -i/-o to the same file
102
+ -j, --json Output values from the 'get' subcommand in json format
103
+ -h, --help Show this message
104
+ -v, --version Show version
105
+ ```
106
+
107
+ CLI Examples
108
+ --------
109
+ ### Basic Usage
110
+ ```
111
+ $ cat settings.conf
112
+ {
113
+ foo: bar
114
+ }
115
+
116
+ $ hocon -i settings.conf get foo
117
+ bar
118
+
119
+ $ hocon -i settings.conf set foo.baz
120
+ {
121
+ foo: baz
122
+ }
123
+
124
+ # Write to a different file
125
+ $ hocon -i settings.conf -o new_settings.conf set some.nested.value 42
126
+ $ cat new_settings.conf
127
+ {
128
+ foo: bar
129
+ some: {
130
+ nested: {
131
+ value: 42
132
+
133
+ }
134
+ }
135
+ }
136
+
137
+ # Write back to the same file
138
+ $ hocon -f settings.conf set some.nested.value 42
139
+ $ cat settings.conf
140
+ {
141
+ foo: bar
142
+ some: {
143
+ nested: {
144
+ value: 42
145
+
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ ### Complex Values
152
+ If you give `set` a properly formatted hocon dictionary or array, it will try to accept it
153
+
154
+ ```
155
+ $ hocon -i settings.conf set foo "{one: [1, 2, 3], two: {hello: world}}"
156
+ {
157
+ foo: {one: [1, 2, 3], two: {hello: world}}
158
+ }
159
+ ```
160
+
161
+ ### Chaining
162
+ If `--in-file` or `--out-file` aren't specified, STDIN and STDOUT are used for the missing options. Therefore it's possible to chain `hocon` calls
163
+
164
+ ```
165
+ $ cat settings.conf
166
+ {
167
+ foo: bar
168
+ }
169
+
170
+ $ cat settings.conf | hocon set foo 42 | hocon set one.two three
171
+ {
172
+ foo: 42
173
+ one: {
174
+ two: three
175
+ }
176
+ }
177
+ ```
178
+
179
+ ### JSON Output
180
+ Calls to the `get` subcommand will return the data in HOCON format by default, but setting the `-j/--json` flag will cause it to return a valid JSON object
181
+
182
+ ```
183
+ $ cat settings.conf
184
+ foo: {
185
+ bar: {
186
+ baz: 42
187
+ }
188
+ }
189
+
190
+ $ hocon -i settings.conf get foo --json
191
+ {
192
+ "bar": {
193
+ "baz": 42
194
+ }
195
+ }
196
+ ```
197
+
75
198
  Testing
76
199
  =======
77
200
 
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require 'hocon/cli'
3
+
4
+ Hocon::CLI.main(Hocon::CLI.parse_args(ARGV))
5
+
@@ -0,0 +1,225 @@
1
+ require 'optparse'
2
+ require 'hocon'
3
+ require 'hocon/version'
4
+ require 'hocon/config_render_options'
5
+ require 'hocon/config_factory'
6
+ require 'hocon/config_value_factory'
7
+ require 'hocon/parser/config_document_factory'
8
+ require 'hocon/config_error'
9
+
10
+ module Hocon::CLI
11
+ # Aliases
12
+ ConfigMissingError = Hocon::ConfigError::ConfigMissingError
13
+ ConfigWrongTypeError = Hocon::ConfigError::ConfigWrongTypeError
14
+
15
+ # List of valid subcommands
16
+ SUBCOMMANDS = ['get', 'set', 'unset']
17
+
18
+ # For when a path can't be found in a hocon config
19
+ class MissingPathError < StandardError
20
+ end
21
+
22
+ # Parses the command line flags and argument
23
+ # Returns a options hash with values for each option and argument
24
+ def self.parse_args(args)
25
+ options = {}
26
+ opt_parser = OptionParser.new do |opts|
27
+ subcommands = SUBCOMMANDS.join(',')
28
+ opts.banner = "Usage: hocon [options] {#{subcommands}} PATH [VALUE]\n\n" +
29
+ "Example usages:\n" +
30
+ " hocon -i settings.conf -o new_settings.conf set some.nested.value 42\n" +
31
+ " hocon -f settings.conf set some.nested.value 42\n" +
32
+ " cat settings.conf | hocon get some.nested.value\n\n" +
33
+ "Subcommands:\n" +
34
+ " get PATH - Returns the value at the given path\n" +
35
+ " set PATH VALUE - Sets or adds the given value at the given path\n" +
36
+ " unset PATH - Removes the value at the given path"
37
+
38
+ opts.separator('')
39
+ opts.separator('Options:')
40
+
41
+ in_file_description = 'HOCON file to read/modify. If omitted, STDIN assumed'
42
+ opts.on('-i', '--in-file HOCON_FILE', in_file_description) do |in_file|
43
+ options[:in_file] = in_file
44
+ end
45
+
46
+ out_file_description = 'File to be written to. If omitted, STDOUT assumed'
47
+ opts.on('-o', '--out-file HOCON_FILE', out_file_description) do |out_file|
48
+ options[:out_file] = out_file
49
+ end
50
+
51
+ file_description = 'File to read/write to. Equivalent to setting -i/-o to the same file'
52
+ opts.on('-f', '--file HOCON_FILE', file_description) do |file|
53
+ options[:file] = file
54
+ end
55
+
56
+ json_description = "Output values from the 'get' subcommand in json format"
57
+ opts.on('-j', '--json', json_description) do |json|
58
+ options[:json] = json
59
+ end
60
+
61
+ opts.on_tail('-h', '--help', 'Show this message') do
62
+ puts opts
63
+ exit
64
+ end
65
+
66
+ opts.on_tail('-v', '--version', 'Show version') do
67
+ puts Hocon::Version::STRING
68
+ exit
69
+ end
70
+ end
71
+ # parse! returns the argument list minus all the flags it found
72
+ remaining_args = opt_parser.parse!(args)
73
+
74
+ # Ensure -i and -o aren't used at the same time as -f
75
+ if (options[:in_file] || options[:out_file]) && options[:file]
76
+ exit_with_usage_and_error(opt_parser, "--file can't be used with --in-file or --out-file")
77
+ end
78
+
79
+ # If --file is used, set --in/out-file to the same file
80
+ if options[:file]
81
+ options[:in_file] = options[:file]
82
+ options[:out_file] = options[:file]
83
+ end
84
+
85
+ no_subcommand_error(opt_parser) unless remaining_args.size > 0
86
+
87
+ # Assume the first arg is the subcommand
88
+ subcommand = remaining_args.shift
89
+ options[:subcommand] = subcommand
90
+
91
+ case subcommand
92
+ when 'set'
93
+ subcommand_arguments_error(subcommand, opt_parser) unless remaining_args.size >= 2
94
+ options[:path] = remaining_args.shift
95
+ options[:new_value] = remaining_args.shift
96
+
97
+ when 'get', 'unset'
98
+ subcommand_arguments_error(subcommand, opt_parser) unless remaining_args.size >= 1
99
+ options[:path] = remaining_args.shift
100
+
101
+ else
102
+ invalid_subcommand_error(subcommand, opt_parser)
103
+ end
104
+
105
+ options
106
+ end
107
+
108
+ # Main entry point into the script
109
+ # Calls the appropriate subcommand and handles errors raised from the subcommands
110
+ def self.main(opts)
111
+ hocon_text = get_hocon_file(opts[:in_file])
112
+
113
+ begin
114
+ case opts[:subcommand]
115
+ when 'get'
116
+ puts do_get(opts, hocon_text)
117
+ when 'set'
118
+ print_or_write(do_set(opts, hocon_text), opts[:out_file])
119
+ when 'unset'
120
+ print_or_write(do_unset(opts, hocon_text), opts[:out_file])
121
+ end
122
+
123
+ rescue MissingPathError
124
+ exit_with_error("Can't find the given path: '#{opts[:path]}'")
125
+ end
126
+
127
+ exit
128
+ end
129
+
130
+ # Entry point for the 'get' subcommand
131
+ # Returns a string representation of the the value at the path given on the
132
+ # command line
133
+ def self.do_get(opts, hocon_text)
134
+ config = Hocon::ConfigFactory.parse_string(hocon_text)
135
+ unless config.has_path?(opts[:path])
136
+ raise MissingPathError.new
137
+ end
138
+
139
+ value = config.get_any_ref(opts[:path])
140
+
141
+ render_options = Hocon::ConfigRenderOptions.defaults
142
+ # Otherwise weird comments show up in the output
143
+ render_options.origin_comments = false
144
+ # If json is false, the hocon format is used
145
+ render_options.json = opts[:json]
146
+ # Output colons between keys and values
147
+ render_options.key_value_separator = :colon
148
+
149
+ Hocon::ConfigValueFactory.from_any_ref(value).render(render_options)
150
+ end
151
+
152
+ # Entry point for the 'set' subcommand
153
+ # Returns a string representation of the HOCON config after adding/replacing
154
+ # the value at the given path with the given value
155
+ def self.do_set(opts, hocon_text)
156
+ config_doc = Hocon::Parser::ConfigDocumentFactory.parse_string(hocon_text)
157
+ modified_config_doc = config_doc.set_value(opts[:path], opts[:new_value])
158
+
159
+ modified_config_doc.render
160
+ end
161
+
162
+ # Entry point for the 'unset' subcommand
163
+ # Returns a string representation of the HOCON config after removing the
164
+ # value at the given path
165
+ def self.do_unset(opts, hocon_text)
166
+ config_doc = Hocon::Parser::ConfigDocumentFactory.parse_string(hocon_text)
167
+ unless config_doc.has_value?(opts[:path])
168
+ raise MissingPathError.new
169
+ end
170
+
171
+ modified_config_doc = config_doc.remove_value(opts[:path])
172
+
173
+ modified_config_doc.render
174
+ end
175
+
176
+ # If a file is provided, return it's contents. Otherwise read from STDIN
177
+ def self.get_hocon_file(in_file)
178
+ if in_file
179
+ File.read(in_file)
180
+ else
181
+ STDIN.read
182
+ end
183
+ end
184
+
185
+ # Print an error message and exit the program
186
+ def self.exit_with_error(message)
187
+ STDERR.puts "Error: #{message}"
188
+ exit(1)
189
+ end
190
+
191
+ # Print an error message and usage, then exit the program
192
+ def self.exit_with_usage_and_error(opt_parser, message)
193
+ STDERR.puts opt_parser
194
+ exit_with_error(message)
195
+ end
196
+
197
+ # Exits with an error saying there aren't enough arguments found for a given
198
+ # subcommand. Prints the usage
199
+ def self.subcommand_arguments_error(subcommand, opt_parser)
200
+ error_message = "Too few arguments for '#{subcommand}' subcommand"
201
+ exit_with_usage_and_error(opt_parser, error_message)
202
+ end
203
+
204
+ # Exits with an error for when no subcommand is supplied on the command line.
205
+ # Prints the usage
206
+ def self.no_subcommand_error(opt_parser)
207
+ error_message = "Must specify subcommand from [#{SUBCOMMANDS.join(', ')}]"
208
+ exit_with_usage_and_error(opt_parser, error_message)
209
+ end
210
+
211
+ # Exits with an error for when a subcommand doesn't exist. Prints the usage
212
+ def self.invalid_subcommand_error(subcommand, opt_parser)
213
+ error_message = "Invalid subcommand '#{subcommand}', must be one of [#{SUBCOMMANDS.join(', ')}]"
214
+ exit_with_usage_and_error(opt_parser, error_message)
215
+ end
216
+
217
+ # If out_file is not nil, write to that file. Otherwise print to STDOUT
218
+ def self.print_or_write(string, out_file)
219
+ if out_file
220
+ File.open(out_file, 'w') { |file| file.write(string) }
221
+ else
222
+ puts string
223
+ end
224
+ end
225
+ end
@@ -3,14 +3,15 @@
3
3
  require 'hocon'
4
4
 
5
5
  class Hocon::ConfigRenderOptions
6
- def initialize(origin_comments, comments, formatted, json)
6
+ def initialize(origin_comments, comments, formatted, json, key_value_separator=:equals)
7
7
  @origin_comments = origin_comments
8
8
  @comments = comments
9
9
  @formatted = formatted
10
10
  @json = json
11
+ @key_value_separator = key_value_separator
11
12
  end
12
13
 
13
- attr_accessor :origin_comments, :comments, :formatted, :json
14
+ attr_accessor :origin_comments, :comments, :formatted, :json, :key_value_separator
14
15
 
15
16
  def origin_comments?
16
17
  @origin_comments
@@ -314,20 +314,17 @@ module Hocon::Impl::AbstractConfigValue
314
314
 
315
315
  if options.json?
316
316
  if options.formatted?
317
- sb << " : "
317
+ sb << ": "
318
318
  else
319
319
  sb << ":"
320
320
  end
321
321
  else
322
- # in non-JSON we can omit the colon or equals before an object
323
- if self.is_a?(Hocon::ConfigObject)
324
- if options.formatted?
325
- sb << ' '
326
- end
327
- else
328
- sb << "="
329
- end
330
- end
322
+ case options.key_value_separator
323
+ when :colon
324
+ sb << ": "
325
+ else
326
+ sb << "="
327
+ end end
331
328
  end
332
329
  render_value_to_sb(sb, indent, at_root, options)
333
330
  end
@@ -296,7 +296,7 @@ class Hocon::Impl::ConfigDelayedMerge
296
296
  if !at_key.nil?
297
297
  sb << Hocon::Impl::ConfigImplUtil.render_json_string(at_key)
298
298
  if options.formatted
299
- sb << " : "
299
+ sb << ": "
300
300
  else
301
301
  sb << ":"
302
302
  end
@@ -206,7 +206,6 @@ class Hocon::Impl::ConfigNodeObject
206
206
  new_nodes = []
207
207
  new_nodes += indentation
208
208
  new_nodes.push(desired_path.first)
209
- new_nodes.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens.new_ignored_whitespace(nil, ' ')))
210
209
  new_nodes.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens::COLON))
211
210
  new_nodes.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens.new_ignored_whitespace(nil, ' ')))
212
211
 
@@ -281,4 +280,4 @@ class Hocon::Impl::ConfigNodeObject
281
280
  path = Hocon::Impl::PathParser.parse_path_node(desired_path, flavor).value
282
281
  change_value_on_path(path, nil, flavor)
283
282
  end
284
- end
283
+ end
@@ -57,4 +57,4 @@ class Hocon::Impl::ConfigNodeRoot
57
57
  end
58
58
  raise Hocon::ConfigError::ConfigBugOrBrokenError, "ConfigNodeRoot did not contain a value"
59
59
  end
60
- end
60
+ end
@@ -48,4 +48,4 @@ class Hocon::Impl::SimpleConfigDocument
48
48
  def hash
49
49
  render.hash
50
50
  end
51
- end
51
+ end