fig 0.1.81 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,11 +1,33 @@
1
+ # coding: utf-8
2
+
1
3
  require 'fig/tokenized_string'
2
4
  require 'fig/tokenized_string/plain_segment'
3
5
 
4
6
  module Fig; end
5
7
 
6
8
  class Fig::StringTokenizer
7
- def initialize(subexpression_matchers = DEFAULT_SUBEXPRESSION_MATCHER)
9
+ # subexpression_matchers is an array of hashes. Each hash is expected to
10
+ # contain two keys: :pattern and :action.
11
+ #
12
+ # The :pattern value needs to be a regular expression for the substring that
13
+ # needs special handling.
14
+ #
15
+ # The :action value needs to be a block that takes two parameters.
16
+ #
17
+ # The first parameter is the text that was matched and the second is the
18
+ # error block passed to #tokenize().
19
+ #
20
+ # On success the block returns either a String containing replacement text or
21
+ # a Fig::TokenizedString::Token representing the special handling of the
22
+ # consumed text. If there was a problem, then the error block should have
23
+ # been invoked and the block should return nil.
24
+ #
25
+ #
26
+ # metacharacters is a regular expression character class for characters that
27
+ # need to be escaped when un-single quoting a string.
28
+ def initialize(subexpression_matchers = [], metacharacters = '')
8
29
  @subexpression_matchers = subexpression_matchers
30
+ @metacharacters = metacharacters
9
31
 
10
32
  return
11
33
  end
@@ -28,15 +50,11 @@ class Fig::StringTokenizer
28
50
 
29
51
  return if @segments.empty?
30
52
 
31
- return Fig::TokenizedString.new(@segments, @single_quoted)
53
+ return Fig::TokenizedString.new(@segments, @single_quoted, @metacharacters)
32
54
  end
33
55
 
34
56
  private
35
57
 
36
- DEFAULT_SUBEXPRESSION_MATCHER = [
37
- { :pattern => %r<\@>, :action => lambda {|character| character} }
38
- ]
39
-
40
58
  def strip_quotes_and_process_escapes()
41
59
  if @string.length == 0
42
60
  @single_quoted = false
@@ -60,13 +78,15 @@ class Fig::StringTokenizer
60
78
 
61
79
  def strip_single_quotes_and_process_escapes()
62
80
  return false if @string[0..0] != %q<'> && @string[-1..-1] != %q<'>
