puppet 4.4.2 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (218) hide show
  1. data/CONTRIBUTING.md +5 -5
  2. data/Gemfile +2 -2
  3. data/LICENSE +2 -2
  4. data/README.md +5 -0
  5. data/ext/project_data.yaml +2 -0
  6. data/lib/hiera_puppet.rb +6 -14
  7. data/lib/puppet/application/agent.rb +2 -3
  8. data/lib/puppet/data_providers/hiera_config.rb +2 -4
  9. data/lib/puppet/data_providers/hiera_interpolate.rb +12 -154
  10. data/lib/puppet/data_providers/json_data_provider_factory.rb +0 -7
  11. data/lib/puppet/data_providers/yaml_data_provider_factory.rb +2 -8
  12. data/lib/puppet/defaults.rb +70 -7
  13. data/lib/puppet/functions.rb +69 -0
  14. data/lib/puppet/functions/dig.rb +39 -0
  15. data/lib/puppet/functions/lest.rb +53 -0
  16. data/lib/puppet/functions/lookup.rb +40 -27
  17. data/lib/puppet/functions/new.rb +502 -0
  18. data/lib/puppet/functions/regsubst.rb +11 -10
  19. data/lib/puppet/functions/then.rb +74 -0
  20. data/lib/puppet/functions/type.rb +4 -4
  21. data/lib/puppet/functions/with.rb +1 -1
  22. data/lib/puppet/indirector/catalog/compiler.rb +2 -0
  23. data/lib/puppet/indirector/resource_type/parser.rb +5 -0
  24. data/lib/puppet/indirector/rest.rb +5 -1
  25. data/lib/puppet/loaders.rb +2 -0
  26. data/lib/puppet/metatype/manager.rb +19 -2
  27. data/lib/puppet/module_tool/applications/application.rb +1 -1
  28. data/lib/puppet/module_tool/skeleton/templates/generator/Gemfile +6 -2
  29. data/lib/puppet/module_tool/skeleton/templates/generator/Rakefile +19 -4
  30. data/lib/puppet/module_tool/skeleton/templates/generator/{tests → examples}/init.pp.erb +1 -1
  31. data/lib/puppet/module_tool/skeleton/templates/generator/spec/classes/init_spec.rb.erb +0 -1
  32. data/lib/puppet/network/http/api/master/v3/environment.rb +6 -2
  33. data/lib/puppet/parser/ast/pops_bridge.rb +20 -3
  34. data/lib/puppet/parser/compiler/catalog_validator/relationship_validator.rb +24 -2
  35. data/lib/puppet/parser/e4_parser_adapter.rb +13 -12
  36. data/lib/puppet/parser/environment_compiler.rb +2 -2
  37. data/lib/puppet/parser/resource.rb +14 -5
  38. data/lib/puppet/parser/scope.rb +18 -15
  39. data/lib/puppet/plugins/data_providers/data_provider.rb +19 -8
  40. data/lib/puppet/pops.rb +6 -0
  41. data/lib/puppet/pops/adapters.rb +5 -1
  42. data/lib/puppet/pops/evaluator/access_operator.rb +52 -14
  43. data/lib/puppet/pops/evaluator/compare_operator.rb +34 -4
  44. data/lib/puppet/pops/evaluator/evaluator_impl.rb +75 -22
  45. data/lib/puppet/pops/evaluator/literal_evaluator.rb +7 -6
  46. data/lib/puppet/pops/evaluator/runtime3_converter.rb +13 -1
  47. data/lib/puppet/pops/evaluator/runtime3_support.rb +14 -4
  48. data/lib/puppet/pops/functions/dispatcher.rb +1 -1
  49. data/lib/puppet/pops/issues.rb +18 -2
  50. data/lib/puppet/pops/loader/base_loader.rb +48 -7
  51. data/lib/puppet/pops/loader/dependency_loader.rb +27 -2
  52. data/lib/puppet/pops/loader/loader.rb +12 -0
  53. data/lib/puppet/pops/loader/predefined_loader.rb +29 -0
  54. data/lib/puppet/pops/loader/runtime3_type_loader.rb +57 -0
  55. data/lib/puppet/pops/loader/static_loader.rb +92 -5
  56. data/lib/puppet/pops/loader/type_definition_instantiator.rb +25 -3
  57. data/lib/puppet/pops/loaders.rb +84 -14
  58. data/lib/puppet/pops/lookup/explainer.rb +38 -1
  59. data/lib/puppet/pops/lookup/interpolation.rb +115 -0
  60. data/lib/puppet/pops/lookup/sub_lookup.rb +86 -0
  61. data/lib/puppet/pops/model/ast_transformer.rb +8 -1
  62. data/lib/puppet/pops/model/factory.rb +31 -8
  63. data/lib/puppet/pops/model/model.rb +8 -0
  64. data/lib/puppet/pops/model/model_label_provider.rb +1 -0
  65. data/lib/puppet/pops/model/model_meta.rb +7 -1
  66. data/lib/puppet/pops/model/model_tree_dumper.rb +4 -0
  67. data/lib/puppet/pops/parser/egrammar.ra +24 -7
  68. data/lib/puppet/pops/parser/eparser.rb +863 -798
  69. data/lib/puppet/pops/parser/evaluating_parser.rb +4 -0
  70. data/lib/puppet/pops/parser/locator.rb +8 -4
  71. data/lib/puppet/pops/pcore.rb +30 -0
  72. data/lib/puppet/pops/types/class_loader.rb +2 -4
  73. data/lib/puppet/pops/types/implementation_registry.rb +146 -0
  74. data/lib/puppet/pops/types/iterable.rb +4 -4
  75. data/lib/puppet/pops/types/p_object_type.rb +846 -0
  76. data/lib/puppet/pops/types/p_runtime_type.rb +102 -0
  77. data/lib/puppet/pops/types/p_sem_ver_range_type.rb +164 -0
  78. data/lib/puppet/pops/types/p_sem_ver_type.rb +113 -0
  79. data/lib/puppet/pops/types/puppet_object.rb +21 -0
  80. data/lib/puppet/pops/types/ruby_generator.rb +258 -0
  81. data/lib/puppet/pops/types/string_converter.rb +922 -0
  82. data/lib/puppet/pops/types/type_calculator.rb +29 -5
  83. data/lib/puppet/pops/types/type_conversion_error.rb +15 -0
  84. data/lib/puppet/pops/types/type_factory.rb +49 -16
  85. data/lib/puppet/pops/types/type_formatter.rb +335 -112
  86. data/lib/puppet/pops/types/type_mismatch_describer.rb +110 -29
  87. data/lib/puppet/pops/types/type_parser.rb +205 -197
  88. data/lib/puppet/pops/types/types.rb +481 -103
  89. data/lib/puppet/pops/validation.rb +1 -1
  90. data/lib/puppet/pops/validation/checker4_0.rb +66 -4
  91. data/lib/puppet/pops/validation/validator_factory_4_0.rb +1 -0
  92. data/lib/puppet/pops/visitor.rb +3 -1
  93. data/lib/puppet/property.rb +1 -1
  94. data/lib/puppet/provider/augeas/augeas.rb +1 -1
  95. data/lib/puppet/provider/package/pip.rb +64 -20
  96. data/lib/puppet/provider/package/rpm.rb +112 -0
  97. data/lib/puppet/provider/package/yum.rb +7 -68
  98. data/lib/puppet/provider/service/daemontools.rb +3 -3
  99. data/lib/puppet/provider/service/init.rb +4 -2
  100. data/lib/puppet/provider/service/runit.rb +3 -3
  101. data/lib/puppet/provider/service/smf.rb +6 -3
  102. data/lib/puppet/provider/service/systemd.rb +59 -73
  103. data/lib/puppet/reference/providers.rb +1 -2
  104. data/lib/puppet/resource.rb +54 -37
  105. data/lib/puppet/resource/catalog.rb +31 -29
  106. data/lib/puppet/resource/type_collection.rb +23 -8
  107. data/lib/puppet/settings.rb +4 -2
  108. data/lib/puppet/settings/base_setting.rb +9 -3
  109. data/lib/puppet/settings/symbolic_enum_setting.rb +17 -0
  110. data/lib/puppet/test/test_helper.rb +0 -1
  111. data/lib/puppet/type.rb +9 -3
  112. data/lib/puppet/type/exec.rb +17 -17
  113. data/lib/puppet/type/file.rb +12 -0
  114. data/lib/puppet/type/file/content.rb +6 -6
  115. data/lib/puppet/type/file/ensure.rb +4 -4
  116. data/lib/puppet/type/file/source.rb +4 -4
  117. data/lib/puppet/type/file/target.rb +2 -2
  118. data/lib/puppet/type/mount.rb +18 -1
  119. data/lib/puppet/type/package.rb +3 -3
  120. data/lib/puppet/type/schedule.rb +4 -4
  121. data/lib/puppet/type/service.rb +15 -0
  122. data/lib/puppet/type/sshkey.rb +5 -3
  123. data/lib/puppet/type/tidy.rb +3 -3
  124. data/lib/puppet/type/zone.rb +5 -5
  125. data/lib/puppet/util/feature.rb +1 -1
  126. data/lib/puppet/util/monkey_patches.rb +8 -0
  127. data/lib/puppet/util/network_device/cisco/device.rb +16 -6
  128. data/lib/puppet/util/network_device/cisco/interface.rb +5 -6
  129. data/lib/puppet/util/plist.rb +3 -3
  130. data/lib/puppet/version.rb +1 -1
  131. data/spec/fixtures/unit/application/environments/production/data/common.yaml +13 -0
  132. data/spec/fixtures/unit/data_providers/environments/production/modules/abc/lib/puppet/functions/abc/data.rb +2 -1
  133. data/spec/fixtures/unit/data_providers/environments/production/modules/abc/manifests/init.pp +2 -1
  134. data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_json/data/empty_key.json +1 -0
  135. data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_json/hiera.yaml +5 -0
  136. data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_json/manifests/init.pp +2 -0
  137. data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_json/metadata.json +9 -0
  138. data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_yaml/data/empty_key.yaml +1 -0
  139. data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_yaml/hiera.yaml +5 -0
  140. data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_yaml/manifests/init.pp +2 -0
  141. data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_key_yaml/metadata.json +9 -0
  142. data/spec/fixtures/unit/functions/lookup/environments/production/modules/empty_yaml/data/empty.yaml +1 -0
  143. data/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/lib/puppet/type/usee_type.rb +5 -0
  144. data/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/manifests/init.pp +6 -0
  145. data/spec/fixtures/unit/provider/service/smf/svcs.out +4 -3
  146. data/spec/integration/module_tool/tar/mini_spec.rb +27 -27
  147. data/spec/integration/parser/catalog_spec.rb +14 -2
  148. data/spec/integration/parser/compiler_spec.rb +94 -3
  149. data/spec/integration/parser/resource_expressions_spec.rb +1 -1
  150. data/spec/integration/resource/type_collection_spec.rb +8 -0
  151. data/spec/lib/puppet_spec/compiler.rb +11 -4
  152. data/spec/shared_contexts/types_setup.rb +4 -0
  153. data/spec/unit/application/lookup_spec.rb +91 -9
  154. data/spec/unit/appmgmt_spec.rb +44 -35
  155. data/spec/unit/capability_spec.rb +33 -53
  156. data/spec/unit/data_providers/function_data_provider_spec.rb +19 -1
  157. data/spec/unit/data_providers/hiera_data_provider_spec.rb +1 -1
  158. data/spec/unit/defaults_spec.rb +18 -0
  159. data/spec/unit/functions/assert_type_spec.rb +1 -1
  160. data/spec/unit/functions/dig_spec.rb +58 -0
  161. data/spec/unit/functions/lest_spec.rb +34 -0
  162. data/spec/unit/functions/lookup_spec.rb +108 -2
  163. data/spec/unit/functions/new_spec.rb +543 -0
  164. data/spec/unit/functions/regsubst_spec.rb +8 -0
  165. data/spec/unit/functions/then_spec.rb +40 -0
  166. data/spec/unit/functions4_spec.rb +78 -10
  167. data/spec/unit/hiera_puppet_spec.rb +49 -8
  168. data/spec/unit/indirector/resource_type/parser_spec.rb +5 -0
  169. data/spec/unit/indirector/rest_spec.rb +12 -0
  170. data/spec/unit/network/http/api/master/v3/environment_spec.rb +60 -0
  171. data/spec/unit/node/environment_spec.rb +10 -0
  172. data/spec/unit/parser/compiler_spec.rb +20 -1
  173. data/spec/unit/parser/functions/create_resources_spec.rb +2 -2
  174. data/spec/unit/parser/functions/shared.rb +1 -1
  175. data/spec/unit/parser/resource_spec.rb +8 -1
  176. data/spec/unit/parser/scope_spec.rb +45 -0
  177. data/spec/unit/pops/evaluator/access_ops_spec.rb +14 -0
  178. data/spec/unit/pops/evaluator/evaluating_parser_spec.rb +13 -5
  179. data/spec/unit/pops/loaders/static_loader_spec.rb +92 -1
  180. data/spec/unit/{data_providers/hiera_interpolation_spec.rb → pops/lookup/interpolation_spec.rb} +7 -5
  181. data/spec/unit/pops/parser/lexer2_spec.rb +2 -9
  182. data/spec/unit/pops/parser/parse_application_spec.rb +3 -8
  183. data/spec/unit/pops/parser/parse_basic_expressions_spec.rb +19 -0
  184. data/spec/unit/pops/parser/parse_capabilities_spec.rb +3 -10
  185. data/spec/unit/pops/parser/parse_site_spec.rb +19 -10
  186. data/spec/unit/pops/parser/parser_rspec_helper.rb +0 -4
  187. data/spec/unit/pops/types/enumeration_spec.rb +13 -12
  188. data/spec/unit/pops/types/iterable_spec.rb +2 -2
  189. data/spec/unit/pops/types/p_object_type_spec.rb +1060 -0
  190. data/spec/unit/pops/types/p_sem_ver_type_spec.rb +285 -0
  191. data/spec/unit/pops/types/recursion_guard_spec.rb +19 -17
  192. data/spec/unit/pops/types/ruby_generator_spec.rb +261 -0
  193. data/spec/unit/pops/types/string_converter_spec.rb +904 -0
  194. data/spec/unit/pops/types/type_calculator_spec.rb +430 -406
  195. data/spec/unit/pops/types/type_factory_spec.rb +119 -104
  196. data/spec/unit/pops/types/type_formatter_spec.rb +73 -6
  197. data/spec/unit/pops/types/type_mismatch_describer_spec.rb +2 -2
  198. data/spec/unit/pops/types/type_parser_spec.rb +54 -15
  199. data/spec/unit/pops/types/types_spec.rb +113 -8
  200. data/spec/unit/pops/validator/validator_spec.rb +84 -10
  201. data/spec/unit/provider/package/pip3_spec.rb +9 -270
  202. data/spec/unit/provider/package/pip_spec.rb +85 -30
  203. data/spec/unit/provider/package/rpm_spec.rb +160 -3
  204. data/spec/unit/provider/package/yum_spec.rb +23 -134
  205. data/spec/unit/provider/service/smf_spec.rb +14 -2
  206. data/spec/unit/provider/service/systemd_spec.rb +33 -41
  207. data/spec/unit/resource/capability_finder_spec.rb +10 -2
  208. data/spec/unit/settings/file_setting_spec.rb +6 -0
  209. data/spec/unit/transaction/additional_resource_generator_spec.rb +80 -65
  210. data/spec/unit/type/mount_spec.rb +51 -10
  211. data/spec/unit/type/service_spec.rb +16 -0
  212. data/spec/unit/type_spec.rb +14 -0
  213. data/spec/unit/util/feature_spec.rb +1 -1
  214. data/spec/unit/util/monkey_patches_spec.rb +60 -0
  215. data/spec/unit/util/network_device/cisco/device_spec.rb +1 -1
  216. metadata +63 -11
  217. data/lib/puppet/pops/types/types_meta.rb +0 -0
  218. data/spec/integration/provider/package_spec.rb +0 -35
