fig 0.1.81 → 0.2.1

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.
Files changed (43) hide show
  1. data/Changes +87 -0
  2. data/lib/fig.rb +1 -1
  3. data/lib/fig/command.rb +5 -0
  4. data/lib/fig/command/action/dump_package_definition_for_command_line.rb +62 -0
  5. data/lib/fig/command/action/dump_package_definition_parsed.rb +19 -2
  6. data/lib/fig/command/action/list_local.rb +9 -1
  7. data/lib/fig/command/action/list_remote.rb +9 -1
  8. data/lib/fig/command/action/role/list_variables_in_a_tree.rb +1 -1
  9. data/lib/fig/command/action/run_command_line.rb +1 -1
  10. data/lib/fig/command/action/run_command_statement.rb +4 -2
  11. data/lib/fig/command/options.rb +50 -18
  12. data/lib/fig/command/options/parser.rb +16 -15
  13. data/lib/fig/command/package_applier.rb +5 -3
  14. data/lib/fig/grammar/v0.rb +287 -289
  15. data/lib/fig/grammar/v0.treetop +66 -42
  16. data/lib/fig/grammar/v1.rb +629 -533
  17. data/lib/fig/grammar/v1.treetop +102 -39
  18. data/lib/fig/grammar_monkey_patches.rb +21 -0
  19. data/lib/fig/operating_system.rb +53 -36
  20. data/lib/fig/package_descriptor.rb +1 -12
  21. data/lib/fig/parser.rb +8 -33
  22. data/lib/fig/parser_package_build_state.rb +92 -31
  23. data/lib/fig/repository_package_publisher.rb +2 -2
  24. data/lib/fig/runtime_environment.rb +54 -120
  25. data/lib/fig/statement.rb +6 -6
  26. data/lib/fig/statement/asset.rb +1 -13
  27. data/lib/fig/statement/command.rb +47 -0
  28. data/lib/fig/statement/environment_variable.rb +64 -3
  29. data/lib/fig/statement/grammar_version.rb +4 -0
  30. data/lib/fig/statement/include.rb +4 -0
  31. data/lib/fig/statement/override.rb +4 -0
  32. data/lib/fig/statement/path.rb +40 -16
  33. data/lib/fig/statement/retrieve.rb +61 -5
  34. data/lib/fig/statement/set.rb +16 -19
  35. data/lib/fig/string_tokenizer.rb +63 -25
  36. data/lib/fig/tokenized_string.rb +31 -5
  37. data/lib/fig/tokenized_string/plain_segment.rb +32 -2
  38. data/lib/fig/tokenized_string/token.rb +12 -0
  39. data/lib/fig/unparser.rb +27 -12
  40. data/lib/fig/unparser/v0.rb +4 -5
  41. data/lib/fig/unparser/v1.rb +43 -6
  42. data/lib/fig/url.rb +13 -0
  43. metadata +44 -42
@@ -21,7 +21,11 @@ module Fig
21
21
  include Fig::Grammar::Version
22
22
 
23
23
  rule package