63
- return false if @string =~ %r< \A (?: \\{2} )* \\ ' \z >x # «\'» is legal
81
+ return false if @string =~ %r< # «\'» is legal
82
+ \A ( [^\\']* (?: \\{2} )* \\ ' )* \z
83
+ >x
64
84
 
65
85
  if (
66
- @string.length == 1 ||
67
- @string[0..0] != %q<'> ||
68
- @string[-1..-1] != %q<'> ||
69
- @string =~ %r< [^\\] (?: \\{2} )* \\ ' \z >x
86
+ @string.length == 1 ||
87
+ @string[0..0] != %q<'> ||
88
+ @string[-1..-1] != %q<'> ||
89
+ @string =~ %r< [^\\] (?: \\{2} )* (?: \\ | ' .* ) ' \z >x
70
90
  )
71
91
  @error_block.call 'has unbalanced single quotes.'
72
92
  return
@@ -85,7 +105,8 @@ class Fig::StringTokenizer
85
105
  end
86
106
 
87
107
  def strip_double_quotes_and_process_escapes()
88
- return if ! check_and_strip_double_quotes
108
+ was_quoted = check_and_strip_double_quotes
109
+ return if was_quoted.nil?
89
110
 
90
111
  if @string == %q<\\'>
91
112
  @segments << Fig::TokenizedString::PlainSegment.new(%q<'>)
@@ -93,7 +114,7 @@ class Fig::StringTokenizer
93
114
  return
94
115
  end
95
116
 
96
- generate_segments
117
+ generate_segments was_quoted
97
118
 
98
119
  return
99
120
  end
@@ -101,7 +122,7 @@ class Fig::StringTokenizer
101
122
  def check_and_strip_double_quotes()
102
123
  # We accept any unquoted single character at this point. Later validation
103
124
  # will catch bad characters.
104
- return true if @string =~ %r< \A \\ . \z >xm
125
+ return false if @string =~ %r< \A \\ . \z >xm
105
126
 
106
127
  if @string[0..0] == %q<">
107
128
  if @string.length == 1 || @string[-1..-1] != %q<">
@@ -115,16 +136,18 @@ class Fig::StringTokenizer
115
136
  end
116
137
 
117
138
  @string.sub!( %r< \A " (.*) " \z >xm, '\1' )
139
+
140
+ return true
118
141
  elsif @string =~ %r< (?: \A | [^\\] ) (?: \\{2} )* " \z >xm
119
142
  @error_block.call \
120
143
  %q<has unbalanced double quotes; it ends in a double quote when it didn't start with one.>
121
144
  return
122
145
  end
123
146
 
124
- return true
147
+ return false
125
148
  end
126
149
 
127
- def generate_segments()
150
+ def generate_segments(was_quoted)
128
151
  plain_string = nil
129
152
 
130
153
  while ! @string.empty?
@@ -135,13 +158,20 @@ class Fig::StringTokenizer
135
158
  @error_block.call 'ends in an incomplete escape.'
136
159
  return
137
160
  end
138
- if subexpression_match(remainder) || remainder[0..0] == %q<">
161
+ subexpression_matched = subexpression_match(remainder)
162
+ return if subexpression_matched.nil?
163
+ if (
164
+ subexpression_matched ||
165
+ remainder[0..0] == %q<"> ||
166
+ ! was_quoted && remainder[0..0] == %q<'>
167
+ )
139
168
  plain_string ||= ''
140
169
  plain_string << slashes
141
170
  plain_string << remainder[0..0]
142
171
  @string = remainder[1..-1] || ''
143
172
  else
144
- @error_block.call "contains a bad escape sequence (\\#{$1})."
173
+ @error_block.call \
174
+ "contains a bad escape sequence (\\#{remainder[0..0]})."
145
175
  return
146
176
  end
147
177
  else
@@ -151,17 +181,23 @@ class Fig::StringTokenizer
151
181
  end
152
182
  else
153
183
  replacement, remainder = subexpression_match @string
184
+ return if replacement.nil?
154
185
  if replacement
155
186
  if replacement.is_a? String
156
187
  plain_string << replacement
157
188
  else
158
- @segments << Fig::TokenizedString::PlainSegment.new(plain_string)
159
- plain_string = nil
189
+ if ! plain_string.nil?
190
+ @segments << Fig::TokenizedString::PlainSegment.new(plain_string)
191
+ plain_string = nil
192
+ end
193
+ @segments << replacement
160
194
  end
161
195
  @string = remainder
162
- elsif @string =~ %r< \A (["']) >xm # Fix single quotes in quoted strings
163
- quote_name = $1 == %q<'> ? 'single' : 'double'
164
- @error_block.call "contains an unescaped #{quote_name} quote."
196
+ elsif @string =~ %r< \A " >xm
197
+ @error_block.call 'contains an unescaped double quote.'
198
+ return
199
+ elsif ! was_quoted && @string =~ %r< \A ' >xm
200
+ @error_block.call 'contains an unescaped single quote.'
165
201
  return
166
202
  else
167
203
  plain_string ||= ''
@@ -185,11 +221,13 @@ class Fig::StringTokenizer
185
221
  pattern = matcher[:pattern]
186
222
  if sub_string =~ %r< \A ( #{pattern} ) >x
187
223
  subexpression, remainder = $1, $'
188
- replacement = matcher[:action].call subexpression
224
+ replacement = matcher[:action].call subexpression, @error_block
225
+
226
+ return if ! replacement
189
227
  return [replacement, remainder]
190
228
  end
191
229
  end
192
230
 
193
- return
231
+ return false
194
232
  end
195
233
  end
@@ -1,9 +1,10 @@
1
1
  module Fig; end
2
2
 
3
3
  class Fig::TokenizedString
4
- def initialize(segments, single_quoted)
5
- @segments = segments
6
- @single_quoted = single_quoted
4
+ def initialize(segments, single_quoted, metacharacters)
5
+ @segments = segments
6
+ @single_quoted = single_quoted
7
+ @metacharacters = metacharacters
7
8
 
8
9
  return
9
10
  end
@@ -12,11 +13,36 @@ class Fig::TokenizedString
12
13
  return @single_quoted
13
14
  end
14
15
 
15
- def to_expanded_string()
16
- return ( @segments.collect {|segment| segment.to_expanded_string} ).join ''
16
+ def can_be_single_quoted?()
17
+ return true if single_quoted?
18
+ return @segments.all? {|segment| segment.type.nil?}
19
+ end
20
+
21
+ def to_expanded_string(&block)
22
+ return (
23
+ @segments.collect { |segment| segment.to_expanded_string(&block) }
24
+ ).join ''
17
25
  end
18
26
 
19
27
  def to_escaped_string()
20
28
  return ( @segments.collect {|segment| segment.to_escaped_string} ).join ''
21
29
  end
30
+
31
+ def to_single_quoted_string()
32
+ return to_escaped_string if single_quoted?
33
+
34
+ return (
35
+ @segments.collect {|segment| segment.to_single_quoted_string}
36
+ ).join ''
37
+ end
38
+
39
+ def to_double_quotable_string()
40
+ return to_escaped_string if ! single_quoted?
41
+
42
+ return (
43
+ @segments.collect {
44
+ |segment| segment.to_double_quotable_string @metacharacters
45
+ }
46
+ ).join ''
47
+ end
22
48
  end
@@ -11,14 +11,44 @@ class Fig::TokenizedString::PlainSegment
11
11
  end
12
12
 
13
13
  def type
14
- return :plain_segment
14
+ return nil
15
15
  end
16
16
 
17
- def to_expanded_string()
17
+ def to_expanded_string(&block)
18
18
  return @raw_value.gsub(%r< \\ (.) >xm, '\1')
19
19
  end
20
20
 
21
21
  def to_escaped_string()
22
22
  return @raw_value
23
23
  end
24
+
25
+ # Should not be invoked if original string was single quoted.
26
+ def to_single_quoted_string()
27
+ # Raw value will have come from a non-single quoted string, so we unescape
28
+ # everything (including backslashes) and then escape backslashes and single
29
+ # quotes (which cannot be escaped outside of single quotes).
30
+ return \
31
+ @raw_value.gsub(%r< \\ (.) >xm, '\1').gsub(%r< ([\\']) >xm, '\\\\\1')
32
+ end
33
+
34
+ # Should not be invoked if original string was not single quoted.
35
+ def to_double_quotable_string(metacharacters)
36
+ quoted_value = @raw_value.gsub %r< ( ["#{metacharacters}] ) >xm, '\\\\\1'
37
+
38
+ quoted_value.gsub!(
39
+ %r<
40
+ (
41
+ (?: ^ | [^\\] ) # New line or non-backslash
42
+ (\\{2})* # Even number of backslashes
43
+ )
44
+
45
+ # All single quotes must have been escaped. The important bit is to
46
+ # not lose escaped backslashes.
47
+ \\'
48
+ >xm,
49
+ %q<\1'>
50
+ )
51
+
52
+ return quoted_value
53
+ end
24
54
  end
@@ -12,7 +12,19 @@ class Fig::TokenizedString::Token
12
12
  return
13
13
  end
14
14
 
15
+ def to_expanded_string(&block)
16
+ return block.call self
17
+ end
18
+
15
19
  def to_escaped_string()
16
20
  return raw_value
17
21
  end
22
+
23
+ def to_double_quotable_string(metacharacters)
24
+ return raw_value
25
+ end
26
+
27
+ def to_single_quoted_string()
28
+ raise NotImplementedError.new 'Cannot single-quote a token.'
29
+ end
18
30
  end
data/lib/fig/unparser.rb CHANGED
@@ -1,3 +1,6 @@
1
+ # Note: we very specifically do not require the files containing the
2
+ # Unparser classes in order to avoid circular dependencies.
3
+
1
4
  module Fig; end
2
5
 
3
6
  module Fig::Unparser
@@ -7,8 +10,6 @@ module Fig::Unparser
7
10
  def self.class_for_statements(
8
11
  statements, emit_as_input_or_to_be_published_values
9
12
  )
10
- # Note: we very specifically do not require the files containing the
11
- # Unparser classes in order to avoid circular dependencies.
12
13
  statements = [statements].flatten
13
14
 
14
15
  versions =
@@ -21,12 +22,6 @@ module Fig::Unparser
21
22
  return Fig::Unparser::V0, explanations
22
23
  end
23
24
 
24
- # TODO: Until v1 grammar handling is done, ensure we don't emit anything
25
- # old fig versions cannot handle.
26
- if ! ENV['FIG_ALLOW_NON_V0_GRAMMAR']
27
- raise 'Reached a point where something could not be represented by the v0 grammar. Bailing out.'
28
- end
29
-
30
25
  return Fig::Unparser::V1, explanations
31
26
  end
32
27
 
@@ -44,8 +39,11 @@ module Fig::Unparser
44
39
  private
45
40
 
46
41
  def self.gather_versions(statements, emit_as_input_or_to_be_published_values)
42
+ all_statements = gather_all_statements statements
43
+
47
44
  if emit_as_input_or_to_be_published_values == :emit_as_input
48
- return statements.map {
45
+ versions = []
46
+ return all_statements.map {
49
47
  |statement|
50
48
 
51
49
  self.expand_version_and_explanation(
@@ -54,7 +52,7 @@ module Fig::Unparser
54
52
  }
55
53
  end
56
54
 
57
- return statements.map {
55
+ return all_statements.map {
58
56
  |statement|
59
57
 
60
58
  self.expand_version_and_explanation(
@@ -63,6 +61,21 @@ module Fig::Unparser
63
61
  }
64
62
  end
65
63
 
64
+ def self.gather_all_statements(statements)
65
+ all_statements = []
66
+ statements.each do
67
+ |statement|
68
+
69
+ all_statements << statement
70
+
71
+ if statement.is_a? Fig::Statement::Configuration
72
+ all_statements << statement.statements
73
+ end
74
+ end
75
+
76
+ return all_statements.flatten
77
+ end
78
+
66
79
  def self.expand_version_and_explanation(statement, version_info)
67
80
  version, explanation = *version_info
68
81
  if explanation.nil?
@@ -159,6 +172,8 @@ module Fig::Unparser
159
172
  end
160
173
 
161
174
  def path(statement)
175
+ # I'd really like to change this to "add", but because the command-line
176
+ # option is "--append", I'm not going to.
162
177
  environment_variable(statement, 'append')
163
178
 
164
179
  return
@@ -202,8 +217,8 @@ module Fig::Unparser
202
217
  raise NotImplementedError
203
218
  end
204
219
 
205
- def add_indent()
206
- @text << @indent_string * @indent_level
220
+ def add_indent(indent_level = @indent_level)
221
+ @text << @indent_string * indent_level
207
222
 
208
223
  return
209
224
  end
@@ -1,4 +1,3 @@
1
- require 'fig/package_descriptor'
2
1
  require 'fig/unparser'
3
2
 
4
3
  module Fig; end
@@ -25,7 +24,7 @@ class Fig::Unparser::V0
25
24
  add_indent
26
25
 
27
26
  @text << %q<command ">
28
- @text << statement.command
27
+ @text << statement.command.first.to_double_quotable_string
29
28
  @text << %Q<"\n>
30
29
 
31
30
  return
@@ -44,9 +43,9 @@ class Fig::Unparser::V0
44
43
  add_indent
45
44
 
46
45
  @text << 'retrieve '
47
- @text << statement.var
46
+ @text << statement.variable
48
47
  @text << '->'
49
- @text << statement.path
48
+ @text << statement.tokenized_path.to_double_quotable_string
50
49
  @text << "\n"
51
50
 
52
51
  return
@@ -80,7 +79,7 @@ class Fig::Unparser::V0
80
79
  @text << ' '
81
80
  @text << statement.name
82
81
  @text << '='
83
- @text << statement.value
82
+ @text << statement.tokenized_value.to_double_quotable_string
84
83
  @text << "\n"
85
84
 
86
85
  return
@@ -22,10 +22,19 @@ class Fig::Unparser::V1
22
22
 
23
23
  def command(statement)
24
24
  add_indent
25
+ @text << %Q<command\n>
25
26
 
26
- @text << %q<command ">
27
- @text << statement.command
28
- @text << %Q<"\n>
27
+ add_indent(@indent_level + 1)
28
+ statement.command.each do
29
+ |argument|
30
+
31
+ emit_tokenized_value argument
32
+ @text << ' '
33
+ end
34
+
35
+ @text << %Q<\n>
36
+ add_indent
37
+ @text << %Q<end\n>
29
38
 
30
39
  return
31
40
  end
@@ -38,6 +47,20 @@ class Fig::Unparser::V1
38
47
  return
39
48
  end
40
49
 
50
+ def retrieve(statement)
51
+ add_indent
52
+
53
+ @text << 'retrieve '
54
+ @text << statement.variable
55
+ @text << '->'
56
+
57
+ emit_tokenized_value statement.tokenized_path
58
+
59
+ @text << "\n"
60
+
61
+ return
62
+ end
63
+
41
64
  def grammar_description()
42
65
  return 'v1'
43
66
  end
@@ -61,17 +84,31 @@ class Fig::Unparser::V1
61
84
  end
62
85
 
63
86
  def environment_variable(statement, keyword)
64
- # TODO: temporarily hack v0 grammar in here so we can test asset
65
- # statements; proper implementation once asset statements are done.
66
87
  add_indent
67
88
 
68
89
  @text << keyword
69
90
  @text << ' '
70
91
  @text << statement.name
71
92
  @text << '='
72
- @text << statement.value
93
+
94
+ emit_tokenized_value statement.tokenized_value
95
+
73
96
  @text << "\n"
74
97
 
75
98
  return
76
99
  end
100
+
101
+ def emit_tokenized_value(tokenized_value)
102
+ if tokenized_value.can_be_single_quoted?
103
+ @text << %q<'>
104
+ @text << tokenized_value.to_single_quoted_string
105
+ @text << %q<'>
106
+ else
107
+ @text << %q<">
108
+ @text << tokenized_value.to_escaped_string
109
+ @text << %q<">
110
+ end
111
+
112
+ return
113
+ end
77
114
  end