@@ -0,0 +1,922 @@
1
+ module Puppet::Pops
2
+ module Types
3
+
4
+ # Converts Puppet runtime objects to String under the control of a Format.
5
+ # Use from Puppet Language is via the function `new`.
6
+ #
7
+ # @api private
8
+ #
9
+ class StringConverter
10
+
11
+ # @api private
12
+ class FormatError < ArgumentError
13
+ def initialize(type_string, actual, expected)
14
+ super "Illegal format '#{actual}' specified for value of #{type_string} type - expected one of the characters '#{expected}'"
15
+ end
16
+ end
17
+
18
+ class Indentation
19
+ attr_reader :level
20
+ attr_reader :first
21
+ attr_reader :is_indenting
22
+ alias :first? :first
23
+ alias :is_indenting? :is_indenting
24
+
25
+ def initialize(level, first, is_indenting)
26
+ @level = level
27
+ @first = first
28
+ @is_indenting = is_indenting
29
+ end
30
+
31
+ def subsequent
32
+ first? ? self.class.new(level, false, @is_indenting) : self
33
+ end
34
+
35
+ def indenting(indenting_flag)
36
+ self.class.new(level, first?, indenting_flag)
37
+ end
38
+
39
+ def increase(indenting_flag = false)
40
+ self.class.new(level + 1, true, indenting_flag)
41
+ end
42
+
43
+ def breaks?
44
+ is_indenting? && level > 0 && ! first?
45
+ end
46
+
47
+ def padding
48
+ return ' ' * 2 * level
49
+ end
50
+ end
51
+
52
+ # Format represents one format specification that is textually represented by %<flags><width>.<precision><format>
53
+ # Format parses and makes the individual parts available when an instance is created.
54
+ #
55
+ # @api private
56
+ #
57
+ class Format
58
+ # Boolean, alternate form (varies in meaning)
59
+ attr_reader :alt
60
+ alias :alt? :alt
61
+
62
+ # Nil or Integer with width of field > 0
63
+ attr_reader :width
64
+ # Nil or Integer precisions
65
+ attr_reader :prec
66
+ # One char symbol denoting the format
67
+ attr_reader :format
68
+ # Symbol, :space, :plus, :ignore
69
+ attr_reader :plus
70
+ # Boolean, left adjust in given width or not
71
+ attr_reader :left
72
+ # Boolean left_pad with zero instead of space
73
+ attr_reader :zero_pad
74
+ # Delimiters for containers, a "left" char representing the pair <[{(
75
+ attr_reader :delimiters
76
+
77
+ # Map of type to format for elements contained in an object this format applies to
78
+ attr_accessor :container_string_formats
79
+
80
+ # Separator string inserted between elements in a container
81
+ attr_accessor :separator
82
+
83
+ # Separator string inserted between sub elements in a container
84
+ attr_accessor :separator2
85
+
86
+ attr_reader :orig_fmt
87
+
88
+ FMT_PATTERN = /^%([\s\+\-#0\[\{<\(\|]*)([1-9][0-9]*)?(?:\.([0-9]+))?([a-zA-Z])/
89
+ DELIMITERS = [ '[', '{', '(', '<', '|',]
90
+ DELIMITER_MAP = {
91
+ '[' => ['[', ']'],
92
+ '{' => ['{', '}'],
93
+ '(' => ['(', ')'],
94
+ '<' => ['<', '>'],
95
+ '|' => ['|', '|'],
96
+ :space => ['', '']
97
+ }.freeze
98
+
99
+ def initialize(fmt)
100
+ @orig_fmt = fmt
101
+ match = FMT_PATTERN.match(fmt)
102
+ unless match
103
+ raise ArgumentError, "The format '#{fmt}' is not a valid format on the form '%<flags><width>.<prec><format>'"
104
+ end
105
+
106
+ @format = match[4]
107
+ unless @format.is_a?(String) && @format.length == 1
108
+ raise ArgumentError, "The format must be a one letter format specifier, got '#{@format}'"
109
+ end
110
+ @format = @format.to_sym
111
+ flags = match[1].split('') || []
112
+ unless flags.uniq.size == flags.size
113
+ raise ArgumentError, "The same flag can only be used once, got '#{fmt}'"
114
+ end
115
+ @left = flags.include?('-')
116
+ @alt = flags.include?('#')
117
+ @plus = (flags.include?(' ') ? :space : (flags.include?('+') ? :plus : :ignore))
118
+ @zero_pad = flags.include?('0')
119
+
120
+ @delimiters = nil
121
+ DELIMITERS.each do |d|
122
+ next unless flags.include?(d)
123
+ if !@delimiters.nil?
124
+ raise ArgumentError, "Only one of the delimiters [ { ( < | can be given in the format flags, got '#{fmt}'"
125
+ end
126
+ @delimiters = d
127
+ end
128
+
129
+ @width = match[2] ? match[2].to_i : nil
130
+ @prec = match[3] ? match[3].to_i : nil
131
+ end
132
+
133
+ # Merges one format into this and returns a new `Format`. The `other` format overrides this.
134
+ # @param [Format] other
135
+ # @returns [Format] a merged format
136
+ #
137
+ def merge(other)
138
+ result = Format.new(other.orig_fmt)
139
+ result.separator = other.separator || separator
140
+ result.separator2 = other.separator2 || separator2
141
+ result.container_string_formats = Format.merge_string_formats(container_string_formats, other.container_string_formats)
142
+ result
143
+ end
144
+
145
+ # Merges two formats where the `higher` format overrides the `lower`. Produces a new `Format`
146
+ # @param [Format] lower
147
+ # @param [Format] higher
148
+ # @returns [Format] the merged result
149
+ #
150
+ def self.merge(lower, higher)
151
+ unless lower && higher
152
+ return lower || higher
153
+ end
154
+ lower.merge(higher)
155
+ end
156
+
157
+ # Merges a type => format association and returns a new merged and sorted association.
158
+ # @param [Format] lower
159
+ # @param [Format] higher
160
+ # @returns [Hash] the merged type => format result
161
+ #
162
+ def self.merge_string_formats(lower, higher)
163
+ unless lower && higher
164
+ return lower || higher
165
+ end
166
+ merged = (lower.keys + higher.keys).uniq.map do |k|
167
+ [k, merge(lower[k], higher[k])]
168
+ end
169
+ sort_formats(merged)
170
+ end
171
+
172
+ # Sorts format based on generality of types - most specific types before general
173
+ #
174
+ def self.sort_formats(format_map)
175
+ format_map = format_map.sort do |(a,_),(b,_)|
176
+ ab = b.assignable?(a)
177
+ ba = a.assignable?(b)
178
+ if a == b
179
+ 0
180
+ elsif ab && !ba
181
+ -1
182
+ elsif !ab && ba
183
+ 1
184
+ else
185
+ # arbitrary order if disjunct (based on name of type)
186
+ rank_a = type_rank(a)
187
+ rank_b = type_rank(b)
188
+ if rank_a == 0 || rank_b == 0
189
+ a.to_s <=> b.to_s
190
+ else
191
+ rank_a <=> rank_b
192
+ end
193
+ end
194
+ end
195
+ Hash[format_map]
196
+ end
197
+
198
+ # Ranks type on specificity where it matters
199
+ # lower number means more specific
200
+ def self.type_rank(t)
201
+ case t
202
+ when PStructType
203
+ 1
204
+ when PHashType
205
+ 2
206
+ when PTupleType
207
+ 3
208
+ when PArrayType
209
+ 4
210
+ when PPatternType
211
+ 10
212
+ when PEnumType
213
+ 11
214
+ when PStringType
215
+ 12
216
+ else
217
+ 0
218
+ end
219
+ end
220
+ # Returns an array with a delimiter pair derived from the format.
221
+ # If format does not contain a delimiter specification the given default is returned
222
+ #
223
+ # @param [Array<String>] the default delimiters
224
+ # @returns [Array<String>] a tuple with left, right delimiters
225
+ #
226
+ def delimiter_pair(default = StringConverter::DEFAULT_ARRAY_DELIMITERS)
227
+ DELIMITER_MAP[ @delimiters || @plus ] || default
228
+ end
229
+
230
+ def to_s
231
+ "%#{@flags}#{@width}.#{@prec}#{@format}"
232
+ end
233
+ end
234
+
235
+ # @api public
236
+ def self.convert(value, string_formats = :default)
237
+ singleton.convert(value, string_formats)
238
+ end
239
+
240
+ # @return [TypeConverter] the singleton instance
241
+ #
242
+ # @api public
243
+ def self.singleton
244
+ @tconv_instance ||= new
245
+ end
246
+
247
+ # @api private
248
+ #
249
+ def initialize
250
+ @@string_visitor ||= Visitor.new(self, "string", 3, 3)
251
+ end
252
+
253
+ DEFAULT_INDENTATION = Indentation.new(0, true, false).freeze
254
+
255
+ # format used by default for values in a container
256
+ # (basically strings are quoted since they may contain a ','))
257
+ #
258
+ DEFAULT_CONTAINER_FORMATS = {
259
+ PAnyType::DEFAULT => Format.new('%p').freeze, # quoted string (Ruby inspect)
260
+ }.freeze
261
+
262
+ DEFAULT_ARRAY_FORMAT = Format.new('%a')
263
+ DEFAULT_ARRAY_FORMAT.separator = ','.freeze
264
+ DEFAULT_ARRAY_FORMAT.separator2 = ','.freeze
265
+ DEFAULT_ARRAY_FORMAT.container_string_formats = DEFAULT_CONTAINER_FORMATS
266
+ DEFAULT_ARRAY_FORMAT.freeze
267
+
268
+ DEFAULT_HASH_FORMAT = Format.new('%h')
269
+ DEFAULT_HASH_FORMAT.separator = ','.freeze
270
+ DEFAULT_HASH_FORMAT.separator2 = ' => '.freeze
271
+ DEFAULT_HASH_FORMAT.container_string_formats = DEFAULT_CONTAINER_FORMATS
272
+ DEFAULT_HASH_FORMAT.freeze
273
+
274
+ DEFAULT_HASH_DELIMITERS = ['{', '}'].freeze
275
+ DEFAULT_ARRAY_DELIMITERS = ['[', ']'].freeze
276
+
277
+ DEFAULT_STRING_FORMATS = {
278
+ PFloatType::DEFAULT => Format.new('%f').freeze, # float
279
+ PNumericType::DEFAULT => Format.new('%d').freeze, # decimal number
280
+ PArrayType::DEFAULT => DEFAULT_ARRAY_FORMAT.freeze,
281
+ PHashType::DEFAULT => DEFAULT_HASH_FORMAT.freeze,
282
+ PAnyType::DEFAULT => Format.new('%s').freeze, # unquoted string
283
+ }.freeze
284
+
285
+
286
+ # Converts the given value to a String, under the direction of formatting rules per type.
287
+ #
288
+ # When converting to string it is possible to use a set of built in conversion rules.
289
+ #
290
+ # A format is specified on the form:
291
+ #
292
+ # ´´´
293
+ # %[Flags][Width][.Precision]Format
294
+ # ´´´
295
+ #
296
+ # `Width` is the number of characters into which the value should be fitted. This allocated space is
297
+ # padded if value is shorter. By default it is space padded, and the flag 0 will cause padding with 0
298
+ # for numerical formats.
299
+ #
300
+ # `Precision` is the number of fractional digits to show for floating point, and the maximum characters
301
+ # included in a string format.
302
+ #
303
+ # Note that all data type supports the formats `s` and `p` with the meaning "default to-string" and
304
+ # "default-programmatic to-string".
305
+ #
306
+ # ### Integer
307
+ #
308
+ # | Format | Integer Formats
309
+ # | ------ | ---------------
310
+ # | d | Decimal, negative values produces leading '-'
311
+ # | x X | Hexadecimal in lower or upper case. Uses ..f/..F for negative values unless # is also used
312
+ # | o | Octal. Uses ..0 for negative values unless # is also used
313
+ # | b B | Binary with prefix 'b' or 'B'. Uses ..1/..1 for negative values unless # is also used
314
+ # | c | numeric value representing a Unicode value, result is a one unicode character string, quoted if alternative flag # is used
315
+ # | s | same as d, or d in quotes if alternative flag # is used
316
+ # | p | same as d
317
+ # | eEfgGaA | converts integer to float and formats using the floating point rules
318
+ #
319
+ # Defaults to `d`
320
+ #
321
+ # ### Float
322
+ #
323
+ # | Format | Float formats
324
+ # | ------ | -------------
325
+ # | f | floating point in non exponential notation
326
+ # | e E | exponential notation with 'e' or 'E'
327
+ # | g G | conditional exponential with 'e' or 'E' if exponent < -4 or >= the precision
328
+ # | a A | hexadecimal exponential form, using 'x'/'X' as prefix and 'p'/'P' before exponent
329
+ # | s | converted to string using format p, then applying string formatting rule, alternate form # quotes result
330
+ # | p | f format with minimum significant number of fractional digits, prec has no effect
331
+ # | dxXobBc | converts float to integer and formats using the integer rules
332
+ #
333
+ # Defaults to `p`
334
+ #
335
+ # ### String
336
+ #
337
+ # | Format | String
338
+ # | ------ | ------
339
+ # | s | unquoted string, verbatim output of control chars
340
+ # | p | programmatic representation - strings are quoted, interior quotes and control chars are escaped
341
+ # | C | each :: name segment capitalized, quoted if alternative flag # is used
342
+ # | c | capitalized string, quoted if alternative flag # is used
343
+ # | d | downcased string, quoted if alternative flag # is used
344
+ # | u | upcased string, quoted if alternative flag # is used
345
+ # | t | trims leading and trailing whitespace from the string, quoted if alternative flag # is used
346
+ #
347
+ # Defaults to `s` at top level and `p` inside array or hash.
348
+ #
349
+ # ### Boolean
350
+ #
351
+ # | Format | Boolean Formats
352
+ # | ---- | -------------------
353
+ # | t T | 'true'/'false' or 'True'/'False' , first char if alternate form is used (i.e. 't'/'f' or 'T'/'F').
354
+ # | y Y | 'yes'/'no', 'Yes'/'No', 'y'/'n' or 'Y'/'N' if alternative flag # is used
355
+ # | dxXobB | numeric value 0/1 in accordance with the given format which must be valid integer format
356
+ # | eEfgGaA | numeric value 0.0/1.0 in accordance with the given float format and flags
357
+ # | s | 'true' / 'false'
358
+ # | p | 'true' / 'false'
359
+ #
360
+ # ### Regexp
361
+ #
362
+ # | Format | Regexp Formats (%/)
363
+ # | ---- | ------------------
364
+ # | s | / / delimiters, alternate flag replaces / delimiters with quotes
365
+ # | p | / / delimiters
366
+ #
367
+ # ### Undef
368
+ #
369
+ # | Format | Undef formats
370
+ # | ------ | -------------
371
+ # | s | empty string, or quoted empty string if alternative flag # is used
372
+ # | p | 'undef', or quoted '"undef"' if alternative flag # is used
373
+ # | n | 'nil', or 'null' if alternative flag # is used
374
+ # | dxXobB | 'NaN'
375
+ # | eEfgGaA | 'NaN'
376
+ # | v | 'n/a'
377
+ # | V | 'N/A'
378
+ # | u | 'undef', or 'undefined' if alternative # flag is used
379
+ #
380
+ # ### Default (value)
381
+ #
382
+ # | Format | Default formats
383
+ # | ------ | ---------------
384
+ # | d D | 'default' or 'Default', alternative form # causes value to be quoted
385
+ # | s | same as d
386
+ # | p | same as d
387
+ #
388
+ # ### Array & Tuple
389
+ #
390
+ # | Format | Array/Tuple Formats
391
+ # | ------ | -------------
392
+ # | a | formats with `[ ]` delimiters and `,`, alternate form `#` indents nested arrays/hashes
393
+ # | s | same as a
394
+ # | p | same as a
395
+ #
396
+ # See "Flags" `<[({\|` for formatting of delimiters, and "Additional parameters for containers; Array and Hash" for
397
+ # more information about options.
398
+ #
399
+ # The alternate form flag `#` will cause indentation of nested array or hash containers. If width is also set
400
+ # it is taken as the maximum allowed length of a sequence of elements (not including delimiters). If this max length
401
+ # is exceeded, each element will be indented.
402
+ #
403
+ # ### Hash & Struct
404
+ #
405
+ # | Format | Hash/Struct Formats
406
+ # | ------ | -------------
407
+ # | h | formats with `{ }` delimiters, `,` element separator and ` => ` inner element separator unless overridden by flags
408
+ # | s | same as h
409
+ # | p | same as h
410
+ # | a | converts the hash to an array of [k,v] tuples and formats it using array rule(s)
411
+ #
412
+ # See "Flags" `<[({\|` for formatting of delimiters, and "Additional parameters for containers; Array and Hash" for
413
+ # more information about options.
414
+ #
415
+ # The alternate form flag `#` will format each hash key/value entry indented on a separate line.
416
+ #
417
+ # ### Type
418
+ #
419
+ # | Format | Array/Tuple Formats
420
+ # | ------ | -------------
421
+ # | s | The same as p, quoted if alternative flag # is used
422
+ # | p | Outputs the type in string form as specified by the Puppet Language
423
+ #
424
+ # ### Flags
425
+ #
426
+ # | Flag | Effect
427
+ # | ------ | ------
428
+ # | (space) | space instead of + for numeric output (- is shown), for containers skips delimiters
429
+ # | # | alternate format; prefix 0x/0x, 0 (octal) and 0b/0B for binary, Floats force decimal '.'. For g/G keep trailing 0.
430
+ # | + | show sign +/- depending on value's sign, changes x,X, o,b, B format to not use 2's complement form
431
+ # | - | left justify the value in the given width
432
+ # | 0 | pad with 0 instead of space for widths larger than value
433
+ # | <[({\| | defines an enclosing pair <> [] () {} or \| \| when used with a container type
434
+ #
435
+ #
436
+ # ### Additional parameters for containers; Array and Hash
437
+ #
438
+ # For containers (Array and Hash), the format is specified by a hash where the following keys can be set:
439
+ # * `'format'` - the format specifier for the container itself
440
+ # * `'separator'` - the separator string to use between elements, should not contain padding space at the end
441
+ # * `'separator2'` - the separator string to use between association of hash entries key/value
442
+ # * `'string_formats'´ - a map of type to format for elements contained in the container
443
+ #
444
+ # Note that the top level format applies to Array and Hash objects contained/nested in an Array or a Hash.
445
+ #
446
+ # Given format mappings are merged with (default) formats and a format specified for a narrower type
447
+ # wins over a broader.
448
+ #
449
+ # @param mode [String, Symbol] :strict or :extended (or :default which is the same as :strict)
450
+ # @param string_formats [String, Hash] format tring, or a hash mapping type to a format string, and for Array and Hash types map to hash of details
451
+ #
452
+ def convert(value, string_formats = :default)
453
+ options = DEFAULT_STRING_FORMATS
454
+
455
+ if string_formats.is_a?(String)
456
+ # add the format given for the exact type
457
+ t = TypeCalculator.infer_set(value)
458
+ string_formats = { t => string_formats }
459
+ end
460
+
461
+ case string_formats
462
+ when :default
463
+ # do nothing, use default formats
464
+
465
+ when Hash
466
+ # Convert and validate user input
467
+ string_formats = validate_input(string_formats)
468
+ # Merge user given with defaults such that user options wins, merge is deep and format specific
469
+ options = Format.merge_string_formats(DEFAULT_STRING_FORMATS, string_formats)
470
+ else
471
+ raise ArgumentError, "string conversion expects a Default value or a Hash of type to format mappings, got a '#{string_formats.class}'"
472
+ end
473
+
474
+ _convert(TypeCalculator.infer_set(value), value, options, DEFAULT_INDENTATION)
475
+ end
476
+
477
+ # # A method only used for manual debugging as the default output of the formatting rules is
478
+ # # very hard to read otherwise.
479
+ # #
480
+ # # @api private
481
+ # def dump_string_formats(f, indent = 1)
482
+ # return f.to_s unless f.is_a?(Hash)
483
+ # "{#{f.map {|k,v| "#{k.to_s} => #{dump_string_formats(v,indent+1)}"}.join(",\n#{' '*indent} ")}}"
484
+ # end
485
+
486
+ def _convert(val_type, value, format_map, indentation)
487
+ @@string_visitor.visit_this_3(self, val_type, value, format_map, indentation)
488
+ end
489
+ private :_convert
490
+
491
+ def validate_input(fmt)
492
+ return nil if fmt.nil?
493
+ unless fmt.is_a?(Hash)
494
+ raise ArgumentError, "expected a hash with type to format mappings, got instance of '#{fmt.class}'"
495
+ end
496
+ fmt.reduce({}) do | result, entry|
497
+ key, value = entry
498
+ unless key.is_a?(Types::PAnyType)
499
+ raise ArgumentError, "top level keys in the format hash must be data types, got instance of '#{key.class}'"
500
+ end
501
+ if value.is_a?(Hash)
502
+ result[key] = validate_container_input(value)
503
+ else
504
+ result[key] = Format.new(value)
505
+ end
506
+ result
507
+ end
508
+ end
509
+ private :validate_input
510
+
511
+ FMT_KEYS = %w{separator separator2 format string_formats}.freeze
512
+
513
+ def validate_container_input(fmt)
514
+ if (fmt.keys - FMT_KEYS).size > 0
515
+ raise ArgumentError, "only #{FMT_KEYS}.map {|k| "'#{k}'"}.join(', ')} are allowed in a container format, got #{fmt}"
516
+ end
517
+ result = Format.new(fmt['format'])
518
+ result.separator = fmt['separator']
519
+ result.separator2 = fmt['separator2']
520
+ result.container_string_formats = validate_input(fmt['string_formats'])
521
+ result
522
+ end
523
+ private :validate_container_input
524
+
525
+ def string_PRuntimeType(val_type, val, format_map)
526
+ case (f = get_format(val_type, format_map))
527
+ when :s
528
+ val.to_s
529
+ when :q
530
+ val.to_s.inspect
531
+ when :puppet
532
+ puppet_safe(val.to_s)
533
+ when :i, :d, :x, :o, :f, :puppet
534
+ converted = convert(o, PNumericType) # rest is default
535
+ "%#{f}" % converted
536
+ else
537
+ throw(:failed_conversion, [o, PStringType::DEFAULT, f])
538
+ end
539
+ end
540
+
541
+ # Given an unsafe string make it safe for puppet
542
+ def puppet_safe(str)
543
+ str = str.inspect # all specials are now quoted
544
+ # all $ variables must be quoted
545
+ str.gsub!("\$", "\\\$")
546
+ str
547
+ end
548
+
549
+ # Basically string_PAnyType converts the value to a String and then formats it according
550
+ # to the resulting type
551
+ #
552
+ # @api private
553
+ def string_PAnyType(val_type, val, format_map, _)
554
+ f = get_format(val_type, format_map)
555
+ Kernel.format(f.orig_fmt, val)
556
+ end
557
+
558
+ def string_PDefaultType(val_type, val, format_map, _)
559
+ f = get_format(val_type, format_map)
560
+ case f.format
561
+ when :d, :s, :p
562
+ f.alt? ? '"default"' : 'default'
563
+ when :D
564
+ f.alt? ? '"Default"' : 'Default'
565
+ else
566
+ raise FormatError.new('Default', f.format, 'dDsp')
567
+ end
568
+ end
569
+
570
+ # @api private
571
+ def string_PUndefType(val_type, val, format_map, _)
572
+ f = get_format(val_type, format_map)
573
+ undef_str =
574
+ case f.format
575
+ when :n
576
+ f.alt? ? 'null' : 'nil'
577
+ when :u
578
+ f.alt? ? 'undefined' : 'undef'
579
+ when :d, :x, :X, :o, :b, :B, :e, :E, :f, :g, :G, :a, :A
580
+ 'NaN'
581
+ when :v
582
+ 'n/a'
583
+ when :V
584
+ 'N/A'
585
+ when :s
586
+ f.alt? ? '""' : ''
587
+ when :p
588
+ f.alt? ? '"undef"' : 'undef'
589
+ else
590
+ raise FormatError.new('Undef', f.format, 'nudxXobBeEfgGaAvVsp')
591
+ end
592
+ fmt = "%#{f.left ? '-' : ''}"
593
+ fmt << "#{f.width}" if f.width
594
+ fmt << ".#{f.prec}" if f.prec
595
+ fmt << "s"
596
+ Kernel.format(fmt,undef_str)
597
+ end
598
+
599
+ # @api private
600
+ def string_PBooleanType(val_type, val, format_map, indentation)
601
+ f = get_format(val_type, format_map)
602
+ case f.format
603
+ when :t
604
+ # 'true'/'false' or 't'/'f' if in alt mode
605
+ str_bool = val.to_s
606
+ f.alt? ? str_bool[0] : str_bool
607
+
608
+ when :T
609
+ # 'True'/'False' or 'T'/'F' if in alt mode
610
+ str_bool = val.to_s.capitalize
611
+ f.alt? ? str_bool[0] : str_bool
612
+
613
+ when :y
614
+ # 'yes'/'no' or 'y'/'n' if in alt mode
615
+ str_bool = val ? 'yes' : 'no'
616
+ f.alt? ? str_bool[0] : str_bool
617
+
618
+ when :Y
619
+ # 'Yes'/'No' or 'Y'/'N' if in alt mode
620
+ str_bool = val ? 'Yes' : 'No'
621
+ f.alt? ? str_bool[0] : str_bool
622
+
623
+ when :d, :x, :X, :o, :b, :B
624
+ # Boolean in numeric form, formated by integer rule
625
+ numeric_bool = val ? 1 : 0
626
+ string_formats = { Puppet::Pops::Types::PIntegerType::DEFAULT => f}
627
+ _convert(TypeCalculator.infer_set(numeric_bool), numeric_bool, string_formats, indentation)
628
+
629
+ when :e, :E, :f, :g, :G, :a, :A
630
+ # Boolean in numeric form, formated by float rule
631
+ numeric_bool = val ? 1.0 : 0.0
632
+ string_formats = { Puppet::Pops::Types::PFloatType::DEFAULT => f}
633
+ _convert(TypeCalculator.infer_set(numeric_bool), numeric_bool, string_formats, indentation)
634
+
635
+ when :s
636
+ val.to_s
637
+
638
+ when :p
639
+ val.inspect
640
+
641
+ else
642
+ raise FormatError.new('Boolean', f.format, 'tTyYdxXobBeEfgGaAsp')
643
+ end
644
+ end
645
+
646
+ # @api private
647
+ def string_PIntegerType(val_type, val, format_map, _)
648
+ f = get_format(val_type, format_map)
649
+ case f.format
650
+ when :d, :x, :X, :o, :b, :B, :p
651
+ Kernel.format(f.orig_fmt, val)
652
+
653
+ when :e, :E, :f, :g, :G, :a, :A
654
+ Kernel.format(f.orig_fmt, val.to_f)
655
+
656
+ when :c
657
+ char = [val].pack("U")
658
+ char = f.alt? ? "\"#{char}\"" : char
659
+ char = Kernel.format(f.orig_fmt.gsub('c','s'), char)
660
+
661
+ when :s
662
+ fmt = f.alt? ? 'p' : 's'
663
+ int_str = Kernel.format('%d', val)
664
+ Kernel.format(f.orig_fmt.gsub('s', fmt), int_str)
665
+
666
+ else
667
+ raise FormatError.new('Integer', f.format, 'dxXobBeEfgGaAspc')
668
+ end
669
+ end
670
+
671
+ # @api private
672
+ def string_PFloatType(val_type, val, format_map, _)
673
+ f = get_format(val_type, format_map)
674
+ case f.format
675
+ when :d, :x, :X, :o, :b, :B
676
+ Kernel.format(f.orig_fmt, val.to_i)
677
+
678
+ when :e, :E, :f, :g, :G, :a, :A, :p
679
+ Kernel.format(f.orig_fmt, val)
680
+
681
+ when :s
682
+ float_str = f.alt? ? "\"#{Kernel.format('%p', val)}\"" : Kernel.format('%p', val)
683
+ Kernel.format(f.orig_fmt, float_str)
684
+
685
+ else
686
+ raise FormatError.new('Float', f.format, 'dxXobBeEfgGaAsp')
687
+ end
688
+ end
689
+
690
+ # @api private
691
+ def string_PStringType(val_type, val, format_map, _)
692
+ f = get_format(val_type, format_map)
693
+ case f.format
694
+ when :s, :p
695
+ Kernel.format(f.orig_fmt, val)
696
+
697
+ when :c
698
+ c_val = val.capitalize
699
+ substitute = f.alt? ? 'p' : 's'
700
+ Kernel.format(f.orig_fmt.gsub('c', substitute), c_val)
701
+
702
+ when :C
703
+ c_val = val.split('::').map {|s| s.capitalize }.join('::')
704
+ substitute = f.alt? ? 'p' : 's'
705
+ Kernel.format(f.orig_fmt.gsub('C', substitute), c_val)
706
+
707
+ when :u
708
+ substitute = f.alt? ? 'p' : 's'
709
+ Kernel.format(f.orig_fmt.gsub('u', substitute), val).upcase
710
+
711
+ when :d
712
+ substitute = f.alt? ? 'p' : 's'
713
+ Kernel.format(f.orig_fmt.gsub('d', substitute), val).downcase
714
+
715
+ when :t # trim
716
+ c_val = val.strip
717
+ substitute = f.alt? ? 'p' : 's'
718
+ Kernel.format(f.orig_fmt.gsub('t', substitute), c_val)
719
+
720
+ else
721
+ raise FormatError.new('String', f.format, 'cCudspt')
722
+ end
723
+ end
724
+
725
+ # @api private
726
+ def string_PRegexpType(val_type, val, format_map, _)
727
+ f = get_format(val_type, format_map)
728
+ case f.format
729
+ when :p
730
+ Kernel.format(f.orig_fmt, val)
731
+ when :s
732
+ str_regexp = val.inspect
733
+ str_regexp = f.alt? ? "\"#{str_regexp[1..-2]}\"" : str_regexp
734
+ Kernel.format(f.orig_fmt, str_regexp)
735
+ else
736
+ raise FormatError.new('Regexp', f.format, 'rsp')
737
+ end
738
+ end
739
+
740
+ def string_PArrayType(val_type, val, format_map, indentation)
741
+ format = get_format(val_type, format_map)
742
+ sep = format.separator || DEFAULT_ARRAY_FORMAT.separator
743
+ string_formats = format.container_string_formats || DEFAULT_CONTAINER_FORMATS
744
+ delims = format.delimiter_pair(DEFAULT_ARRAY_DELIMITERS)
745
+
746
+ # Make indentation active, if array is in alternative format, or if nested in indenting
747
+ indentation = indentation.indenting(format.alt? || indentation.is_indenting?)
748
+
749
+ case format.format
750
+ when :a, :s, :p
751
+ buf = ''
752
+ if indentation.breaks?
753
+ buf << "\n"
754
+ buf << indentation.padding
755
+ end
756
+ buf << delims[0]
757
+
758
+ # Make a first pass to format each element
759
+ children_indentation = indentation.increase(format.alt?) # tell children they are expected to indent
760
+ mapped = val.map do |v|
761
+ if children_indentation.first?
762
+ children_indentation = children_indentation.subsequent
763
+ end
764
+ val_t = TypeCalculator.infer_set(v)
765
+ _convert(val_t, v, is_container?(val_t) ? format_map : string_formats, children_indentation)
766
+ end
767
+
768
+ # compute widest run in the array, skip nested arrays and hashes
769
+ # then if size > width, set flag if a break on each element should be performed
770
+ if format.alt? && format.width
771
+ widest = val.each_with_index.reduce([0]) do | memo, v_i |
772
+ # array or hash breaks
773
+ if is_a_or_h?(v_i[0])
774
+ memo << 0
775
+ else
776
+ memo[-1] += mapped[v_i[1]].length
777
+ end
778
+ memo
779
+ end
780
+ widest = widest.max
781
+ sz_break = widest > (format.width || Float::INFINITY)
782
+ else
783
+ sz_break = false
784
+ end
785
+
786
+ # output each element with breaks and padding
787
+ children_indentation = indentation.increase(format.alt?)
788
+ val.each_with_index do |v, i|
789
+ str_val = mapped[i]
790
+ if children_indentation.first?
791
+ children_indentation = children_indentation.subsequent
792
+ # if breaking, indent first element by one
793
+ if sz_break && !is_a_or_h?(v)
794
+ buf << ' '
795
+ end
796
+ else
797
+ buf << sep
798
+ # if break on each (and breaking will not occur because next is an array or hash)
799
+ # or, if indenting, and previous was an array or hash, then break and continue on next line
800
+ # indented.
801
+ if (sz_break && !is_a_or_h?(v)) || (format.alt? && i > 0 && is_a_or_h?(val[i-1]) && !is_a_or_h?(v))
802
+ buf << "\n"
803
+ buf << children_indentation.padding
804
+ elsif !(format.alt? && is_a_or_h?(v))
805
+ buf << ' '
806
+ end
807
+ end
808
+ buf << str_val
809
+ end
810
+ buf << delims[1]
811
+ buf
812
+ else
813
+ raise FormatError.new('Array', format.format, 'asp')
814
+ end
815
+ end
816
+
817
+ def is_a_or_h?(x)
818
+ x.is_a?(Array) || x.is_a?(Hash)
819
+ end
820
+
821
+ def is_container?(t)
822
+ case t
823
+ when PArrayType, PHashType, PStructType, PTupleType
824
+ true
825
+ else
826
+ false
827
+ end
828
+ end
829
+
830
+ # @api private
831
+ def string_PTupleType(val_type, val, format_map, indentation)
832
+ string_PArrayType(val_type, val, format_map, indentation)
833
+ end
834
+
835
+ # @api private
836
+ def string_PIteratorType(val_type, val, format_map, indentation)
837
+ v = val.to_a
838
+ _convert(TypeCalculator.infer_set(v), v, format_map, indentation)
839
+ end
840
+
841
+ # @api private
842
+ def string_PHashType(val_type, val, format_map, indentation)
843
+ format = get_format(val_type, format_map)
844
+ sep = format.separator || DEFAULT_HASH_FORMAT.separator
845
+ assoc = format.separator2 || DEFAULT_HASH_FORMAT.separator2
846
+ string_formats = format.container_string_formats || DEFAULT_CONTAINER_FORMATS
847
+ delims = format.delimiter_pair(DEFAULT_HASH_DELIMITERS)
848
+
849
+ sep = format.alt? ? "#{sep}\n" : "#{sep} "
850
+
851
+ cond_break = ''
852
+ padding = ''
853
+
854
+ case format.format
855
+ when :a
856
+ # Convert to array and use array rules
857
+ array_hash = val.to_a
858
+ _convert(TypeCalculator.infer_set(array_hash), array_hash, format_map, indentation)
859
+
860
+ when :h, :s, :p
861
+ indentation = indentation.indenting(format.alt? || indentation.is_indenting?)
862
+ buf = ''
863
+ if indentation.breaks?
864
+ buf << "\n"
865
+ buf << indentation.padding
866
+ end
867
+
868
+ children_indentation = indentation.increase
869
+ if format.alt?
870
+ cond_break = "\n"
871
+ padding = children_indentation.padding
872
+ end
873
+ buf << delims[0]
874
+ buf << cond_break # break after opening delimiter if pretty printing
875
+ buf << val.map do |k,v|
876
+ key_type = TypeCalculator.infer_set(k)
877
+ val_type = TypeCalculator.infer_set(v)
878
+ key = _convert(key_type, k, is_container?(key_type) ? format_map : string_formats, children_indentation)
879
+ val = _convert(val_type, v, is_container?(val_type) ? format_map : string_formats, children_indentation)
880
+ "#{padding}#{key}#{assoc}#{val}"
881
+ end.join(sep)
882
+ if format.alt?
883
+ buf << cond_break
884
+ buf << indentation.padding
885
+ end
886
+ buf << delims[1]
887
+ buf
888
+ else
889
+ raise FormatError.new('Hash', format.format, 'hasp')
890
+ end
891
+ end
892
+
893
+ # @api private
894
+ def string_PStructType(val_type, val, format_map, indentation)
895
+ string_PHashType(val_type, val, format_map, indentation)
896
+ end
897
+
898
+ # @api private
899
+ def string_PType(val_type, val, format_map, _)
900
+ f = get_format(val_type, format_map)
901
+ case f.format
902
+ when :s
903
+ str_val = f.alt? ? "\"#{val.to_s}\"" : val.to_s
904
+ Kernel.format(f.orig_fmt, str_val)
905
+ when :p
906
+ Kernel.format(f.orig_fmt.gsub('p', 's'), val.to_s)
907
+ else
908
+ raise FormatError.new('Type', f.format, 'sp')
909
+ end
910
+ end
911
+
912
+ # Maps the inferred type of o to a formatting rule
913
+ def get_format(val_t, format_options)
914
+ fmt = format_options.find {|k,_| k.assignable?(val_t) }
915
+ return fmt[1] unless fmt.nil?
916
+ return Format.new("%s")
917
+ end
918
+ private :get_format
919
+
920
+ end
921
+ end
922
+ end