24
- optional_ws_or_comment grammar_version:grammar_version? statements:(package_statement_with_ws*) optional_ws_or_comment {
24
+ optional_ws_or_comment
25
+ grammar_version:grammar_version?
26
+ statements:(package_statement_with_ws*)
27
+ optional_ws_or_comment
28
+ {
25
29
  def to_package(directory, build_state)
26
30
  return build_state.new_package_statement(
27
31
  directory, grammar_version, statements
@@ -45,7 +49,10 @@ module Fig
45
49
  end
46
50
 
47
51
  rule archive
48
- statement_start:'archive' ws_or_comment+ location:asset_location {
52
+ statement_start:'archive'
53
+ ws_or_comment+
54
+ location:quoted_or_bare_string
55
+ {
49
56
  def to_package_statement(build_state)
50
57
  return build_state.new_asset_statement(
51
58
  Statement::Archive, statement_start, location
@@ -55,7 +62,10 @@ module Fig
55
62
  end
56
63
 
57
64
  rule resource
58
- statement_start:'resource' ws_or_comment+ location:asset_location {
65
+ statement_start:'resource'
66
+ ws_or_comment+
67
+ location:quoted_or_bare_string
68
+ {
59
69
  def to_package_statement(build_state)
60
70
  return build_state.new_asset_statement(
61
71
  Statement::Resource, statement_start, location
@@ -64,28 +74,27 @@ module Fig
64
74
  }
65
75
  end
66
76
 
67
- rule asset_location
68
- '"' ( [^"\\] / '\\' . )* '"' /
69
- "'" ( [^'\\] / '\\' . )* "'" /
70
- [^\s#]+
71
- end
72
-
73
77
  rule retrieve
74
- statement_start:'retrieve' ws_or_comment+ var:environment_variable_name '->' path:retrieve_path {
78
+ statement_start:'retrieve'
79
+ ws_or_comment+
80
+ variable:environment_variable_name '->' path:quoted_or_bare_string
81
+ {
75
82
  def to_package_statement(build_state)
76
- return build_state.new_retrieve_statement(statement_start, var, path)
83
+ return build_state.new_retrieve_statement(
84
+ statement_start, variable, path
85
+ )
77
86
  end
78
87
  }
79
88
  end
80
89
 
81
- rule retrieve_path
82
- # TODO: this should match asset location rules, but handling of
83
- # "[package]" substitution is going to be interesting.
84
- [a-zA-Z0-9_/.\[\]-]+
85
- end
86
-
87
90
  rule config
88
- statement_start:'config' ws_or_comment+ config_name ws_or_comment+ statements:config_statement_with_ws* 'end' {
91
+ statement_start:'config'
92
+ ws_or_comment+
93
+ config_name
94
+ ws_or_comment+
95
+ statements:config_statement_with_ws*
96
+ 'end'
97
+ {
89
98
  def to_package_statement(build_state)
90
99
  return build_state.new_configuration_statement(
91
100
  statement_start, config_name, statements
@@ -94,10 +103,6 @@ module Fig
94
103
  }
95
104
  end
96
105
 
97
- rule config_name
98
- [a-zA-Z0-9_.-]+
99
- end
100
-
101
106
  # Shim between "config" and "config_statement" rules to allow the
102
107
  # compiled v0 and v1 grammars to have the same interface.
103
108
  rule config_statement_with_ws
@@ -132,10 +137,6 @@ module Fig
132
137
  }
133
138
  end
134
139
 
135
- rule environment_variable_name
136
- [a-zA-Z0-9_]+
137
- end
138
-
139
140
  rule set
140
141
  statement_start:'set' ws_or_comment+ environment_variable_name_value {
141
142
  def to_config_statement(build_state)
@@ -147,7 +148,10 @@ module Fig
147
148
  end
148
149
 
149
150
  rule path
150
- statement_start:('add' / 'append' / 'path') ws_or_comment+ environment_variable_name_value {
151
+ statement_start:('add' / 'append' / 'path')
152
+ ws_or_comment+
153
+ environment_variable_name_value
154
+ {
151
155
  def to_config_statement(build_state)
152
156
  return build_state.new_environment_variable_statement(
153
157
  Statement::Path, statement_start, environment_variable_name_value
@@ -156,31 +160,90 @@ module Fig
156
160
  }
157
161
  end
158
162
 
159
- rule environment_variable_name_value
160
- # Should result in somewhat reasonable handling by Treetop when
161
- # encountering mis-quoted constructs.
162
- [^\s#\\"]* '"' ( [^"\\] / '\\' . )* '"' /
163
- [^\s#\\']* "'" ( [^'\\] / '\\' . )* "'" /
164
- [^\s#]+
165
- end
166
-
167
163
  rule command
168
- statement_start:'command' ws_or_comment+ command_line {
164
+ statement_start:'command'
165
+ ws_or_comment+
166
+ command_line
167
+ ws_or_comment+
168
+ 'end'
169
+ {
169
170
  def to_config_statement(build_state)
170
- return build_state.new_command_statement(
171
- statement_start, command_line
171
+ return build_state.new_v1_command_statement(
172
+ statement_start, gather_command_argument_nodes(command_line)
172
173
  )
173
174
  end
175
+
176
+ def gather_command_argument_nodes(node, arguments = [])
177
+ if node.respond_to? 'quoted_or_bare_string?'
178
+ arguments << node
179
+ return arguments
180
+ end
181
+
182
+ return arguments if not node.elements
183
+
184
+ node.elements.each do
185
+ |element|
186
+ gather_command_argument_nodes(element, arguments)
187
+ end
188
+
189
+ return arguments
190
+ end
174
191
  }
175
192
  end
176
193
 
177
194
  rule command_line
178
- '"' value:[^"]* '"'
195
+ quoted_or_bare_string
196
+ ! { |sequence| sequence[-1].text_value == 'end' }
197
+ (
198
+ ws_or_comment+
199
+ quoted_or_bare_string
200
+ ! { |sequence| sequence[-1].text_value == 'end' }
201
+ )*
179
202
  end
180
203
 
204
+ # Terminals
205
+
181
206
  rule descriptor_string
182
207
  [^\s#]+
183
208
  end
209
+
210
+ rule config_name
211
+ [a-zA-Z0-9_.-]+
212
+ end
213
+
214
+ rule quoted_or_bare_string
215
+ # In order to deal with the hierarchy of nodes that the command_line
216
+ # rule above generates, we tag each of the expressions here so that
217
+ # they can be found in the syntax tree.
218
+ [^\s#\\"]* '"' ( [^"\\] / '\\' . )* '"'
219
+ { def quoted_or_bare_string?() return true end }
220
+
221
+ /
222
+
223
+ [^\s#\\']* "'" ( [^'\\] / '\\' . )* "'"
224
+ { def quoted_or_bare_string?() return true end }
225
+
226
+ /
227
+
228
+ [^\s#]+
229
+ { def quoted_or_bare_string?() return true end }
230
+ end
231
+
232
+ rule environment_variable_name
233
+ [a-zA-Z0-9_]+
234
+ end
235
+
236
+ rule environment_variable_name_value
237
+ # This is like quoted_or_bare_string, but allows for the unquoted
238
+ # variable name followed by equals sign prior to the quotation marks
239
+ # coming in.
240
+ #
241
+ # Should result in somewhat reasonable handling by Treetop when
242
+ # encountering mis-quoted constructs.
243
+ [^\s#\\'"]* '"' ( [^"\\] / '\\' . )* '"' /
244
+ [^\s#\\'"]* "'" ( [^'\\] / '\\' . )* "'" /
245
+ [^\s#]+
246
+ end
184
247
  end
185
248
  end
186
249
  end
@@ -0,0 +1,21 @@
1
+ # The existence of this &!#%)(*!&% file makes me want to hurl, but it's the
2
+ # simplest way to modify the classes generated by Treetop: the Treetop grammar
3
+ # doesn't allow extensions at the grammar level, only the rule level.
4
+
5
+ require 'fig/grammar/v0'
6
+ require 'fig/grammar/v1'
7
+
8
+ module Fig; end
9
+ module Fig::Grammar; end
10
+
11
+ class Fig::Grammar::V0Parser
12
+ def version()
13
+ return 0
14
+ end
15
+ end
16
+
17
+ class Fig::Grammar::V1Parser
18
+ def version()
19
+ return 1
20
+ end
21
+ end
@@ -10,7 +10,6 @@ require 'net/sftp'
10
10
  require 'net/netrc'
11
11
  require 'rbconfig'
12
12
  require 'tempfile'
13
- require 'uri'
14
13
 
15
14
  require 'highline/import'
16
15
 
@@ -20,6 +19,8 @@ require 'fig/environment_variables/case_sensitive'
20
19
  require 'fig/file_not_found_error'
21
20
  require 'fig/logging'
22
21
  require 'fig/network_error'
22
+ require 'fig/url'
23
+ require 'fig/user_input_error'
23
24
 
24
25
  module Fig; end
25
26
 
@@ -119,40 +120,49 @@ class Fig::OperatingSystem
119
120
 
120
121
  def download_list(url)
121
122
  begin
122
- uri = URI.parse(url)
123
+ uri = Fig::URL.parse(url)
123
124
  rescue
124
125
  Fig::Logging.fatal %Q<Unable to parse url: "#{url}">
125
126
  raise Fig::NetworkError.new
126
127
  end
127
- case uri.scheme
128
- when 'ftp'
129
- ftp = Net::FTP.new(uri.host)
130
- ftp_login(ftp, uri.host)
131
- ftp.chdir(uri.path)
132
- dirs = ftp.nlst
133
- ftp.close
134
128
 
135
- download_ftp_list(uri, dirs)
136
- when 'ssh'
137
- packages = []
138
- Net::SSH.start(uri.host, uri.user) do |ssh|
139
- ls = ssh.exec!("[ -d #{uri.path} ] && find #{uri.path}")
140
- strip_paths_for_list(ls, packages, uri.path)
141
- end
142
- packages
143
- when 'file'
144
- packages = []
145
- unescaped_path = CGI.unescape uri.path
146
- return packages if ! File.exist?(unescaped_path)
129
+ begin
130
+ case uri.scheme
131
+ when 'ftp'
132
+ ftp = Net::FTP.new(uri.host)
133
+ ftp_login(ftp, uri.host)
134
+ ftp.chdir(uri.path)
135
+ dirs = ftp.nlst
136
+ ftp.close
147
137
 
148
- ls = ''
149
- Find.find(unescaped_path) { |file| ls << file.to_s; ls << "\n" }
138
+ download_ftp_list(uri, dirs)
139
+ when 'ssh'
140
+ packages = []
141
+ Net::SSH.start(uri.host, uri.user) do |ssh|
142
+ ls = ssh.exec!("[ -d #{uri.path} ] && find #{uri.path}")
143
+ strip_paths_for_list(ls, packages, uri.path)
144
+ end
145
+ packages
146
+ when 'file'
147
+ packages = []
148
+ unescaped_path = CGI.unescape uri.path
149
+ return packages if ! File.exist?(unescaped_path)
150
150
 
151
- strip_paths_for_list(ls, packages, unescaped_path)
152
- return packages
153
- else
154
- Fig::Logging.fatal "Protocol not supported: #{url}"
155
- raise Fig::NetworkError.new("Protocol not supported: #{url}")
151
+ ls = ''
152
+ Find.find(unescaped_path) { |file| ls << file.to_s; ls << "\n" }
153
+
154
+ strip_paths_for_list(ls, packages, unescaped_path)
155
+ return packages
156
+ else
157
+ Fig::Logging.fatal "Protocol not supported: #{url}"
158
+ raise Fig::NetworkError.new "Protocol not supported: #{url}"
159
+ end
160
+ rescue SocketError => error
161
+ Fig::Logging.debug error.message
162
+ raise Fig::NetworkError.new "#{url}: #{error.message}"
163
+ rescue Errno::ETIMEDOUT => error
164
+ Fig::Logging.debug error.message
165
+ raise Fig::NetworkError.new "#{url}: #{error.message}"
156
166
  end
157
167
  end
158
168
 
@@ -200,7 +210,7 @@ class Fig::OperatingSystem
200
210
  def path_up_to_date?(url, path)
201
211
  return false if ! File.exist? path
202
212
 
203
- uri = URI.parse(url)
213
+ uri = Fig::URL.parse(url)
204
214
  case uri.scheme
205
215
  when 'ftp'
206
216
  begin
@@ -242,7 +252,7 @@ class Fig::OperatingSystem
242
252
  # exists and is already up-to-date.
243
253
  def download(url, path)
244
254
  FileUtils.mkdir_p(File.dirname(path))
245
- uri = URI.parse(url)
255
+ uri = Fig::URL.parse(url)
246
256
  case uri.scheme
247
257
  when 'ftp'
248
258
  begin
@@ -263,6 +273,9 @@ class Fig::OperatingSystem
263
273
  rescue SocketError => error
264
274
  Fig::Logging.debug error.message
265
275
  raise Fig::FileNotFoundError.new error.message, url
276
+ rescue Errno::ETIMEDOUT => error
277
+ Fig::Logging.debug error.message
278
+ raise Fig::FileNotFoundError.new error.message, url
266
279
  end
267
280
  when 'http'
268
281
  log_download(url, path)
@@ -303,7 +316,7 @@ class Fig::OperatingSystem
303
316
  def download_resource(url, download_directory)
304
317
  FileUtils.mkdir_p(download_directory)
305
318
 
306
- basename = CGI.unescape URI.parse(url).path.split('/').last
319
+ basename = CGI.unescape Fig::URL.parse(url).path.split('/').last
307
320
  path = File.join(download_directory, basename)
308
321
 
309
322
  download(url, path)
@@ -333,14 +346,14 @@ class Fig::OperatingSystem
333
346
 
334
347
  def upload(local_file, remote_file)
335
348
  Fig::Logging.debug "Uploading #{local_file} to #{remote_file}."
336
- uri = URI.parse(remote_file)
349
+ uri = Fig::URL.parse(remote_file)
337
350
  case uri.scheme
338
351
  when 'ssh'
339
352
  ssh_upload(uri.user, uri.host, local_file, remote_file)
340
353
  when 'ftp'
341
354
  # fail unless system "curl -T #{local_file} --create-dirs --ftp-create-dirs #{remote_file}"
342
355
  require 'net/ftp'
343
- ftp_uri = URI.parse(ENV['FIG_REMOTE_URL'])
356
+ ftp_uri = Fig::URL.parse(ENV['FIG_REMOTE_URL'])
344
357
  ftp_root_path = ftp_uri.path
345
358
  ftp_root_dirs = ftp_uri.path.split('/')
346
359
  remote_publish_path = uri.path[0, uri.path.rindex('/')]
@@ -465,7 +478,11 @@ class Fig::OperatingSystem
465
478
  SimpleCov.at_exit.call
466
479
  end
467
480
 
468
- Kernel.exec(*command)
481
+ begin
482
+ Kernel.exec(*command)
483
+ rescue SystemCallError => exception
484
+ raise Fig::UserInputError.new exception
485
+ end
469
486
  end
470
487
 
471
488
  # *sigh* Apparently Ruby < v1.9.3 does some wacko thing with single argument
@@ -520,7 +537,7 @@ class Fig::OperatingSystem
520
537
  end
521
538
 
522
539
  def ssh_upload(user, host, local_file, remote_file)
523
- uri = URI.parse(remote_file)
540
+ uri = Fig::URL.parse(remote_file)
524
541
  dir = uri.path[0, uri.path.rindex('/')]
525
542
  Net::SSH.start(host, user) do |ssh|
526
543
  ssh.exec!("mkdir -p #{dir}")
@@ -544,7 +561,7 @@ class Fig::OperatingSystem
544
561
  when Net::HTTPRedirection then
545
562
  location = response['location']
546
563
  Fig::Logging.debug "Redirecting to #{location}."
547
- download_via_http_get(location, file, limit - 1)
564
+ download_via_http_get(location, file, redirection_limit - 1)
548
565
  else
549
566
  Fig::Logging.debug "Download failed: #{response.code} #{response.message}."
550
567
  raise Fig::FileNotFoundError.new(
@@ -1,4 +1,5 @@
1
1
  require 'fig/package_descriptor_parse_error'
2
+ require 'fig/statement'
2
3
 
3
4
  module Fig; end
4
5
 
@@ -64,7 +65,6 @@ class Fig::PackageDescriptor
64
65
  validate_component name, 'name', :name, options
65
66
  validate_component version, 'version', :version, options
66
67
  validate_component config, 'config', :config, options
67
- validate_name options
68
68
  validate_across_components options
69
69
  end
70
70
 
@@ -113,17 +113,6 @@ class Fig::PackageDescriptor
113
113
  )
114
114
  end
115
115
 
116
- def validate_name(options)
117
- return if @name.nil?
118
-
119
- return if ! Fig::Parser.strict_keyword? @name
120
-
121
- raise Fig::PackageDescriptorParseError.new(
122
- %Q<Invalid package name; "#{@name}" is a keyword#{standard_exception_suffix(options)}>,
123
- @original_string
124
- )
125
- end
126
-
127
116
  def throw_presence_exception(name, presence_requirement_symbol, options)
128
117
  presence = options[presence_requirement_symbol]
129
118
  raise Fig::PackageDescriptorParseError.new(
data/lib/fig/parser.rb CHANGED
@@ -3,6 +3,7 @@ require 'set'
3
3
  require 'fig/grammar/version_identification'
4
4
  require 'fig/grammar/v0'
5
5
  require 'fig/grammar/v1'
6
+ require 'fig/grammar_monkey_patches'
6
7
  require 'fig/logging'
7
8
  require 'fig/package_parse_error'
8
9
  require 'fig/parser_package_build_state'
@@ -16,19 +17,6 @@ module Fig; end
16
17
  # Parses .fig files (wrapping the Treetop-generated parser object) and deals
17
18
  # with a few restrictions on them.
18
19
  class Fig::Parser
19
- # Keywords that we really want to lock down.
20
- def self.strict_keyword?(string)
21
- # "config" is considered too useful for users, so we allow that where we
22
- # restrict other keywords.
23
- return false if string == 'config'
24
-
25
- return keyword? string
26
- end
27
-
28
- def self.keyword?(string)
29
- return KEYWORDS.include? string
30
- end
31
-
32
20
  def initialize(application_config, check_include_versions)
33
21
  @application_config = application_config
34
22
  @check_include_versions = check_include_versions
@@ -48,21 +36,6 @@ class Fig::Parser
48
36
 
49
37
  private
50
38
 
51
- KEYWORDS = Set.new
52
- KEYWORDS << 'add'
53
- KEYWORDS << 'append'
54
- KEYWORDS << 'archive'
55
- KEYWORDS << 'command'
56
- KEYWORDS << 'config'
57
- KEYWORDS << 'end'
58
- KEYWORDS << 'include'
59
- KEYWORDS << 'override'
60
- KEYWORDS << 'path'
61
- KEYWORDS << 'resource'
62
- KEYWORDS << 'retrieve'
63
- KEYWORDS << 'set'
64
-
65
-
66
39
  def get_grammar_version(
67
40
  descriptor, directory, source_description, unparsed_text
68
41
  )
@@ -77,7 +50,7 @@ class Fig::Parser
77
50
  end
78
51
 
79
52
  statement = result.get_grammar_version(
80
- Fig::ParserPackageBuildState.new(descriptor, extended_description)
53
+ Fig::ParserPackageBuildState.new(nil, descriptor, extended_description)
81
54
  )
82
55
  return 0 if not statement
83
56
 
@@ -96,7 +69,7 @@ class Fig::Parser
96
69
 
97
70
  v0_parser = Fig::Grammar::V0Parser.new
98
71
 
99
- return drive_parser(
72
+ return drive_treetop_parser(
100
73
  v0_parser,
101
74
  descriptor,
102
75
  directory,
@@ -109,7 +82,7 @@ class Fig::Parser
109
82
  def parse_v1(descriptor, directory, source_description, unparsed_text)
110
83
  v1_parser = Fig::Grammar::V1Parser.new
111
84
 
112
- return drive_parser(
85
+ return drive_treetop_parser(
113
86
  v1_parser,
114
87
  descriptor,
115
88
  directory,
@@ -119,7 +92,7 @@ class Fig::Parser
119
92
  )
120
93
  end
121
94
 
122
- def drive_parser(
95
+ def drive_treetop_parser(
123
96
  parser,
124
97
  descriptor,
125
98
  directory,
@@ -140,7 +113,9 @@ class Fig::Parser
140
113
 
141
114
  package = result.to_package(
142
115
  directory,
143
- Fig::ParserPackageBuildState.new(descriptor, extended_description)
116
+ Fig::ParserPackageBuildState.new(
117
+ parser.version, descriptor, extended_description
118
+ )
144
119
  )
145
120
  package.unparsed_text = unparsed_text
146
121