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
@@ -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