puppet 4.7.1-universal-darwin → 4.8.0-universal-darwin

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 (276) hide show
  1. data/Gemfile +0 -3
  2. data/MAINTAINERS +76 -0
  3. data/README.md +0 -6
  4. data/Rakefile +2 -2
  5. data/lib/puppet/agent.rb +3 -3
  6. data/lib/puppet/application/apply.rb +1 -1
  7. data/lib/puppet/configurer.rb +2 -2
  8. data/lib/puppet/data_providers.rb +1 -0
  9. data/lib/puppet/data_providers/data_adapter.rb +1 -0
  10. data/lib/puppet/data_providers/data_function_support.rb +1 -0
  11. data/lib/puppet/data_providers/function_env_data_provider.rb +1 -0
  12. data/lib/puppet/data_providers/function_module_data_provider.rb +1 -0
  13. data/lib/puppet/data_providers/hiera_config.rb +1 -0
  14. data/lib/puppet/data_providers/hiera_env_data_provider.rb +1 -0
  15. data/lib/puppet/data_providers/hiera_interpolate.rb +1 -0
  16. data/lib/puppet/data_providers/hiera_module_data_provider.rb +1 -0
  17. data/lib/puppet/data_providers/hiera_support.rb +1 -2
  18. data/lib/puppet/data_providers/json_data_provider_factory.rb +2 -0
  19. data/lib/puppet/data_providers/yaml_data_provider_factory.rb +2 -0
  20. data/lib/puppet/defaults.rb +20 -1
  21. data/lib/puppet/environments.rb +5 -2
  22. data/lib/puppet/face/catalog.rb +1 -1
  23. data/lib/puppet/face/epp.rb +57 -11
  24. data/lib/puppet/face/module/install.rb +6 -6
  25. data/lib/puppet/functions.rb +23 -24
  26. data/lib/puppet/functions/alert.rb +14 -0
  27. data/lib/puppet/functions/binary_file.rb +25 -0
  28. data/lib/puppet/functions/break.rb +22 -0
  29. data/lib/puppet/functions/contain.rb +33 -0
  30. data/lib/puppet/functions/crit.rb +14 -0
  31. data/lib/puppet/functions/debug.rb +14 -0
  32. data/lib/puppet/functions/emerg.rb +14 -0
  33. data/lib/puppet/functions/epp.rb +1 -1
  34. data/lib/puppet/functions/err.rb +14 -0
  35. data/lib/puppet/functions/find_file.rb +31 -0
  36. data/lib/puppet/functions/include.rb +21 -0
  37. data/lib/puppet/functions/info.rb +14 -0
  38. data/lib/puppet/functions/new.rb +1 -1
  39. data/lib/puppet/functions/next.rb +23 -0
  40. data/lib/puppet/functions/notice.rb +14 -0
  41. data/lib/puppet/functions/regsubst.rb +12 -16
  42. data/lib/puppet/functions/require.rb +37 -0
  43. data/lib/puppet/functions/return.rb +22 -0
  44. data/lib/puppet/functions/strftime.rb +35 -0
  45. data/lib/puppet/functions/warning.rb +14 -0
  46. data/lib/puppet/generate/models/type/type.rb +4 -0
  47. data/lib/puppet/generate/templates/type/pcore.erb +2 -1
  48. data/lib/puppet/indirector/face.rb +6 -1
  49. data/lib/puppet/network/http/error.rb +2 -2
  50. data/lib/puppet/network/http/handler.rb +2 -2
  51. data/lib/puppet/node/environment.rb +11 -0
  52. data/lib/puppet/parser/ast.rb +5 -0
  53. data/lib/puppet/parser/ast/pops_bridge.rb +17 -4
  54. data/lib/puppet/parser/compiler.rb +29 -1
  55. data/lib/puppet/parser/functions.rb +6 -0
  56. data/lib/puppet/parser/functions/assert_type.rb +1 -1
  57. data/lib/puppet/parser/functions/binary_file.rb +24 -0
  58. data/lib/puppet/parser/functions/break.rb +39 -0
  59. data/lib/puppet/parser/functions/contain.rb +7 -15
  60. data/lib/puppet/parser/functions/defined.rb +2 -2
  61. data/lib/puppet/parser/functions/dig.rb +1 -1
  62. data/lib/puppet/parser/functions/each.rb +1 -1
  63. data/lib/puppet/parser/functions/epp.rb +2 -2
  64. data/lib/puppet/parser/functions/filter.rb +1 -1
  65. data/lib/puppet/parser/functions/find_file.rb +28 -0
  66. data/lib/puppet/parser/functions/hiera.rb +4 -4
  67. data/lib/puppet/parser/functions/hiera_array.rb +1 -1
  68. data/lib/puppet/parser/functions/hiera_hash.rb +1 -1
  69. data/lib/puppet/parser/functions/hiera_include.rb +1 -1
  70. data/lib/puppet/parser/functions/include.rb +4 -8
  71. data/lib/puppet/parser/functions/inline_epp.rb +1 -1
  72. data/lib/puppet/parser/functions/lest.rb +1 -1
  73. data/lib/puppet/parser/functions/lookup.rb +4 -2
  74. data/lib/puppet/parser/functions/map.rb +1 -1
  75. data/lib/puppet/parser/functions/match.rb +1 -1
  76. data/lib/puppet/parser/functions/new.rb +414 -18
  77. data/lib/puppet/parser/functions/next.rb +38 -0
  78. data/lib/puppet/parser/functions/reduce.rb +1 -1
  79. data/lib/puppet/parser/functions/regsubst.rb +4 -2
  80. data/lib/puppet/parser/functions/require.rb +4 -27
  81. data/lib/puppet/parser/functions/return.rb +71 -0
  82. data/lib/puppet/parser/functions/reverse_each.rb +1 -1
  83. data/lib/puppet/parser/functions/scanf.rb +13 -8
  84. data/lib/puppet/parser/functions/slice.rb +1 -1
  85. data/lib/puppet/parser/functions/split.rb +1 -1
  86. data/lib/puppet/parser/functions/step.rb +1 -1
  87. data/lib/puppet/parser/functions/strftime.rb +185 -0
  88. data/lib/puppet/parser/functions/then.rb +1 -1
  89. data/lib/puppet/parser/functions/type.rb +1 -1
  90. data/lib/puppet/parser/functions/with.rb +3 -3
  91. data/lib/puppet/parser/resource.rb +8 -5
  92. data/lib/puppet/parser/scope.rb +1 -1
  93. data/lib/puppet/plugins/configuration.rb +8 -0
  94. data/lib/puppet/plugins/data_providers.rb +1 -0
  95. data/lib/puppet/plugins/data_providers/data_provider.rb +7 -28
  96. data/lib/puppet/plugins/data_providers/registry.rb +1 -0
  97. data/lib/puppet/pops.rb +4 -0
  98. data/lib/puppet/pops/evaluator/access_operator.rb +36 -5
  99. data/lib/puppet/pops/evaluator/closure.rb +81 -12
  100. data/lib/puppet/pops/evaluator/compare_operator.rb +24 -1
  101. data/lib/puppet/pops/evaluator/evaluator_impl.rb +29 -5
  102. data/lib/puppet/pops/evaluator/json_strict_literal_evaluator.rb +1 -1
  103. data/lib/puppet/pops/evaluator/runtime3_converter.rb +53 -62
  104. data/lib/puppet/pops/evaluator/runtime3_support.rb +15 -6
  105. data/lib/puppet/pops/functions/dispatch.rb +9 -2
  106. data/lib/puppet/pops/functions/dispatcher.rb +3 -1
  107. data/lib/puppet/pops/functions/function.rb +19 -2
  108. data/lib/puppet/pops/issues.rb +9 -0
  109. data/lib/puppet/pops/label_provider.rb +2 -2
  110. data/lib/puppet/pops/loader/loader.rb +17 -0
  111. data/lib/puppet/pops/loader/static_loader.rb +0 -41
  112. data/lib/puppet/pops/lookup.rb +12 -0
  113. data/lib/puppet/pops/lookup/context.rb +86 -0
  114. data/lib/puppet/pops/lookup/explainer.rb +46 -6
  115. data/lib/puppet/pops/lookup/invocation.rb +19 -0
  116. data/lib/puppet/pops/lookup/sub_lookup.rb +1 -1
  117. data/lib/puppet/pops/model/factory.rb +20 -8
  118. data/lib/puppet/pops/model/model_label_provider.rb +3 -0
  119. data/lib/puppet/pops/model/model_meta.rb +2 -0
  120. data/lib/puppet/pops/model/model_tree_dumper.rb +14 -0
  121. data/lib/puppet/pops/parser/egrammar.ra +11 -6
  122. data/lib/puppet/pops/parser/eparser.rb +1112 -1086
  123. data/lib/puppet/pops/parser/heredoc_support.rb +1 -2
  124. data/lib/puppet/pops/pcore.rb +1 -0
  125. data/lib/puppet/pops/puppet_stack.rb +3 -3
  126. data/lib/puppet/pops/resource/param.rb +5 -1
  127. data/lib/puppet/pops/resource/resource_type_impl.rb +8 -4
  128. data/lib/puppet/pops/resource/resource_type_set.pcore +1 -0
  129. data/lib/puppet/pops/serialization/abstract_reader.rb +19 -2
  130. data/lib/puppet/pops/serialization/abstract_writer.rb +16 -3
  131. data/lib/puppet/pops/serialization/deserializer.rb +5 -1
  132. data/lib/puppet/pops/serialization/extension.rb +2 -0
  133. data/lib/puppet/pops/serialization/json.rb +76 -26
  134. data/lib/puppet/pops/serialization/serializer.rb +5 -1
  135. data/lib/puppet/pops/serialization/time_factory.rb +2 -1
  136. data/lib/puppet/pops/time/timespan.rb +718 -0
  137. data/lib/puppet/pops/time/timestamp.rb +148 -0
  138. data/lib/puppet/pops/types/p_binary_type.rb +220 -0
  139. data/lib/puppet/pops/types/p_object_type.rb +12 -6
  140. data/lib/puppet/pops/types/p_sensitive_type.rb +5 -1
  141. data/lib/puppet/pops/types/p_timespan_type.rb +141 -0
  142. data/lib/puppet/pops/types/p_timestamp_type.rb +69 -0
  143. data/lib/puppet/pops/types/string_converter.rb +62 -0
  144. data/lib/puppet/pops/types/type_asserter.rb +1 -1
  145. data/lib/puppet/pops/types/type_calculator.rb +17 -3
  146. data/lib/puppet/pops/types/type_factory.rb +35 -1
  147. data/lib/puppet/pops/types/type_formatter.rb +64 -11
  148. data/lib/puppet/pops/types/type_mismatch_describer.rb +110 -61
  149. data/lib/puppet/pops/types/type_parser.rb +18 -4
  150. data/lib/puppet/pops/types/types.rb +98 -63
  151. data/lib/puppet/pops/validation.rb +9 -1
  152. data/lib/puppet/pops/validation/checker4_0.rb +7 -0
  153. data/lib/puppet/property.rb +1 -1
  154. data/lib/puppet/provider.rb +3 -6
  155. data/lib/puppet/provider/mcx/mcxcontent.rb +1 -1
  156. data/lib/puppet/provider/mount/parsed.rb +18 -4
  157. data/lib/puppet/provider/nameservice/directoryservice.rb +15 -7
  158. data/lib/puppet/provider/package/gem.rb +6 -1
  159. data/lib/puppet/provider/package/pip.rb +0 -1
  160. data/lib/puppet/provider/package/pkg.rb +5 -1
  161. data/lib/puppet/provider/package/pkgng.rb +1 -1
  162. data/lib/puppet/provider/package/yum.rb +10 -0
  163. data/lib/puppet/provider/service/launchd.rb +1 -0
  164. data/lib/puppet/provider/user/directoryservice.rb +6 -6
  165. data/lib/puppet/provider/yumrepo/inifile.rb +1 -1
  166. data/lib/puppet/provider/zpool/zpool.rb +1 -1
  167. data/lib/puppet/resource.rb +54 -12
  168. data/lib/puppet/resource/capability_finder.rb +15 -9
  169. data/lib/puppet/resource/catalog.rb +25 -6
  170. data/lib/puppet/resource/type.rb +3 -1
  171. data/lib/puppet/settings.rb +1 -1
  172. data/lib/puppet/settings/environment_conf.rb +12 -4
  173. data/lib/puppet/syntax_checkers/base64.rb +41 -0
  174. data/lib/puppet/syntax_checkers/json.rb +0 -2
  175. data/lib/puppet/transaction.rb +6 -0
  176. data/lib/puppet/transaction/additional_resource_generator.rb +5 -0
  177. data/lib/puppet/transaction/report.rb +7 -2
  178. data/lib/puppet/type.rb +2 -1
  179. data/lib/puppet/type/file/checksum.rb +1 -0
  180. data/lib/puppet/type/file/content.rb +4 -4
  181. data/lib/puppet/type/mount.rb +44 -0
  182. data/lib/puppet/type/ssh_authorized_key.rb +1 -1
  183. data/lib/puppet/type/tidy.rb +3 -0
  184. data/lib/puppet/type/user.rb +12 -6
  185. data/lib/puppet/util/log.rb +25 -0
  186. data/lib/puppet/util/plist.rb +8 -3
  187. data/lib/puppet/version.rb +1 -1
  188. data/lib/puppet_x.rb +7 -1
  189. data/spec/integration/application/apply_spec.rb +118 -0
  190. data/spec/integration/parser/compiler_spec.rb +28 -0
  191. data/spec/integration/parser/pcore_resource_spec.rb +40 -3
  192. data/spec/integration/provider/mount_spec.rb +2 -1
  193. data/spec/integration/util/windows/principal_spec.rb +2 -2
  194. data/spec/integration/util/windows/registry_spec.rb +4 -4
  195. data/spec/lib/puppet_spec/compiler.rb +5 -1
  196. data/spec/lib/puppet_spec/unindent.rb +5 -0
  197. data/spec/shared_contexts/types_setup.rb +6 -0
  198. data/spec/shared_examples/rhel_package_provider.rb +16 -0
  199. data/spec/spec_helper.rb +1 -0
  200. data/spec/unit/agent_spec.rb +11 -0
  201. data/spec/unit/application/lookup_spec.rb +94 -3
  202. data/spec/unit/capability_spec.rb +22 -0
  203. data/spec/unit/configurer_spec.rb +8 -0
  204. data/spec/unit/face/epp_face_spec.rb +22 -3
  205. data/spec/unit/functions/assert_type_spec.rb +3 -3
  206. data/spec/unit/functions/binary_file_spec.rb +46 -0
  207. data/spec/unit/functions/break_spec.rb +89 -0
  208. data/spec/unit/{parser/functions → functions}/contain_spec.rb +68 -3
  209. data/spec/unit/functions/find_file_spec.rb +69 -0
  210. data/spec/unit/functions/include_spec.rb +175 -0
  211. data/spec/unit/functions/logging_spec.rb +54 -0
  212. data/spec/unit/functions/lookup_spec.rb +3 -3
  213. data/spec/unit/functions/new_spec.rb +105 -5
  214. data/spec/unit/functions/next_spec.rb +93 -0
  215. data/spec/unit/functions/require_spec.rb +83 -0
  216. data/spec/unit/functions/return_spec.rb +105 -0
  217. data/spec/unit/{parser/functions → functions}/shared.rb +14 -11
  218. data/spec/unit/functions/strftime_spec.rb +152 -0
  219. data/spec/unit/functions4_spec.rb +22 -0
  220. data/spec/unit/indirector/face_spec.rb +10 -2
  221. data/spec/unit/network/http/error_spec.rb +1 -2
  222. data/spec/unit/network/http/handler_spec.rb +6 -5
  223. data/spec/unit/parser/functions/hiera_array_spec.rb +1 -1
  224. data/spec/unit/parser/functions/hiera_hash_spec.rb +1 -1
  225. data/spec/unit/parser/functions/hiera_include_spec.rb +1 -1
  226. data/spec/unit/parser/functions/hiera_spec.rb +1 -1
  227. data/spec/unit/parser/functions/lookup_spec.rb +1 -1
  228. data/spec/unit/parser/functions/regsubst_spec.rb +1 -1
  229. data/spec/unit/parser/functions/split_spec.rb +1 -1
  230. data/spec/unit/pops/evaluator/access_ops_spec.rb +81 -1
  231. data/spec/unit/pops/evaluator/arithmetic_ops_spec.rb +170 -0
  232. data/spec/unit/pops/evaluator/evaluating_parser_spec.rb +29 -4
  233. data/spec/unit/pops/evaluator/runtime3_converter_spec.rb +112 -4
  234. data/spec/unit/pops/loaders/dependency_loader_spec.rb +12 -0
  235. data/spec/unit/pops/loaders/static_loader_spec.rb +0 -26
  236. data/spec/unit/pops/lookup/context_spec.rb +149 -0
  237. data/spec/unit/pops/parser/parse_functions_spec.rb +19 -0
  238. data/spec/unit/pops/parser/parse_lambda_spec.rb +19 -0
  239. data/spec/unit/pops/puppet_stack_spec.rb +1 -1
  240. data/spec/unit/pops/resource/resource_type_impl_spec.rb +74 -0
  241. data/spec/unit/pops/serialization/packer_spec.rb +34 -14
  242. data/spec/unit/pops/serialization/serialization_spec.rb +67 -5
  243. data/spec/unit/pops/time/timespan_spec.rb +121 -0
  244. data/spec/unit/pops/types/p_binary_type_spec.rb +243 -0
  245. data/spec/unit/pops/types/p_object_type_spec.rb +7 -7
  246. data/spec/unit/pops/types/p_sensitive_type_spec.rb +1 -1
  247. data/spec/unit/pops/types/p_timespan_type_spec.rb +273 -0
  248. data/spec/unit/pops/types/p_timestamp_type_spec.rb +311 -0
  249. data/spec/unit/pops/types/p_type_set_type_spec.rb +13 -13
  250. data/spec/unit/pops/types/ruby_generator_spec.rb +12 -12
  251. data/spec/unit/pops/types/string_converter_spec.rb +89 -0
  252. data/spec/unit/pops/types/type_asserter_spec.rb +3 -3
  253. data/spec/unit/pops/types/type_calculator_spec.rb +113 -5
  254. data/spec/unit/pops/types/type_formatter_spec.rb +40 -0
  255. data/spec/unit/pops/types/type_mismatch_describer_spec.rb +49 -38
  256. data/spec/unit/pops/types/type_parser_spec.rb +87 -4
  257. data/spec/unit/pops/types/types_spec.rb +1 -1
  258. data/spec/unit/pops/validator/validator_spec.rb +23 -0
  259. data/spec/unit/provider/mount/parsed_spec.rb +47 -29
  260. data/spec/unit/provider/package/pkg_spec.rb +109 -99
  261. data/spec/unit/provider/ssh_authorized_key/parsed_spec.rb +1 -0
  262. data/spec/unit/provider/user/aix_spec.rb +1 -1
  263. data/spec/unit/provider/user/directoryservice_spec.rb +101 -30
  264. data/spec/unit/resource/capability_finder_spec.rb +29 -7
  265. data/spec/unit/resource/catalog_spec.rb +127 -0
  266. data/spec/unit/ssl/certificate_request_spec.rb +1 -1
  267. data/spec/unit/transaction/additional_resource_generator_spec.rb +30 -0
  268. data/spec/unit/transaction/persistence_spec.rb +1 -6
  269. data/spec/unit/transaction/report_spec.rb +23 -0
  270. data/spec/unit/transaction_spec.rb +38 -0
  271. data/spec/unit/type/mount_spec.rb +5 -0
  272. data/spec/unit/util/plist_spec.rb +14 -2
  273. metadata +71 -12
  274. data/spec/integration/parser/functions/require_spec.rb +0 -43
  275. data/spec/unit/parser/functions/include_spec.rb +0 -55
  276. data/spec/unit/parser/functions/require_spec.rb +0 -68
@@ -4,7 +4,8 @@ module Serialization
4
4
  # the created Time object can be serialized and deserialized using its
5
5
  # seconds and nanoseconds without loss of precision.
6
6
  #
7
- # @api public
7
+ # @deprecated No longer in use. Functionality replaced by Timestamp
8
+ # @api private
8
9
  class TimeFactory
9
10
 
10
11
  NANO_DENOMINATOR = 10**9
@@ -0,0 +1,718 @@
1
+ module Puppet::Pops
2
+ module Time
3
+ NSECS_PER_USEC = 1000
4
+ NSECS_PER_MSEC = NSECS_PER_USEC * 1000
5
+ NSECS_PER_SEC = NSECS_PER_MSEC * 1000
6
+ NSECS_PER_MIN = NSECS_PER_SEC * 60
7
+ NSECS_PER_HOUR = NSECS_PER_MIN * 60
8
+ NSECS_PER_DAY = NSECS_PER_HOUR * 24
9
+
10
+ KEY_STRING = 'string'.freeze
11
+ KEY_FORMAT = 'format'.freeze
12
+ KEY_NEGATIVE = 'negative'.freeze
13
+ KEY_DAYS = 'days'.freeze
14
+ KEY_HOURS = 'hours'.freeze
15
+ KEY_MINUTES = 'minutes'.freeze
16
+ KEY_SECONDS = 'seconds'.freeze
17
+ KEY_MILLISECONDS = 'milliseconds'.freeze
18
+ KEY_MICROSECONDS = 'microseconds'.freeze
19
+ KEY_NANOSECONDS = 'nanoseconds'.freeze
20
+
21
+ # TimeData is a Numeric that stores its value internally as nano-seconds but will be considered to be seconds and fractions of
22
+ # seconds when used in arithmetic or comparison with other Numeric types.
23
+ #
24
+ class TimeData < Numeric
25
+ include LabelProvider
26
+
27
+ attr_reader :nsecs
28
+
29
+ def initialize(nanoseconds)
30
+ @nsecs = nanoseconds
31
+ end
32
+
33
+ def <=>(o)
34
+ case o
35
+ when self.class
36
+ @nsecs <=> o.nsecs
37
+ when Integer
38
+ to_int <=> o
39
+ when Float
40
+ to_f <=> o
41
+ else
42
+ nil
43
+ end
44
+ end
45
+
46
+ def label(o)
47
+ Utils.name_to_segments(o.class.name).last
48
+ end
49
+
50
+ # @return [Float] the number of seconds
51
+ def to_f
52
+ @nsecs.fdiv(NSECS_PER_SEC)
53
+ end
54
+
55
+ # @return [Integer] the number of seconds with fraction part truncated
56
+ def to_int
57
+ @nsecs / NSECS_PER_SEC
58
+ end
59
+
60
+ def to_i
61
+ to_int
62
+ end
63
+
64
+ # @return [Complex] short for `#to_f.to_c`
65
+ def to_c
66
+ to_f.to_c
67
+ end
68
+
69
+ # @return [Rational] initial numerator is nano-seconds and denominator is nano-seconds per second
70
+ def to_r
71
+ Rational(@nsecs, NSECS_PER_SEC)
72
+ end
73
+
74
+ undef_method :phase, :polar, :rect, :rectangular
75
+ end
76
+
77
+ class Timespan < TimeData
78
+ def self.from_fields(negative, days, hours, minutes, seconds, milliseconds = 0, microseconds = 0, nanoseconds = 0)
79
+ ns = (((((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000 + milliseconds) * 1000 + microseconds) * 1000 + nanoseconds
80
+ new(negative ? -ns : ns)
81
+ end
82
+
83
+ def self.from_hash(hash)
84
+ hash.include?('string') ? from_string_hash(hash) : from_fields_hash(hash)
85
+ end
86
+
87
+ def self.from_string_hash(hash)
88
+ parse(hash[KEY_STRING], hash[KEY_FORMAT] || Format::DEFAULTS)
89
+ end
90
+
91
+ def self.from_fields_hash(hash)
92
+ from_fields(
93
+ hash[KEY_NEGATIVE] || false,
94
+ hash[KEY_DAYS] || 0,
95
+ hash[KEY_HOURS] || 0,
96
+ hash[KEY_MINUTES] || 0,
97
+ hash[KEY_SECONDS] || 0,
98
+ hash[KEY_MILLISECONDS] || 0,
99
+ hash[KEY_MICROSECONDS] || 0,
100
+ hash[KEY_NANOSECONDS] || 0)
101
+ end
102
+
103
+ def self.parse(str, format = Format::DEFAULTS)
104
+ if format.is_a?(::Array)
105
+ format.each do |fmt|
106
+ fmt = FormatParser.singleton.parse_format(fmt) unless fmt.is_a?(Format)
107
+ begin
108
+ return fmt.parse(str)
109
+ rescue ArgumentError
110
+ end
111
+ end
112
+ raise ArgumentError, "Unable to parse '#{str}' using any of the formats #{format.join(', ')}"
113
+ end
114
+ format = FormatParser.singleton.parse_format(format) unless format.is_a?(Format)
115
+ format.parse(str)
116
+ end
117
+
118
+ # @return [true] if the stored value is negative
119
+ def negative?
120
+ @nsecs < 0
121
+ end
122
+
123
+ def +(o)
124
+ case o
125
+ when Timestamp
126
+ Timestamp.new(@nsecs + o.nsecs)
127
+ when Timespan
128
+ Timespan.new(@nsecs + o.nsecs)
129
+ when Integer, Float
130
+ # Add seconds
131
+ Timespan.new(@nsecs + (o * NSECS_PER_SEC).to_i)
132
+ else
133
+ raise ArgumentError, "#{a_an_uc(o)} cannot be added to a Timespan" unless o.is_a?(Timespan)
134
+ end
135
+ end
136
+
137
+ def -(o)
138
+ case o
139
+ when Timespan
140
+ Timespan.new(@nsecs - o.nsecs)
141
+ when Integer, Float
142
+ # Subtract seconds
143
+ Timespan.new(@nsecs - (o * NSECS_PER_SEC).to_i)
144
+ else
145
+ raise ArgumentError, "#{a_an_uc(o)} cannot be subtracted from a Timespan"
146
+ end
147
+ end
148
+
149
+ def -@
150
+ Timespan.new(-@nsecs)
151
+ end
152
+
153
+ def *(o)
154
+ case o
155
+ when Integer, Float
156
+ Timespan.new((@nsecs * o).to_i)
157
+ else
158
+ raise ArgumentError, "A Timestamp cannot be multiplied by #{a_an(o)}"
159
+ end
160
+ end
161
+
162
+ def divmod(o)
163
+ case o
164
+ when Integer
165
+ to_i.divmod(o)
166
+ when Float
167
+ to_f.divmod(o)
168
+ else
169
+ raise ArgumentError, "Can not do modulus on a Timespan using a #{a_an(o)}"
170
+ end
171
+ end
172
+
173
+ def modulo(o)
174
+ divmod(o)[1]
175
+ end
176
+
177
+ def %(o)
178
+ modulo(o)
179
+ end
180
+
181
+ def div(o)
182
+ case o
183
+ when Timespan
184
+ # Timespan/Timespan yields a Float
185
+ @nsecs.fdiv(o.nsecs)
186
+ when Integer, Float
187
+ Timespan.new(@nsecs.div(o))
188
+ else
189
+ raise ArgumentError, "A Timespan cannot be divided by #{a_an(o)}"
190
+ end
191
+ end
192
+
193
+ def /(o)
194
+ div(o)
195
+ end
196
+
197
+ # @return [Integer] a positive integer denoting the number of days
198
+ def days
199
+ total_days
200
+ end
201
+
202
+ # @return [Integer] a positive integer, 0 - 23 denoting hours of day
203
+ def hours
204
+ total_hours % 24
205
+ end
206
+
207
+ # @return [Integer] a positive integer, 0 - 59 denoting minutes of hour
208
+ def minutes
209
+ total_minutes % 60
210
+ end
211
+
212
+ # @return [Integer] a positive integer, 0 - 59 denoting seconds of minute
213
+ def seconds
214
+ total_seconds % 60
215
+ end
216
+
217
+ # @return [Integer] a positive integer, 0 - 999 denoting milliseconds of second
218
+ def milliseconds
219
+ total_milliseconds % 1000
220
+ end
221
+
222
+ # @return [Integer] a positive integer, 0 - 999.999.999 denoting nanoseconds of second
223
+ def nanoseconds
224
+ total_nanoseconds % NSECS_PER_SEC
225
+ end
226
+
227
+ # Formats this timestamp into a string according to the given `format`
228
+ #
229
+ # @param [String,Format] format The format to use when producing the string
230
+ # @return [String] the string representing the formatted timestamp
231
+ # @raise [ArgumentError] if the format is a string with illegal format characters
232
+ # @api public
233
+ def format(format)
234
+ format = FormatParser.singleton.parse_format(format) unless format.is_a?(Format)
235
+ format.format(self)
236
+ end
237
+
238
+ # Formats this timestamp into a string according to {Format::DEFAULTS[0]}
239
+ #
240
+ # @return [String] the string representing the formatted timestamp
241
+ # @api public
242
+ def to_s
243
+ format(Format::DEFAULTS[0])
244
+ end
245
+
246
+ def to_hash(compact = false)
247
+ result = {}
248
+ n = nanoseconds
249
+ if compact
250
+ s = total_seconds
251
+ result[KEY_SECONDS] = negative? ? -s : s
252
+ result[KEY_NANOSECONDS] = negative? ? -n : n unless n == 0
253
+ else
254
+ add_unless_zero(result, KEY_DAYS, days)
255
+ add_unless_zero(result, KEY_HOURS, hours)
256
+ add_unless_zero(result, KEY_MINUTES, minutes)
257
+ add_unless_zero(result, KEY_SECONDS, seconds)
258
+ unless n == 0
259
+ add_unless_zero(result, KEY_NANOSECONDS, n % 1000)
260
+ n /= 1000
261
+ add_unless_zero(result, KEY_MICROSECONDS, n % 1000)
262
+ add_unless_zero(result, KEY_MILLISECONDS, n /= 1000)
263
+ end
264
+ result[KEY_NEGATIVE] = true if negative?
265
+ end
266
+ result
267
+ end
268
+
269
+ def add_unless_zero(result, key, value)
270
+ result[key] = value unless value == 0
271
+ end
272
+ private :add_unless_zero
273
+
274
+ # @api private
275
+ def total_days
276
+ total_nanoseconds / NSECS_PER_DAY
277
+ end
278
+
279
+ # @api private
280
+ def total_hours
281
+ total_nanoseconds / NSECS_PER_HOUR
282
+ end
283
+
284
+ # @api private
285
+ def total_minutes
286
+ total_nanoseconds / NSECS_PER_MIN
287
+ end
288
+
289
+ # @api private
290
+ def total_seconds
291
+ total_nanoseconds / NSECS_PER_SEC
292
+ end
293
+
294
+ # @api private
295
+ def total_milliseconds
296
+ total_nanoseconds / NSECS_PER_MSEC
297
+ end
298
+
299
+ # @api private
300
+ def total_microseconds
301
+ total_nanoseconds / NSECS_PER_USEC
302
+ end
303
+
304
+ # @api private
305
+ def total_nanoseconds
306
+ @nsecs.abs
307
+ end
308
+
309
+ # Represents a compiled Timestamp format. The format is used both when parsing a timestamp
310
+ # in string format and when producing a string from a timestamp instance.
311
+ #
312
+ class Format
313
+ # A segment is either a string that will be represented literally in the formatted timestamp
314
+ # or a value that corresponds to one of the possible format characters.
315
+ class Segment
316
+ def append_to(bld, ts)
317
+ raise NotImplementedError, "'#{self.class.name}' should implement #append_to"
318
+ end
319
+
320
+ def append_regexp(bld, ts)
321
+ raise NotImplementedError, "'#{self.class.name}' should implement #append_regexp"
322
+ end
323
+
324
+ def multiplier
325
+ raise NotImplementedError, "'#{self.class.name}' should implement #multiplier"
326
+ end
327
+ end
328
+
329
+ class LiteralSegment < Segment
330
+ def append_regexp(bld)
331
+ bld << "(#{Regexp.escape(@literal)})"
332
+ end
333
+
334
+ def initialize(literal)
335
+ @literal = literal
336
+ end
337
+
338
+ def append_to(bld, ts)
339
+ bld << @literal
340
+ end
341
+
342
+ def concat(codepoint)
343
+ @literal.concat(codepoint)
344
+ end
345
+
346
+ def nanoseconds
347
+ 0
348
+ end
349
+ end
350
+
351
+ class ValueSegment < Segment
352
+ def initialize(padchar, width, default_width)
353
+ @use_total = false
354
+ @padchar = padchar
355
+ @width = width
356
+ @default_width = default_width
357
+ @format = create_format
358
+ end
359
+
360
+ def create_format
361
+ case @padchar
362
+ when nil
363
+ '%d'
364
+ when ' '
365
+ "%#{@width || @default_width}d"
366
+ else
367
+ "%#{@padchar}#{@width || @default_width}d"
368
+ end
369
+ end
370
+
371
+ def append_regexp(bld)
372
+ if @width.nil?
373
+ case @padchar
374
+ when nil
375
+ bld << (use_total? ? '([0-9]+)' : "([0-9]{1,#{@default_width}})")
376
+ when '0'
377
+ bld << (use_total? ? '([0-9]+)' : "([0-9]{1,#{@default_width}})")
378
+ else
379
+ bld << (use_total? ? '\s*([0-9]+)' : "([0-9\\s]{1,#{@default_width}})")
380
+ end
381
+ else
382
+ case @padchar
383
+ when nil
384
+ bld << "([0-9]{1,#{@width}})"
385
+ when '0'
386
+ bld << "([0-9]{#{@width}})"
387
+ else
388
+ bld << "([0-9\\s]{#{@width}})"
389
+ end
390
+ end
391
+ end
392
+
393
+ def nanoseconds(group)
394
+ group.to_i * multiplier
395
+ end
396
+
397
+ def multiplier
398
+ 0
399
+ end
400
+
401
+ def set_use_total
402
+ @use_total = true
403
+ end
404
+
405
+ def use_total?
406
+ @use_total
407
+ end
408
+
409
+ def append_value(bld, n)
410
+ bld << sprintf(@format, n)
411
+ end
412
+ end
413
+
414
+ class DaySegment < ValueSegment
415
+ def initialize(padchar, width)
416
+ super(padchar, width, 1)
417
+ end
418
+
419
+ def multiplier
420
+ NSECS_PER_DAY
421
+ end
422
+
423
+ def append_to(bld, ts)
424
+ append_value(bld, ts.days)
425
+ end
426
+ end
427
+
428
+ class HourSegment < ValueSegment
429
+ def initialize(padchar, width)
430
+ super(padchar, width, 2)
431
+ end
432
+
433
+ def multiplier
434
+ NSECS_PER_HOUR
435
+ end
436
+
437
+ def append_to(bld, ts)
438
+ append_value(bld, use_total? ? ts.total_hours : ts.hours)
439
+ end
440
+ end
441
+
442
+ class MinuteSegment < ValueSegment
443
+ def initialize(padchar, width)
444
+ super(padchar, width, 2)
445
+ end
446
+
447
+ def multiplier
448
+ NSECS_PER_MIN
449
+ end
450
+
451
+ def append_to(bld, ts)
452
+ append_value(bld, use_total? ? ts.total_minutes : ts.minutes)
453
+ end
454
+ end
455
+
456
+ class SecondSegment < ValueSegment
457
+ def initialize(padchar, width)
458
+ super(padchar, width, 2)
459
+ end
460
+
461
+ def multiplier
462
+ NSECS_PER_SEC
463
+ end
464
+
465
+ def append_to(bld, ts)
466
+ append_value(bld, use_total? ? ts.total_seconds : ts.seconds)
467
+ end
468
+ end
469
+
470
+ # Class that assumes that leading zeroes are significant and that trailing zeroes are not and left justifies when formatting.
471
+ # Applicable after a decimal point, and hence to the %L and %N formats.
472
+ class FragmentSegment < ValueSegment
473
+ def nanoseconds(group)
474
+ # Using %L or %N to parse a string only makes sense when they are considered to be fractions. Using them
475
+ # as a total quantity would introduce ambiguities.
476
+ raise ArgumentError, 'Format specifiers %L and %N denotes fractions and must be used together with a specifier of higher magnitude' if use_total?
477
+ n = group.to_i
478
+ p = 9 - group.length
479
+ p <= 0 ? n : n * 10 ** p
480
+ end
481
+
482
+ def create_format
483
+ if @padchar.nil?
484
+ '%d'
485
+ else
486
+ "%-#{@width || @default_width}d"
487
+ end
488
+ end
489
+
490
+ def append_value(bld, n)
491
+ # Strip trailing zeroes when default format is used
492
+ n = n.to_s.sub(/\A([0-9]+?)0*\z/, '\1').to_i unless use_total? || @padchar == '0'
493
+ super(bld, n)
494
+ end
495
+ end
496
+
497
+ class MilliSecondSegment < FragmentSegment
498
+ def initialize(padchar, width)
499
+ super(padchar, width, 3)
500
+ end
501
+
502
+ def multiplier
503
+ NSECS_PER_MSEC
504
+ end
505
+
506
+ def append_to(bld, ts)
507
+ append_value(bld, use_total? ? ts.total_milliseconds : ts.milliseconds)
508
+ end
509
+ end
510
+
511
+ class NanoSecondSegment < FragmentSegment
512
+ def initialize(padchar, width)
513
+ super(padchar, width, 9)
514
+ end
515
+
516
+ def multiplier
517
+ width = @width || @default_width
518
+ if width < 9
519
+ 10 ** (9 - width)
520
+ else
521
+ 1
522
+ end
523
+ end
524
+
525
+ def append_to(bld, ts)
526
+ ns = ts.total_nanoseconds
527
+ width = @width || @default_width
528
+ if width < 9
529
+ # Truncate digits to the right, i.e. let %6N reflect microseconds
530
+ ns /= 10 ** (9 - width)
531
+ ns %= 10 ** width unless use_total?
532
+ else
533
+ ns %= NSECS_PER_SEC unless use_total?
534
+ end
535
+ append_value(bld, ns)
536
+ end
537
+ end
538
+
539
+ def initialize(format, segments)
540
+ @format = format.freeze
541
+ @segments = segments.freeze
542
+ end
543
+
544
+ def format(timespan)
545
+ bld = timespan.negative? ? '-' : ''
546
+ @segments.each { |segment| segment.append_to(bld, timespan) }
547
+ bld
548
+ end
549
+
550
+ def parse(timespan)
551
+ md = regexp.match(timespan)
552
+ raise ArgumentError, "Unable to parse '#{timespan}' using format '#{@format}'" if md.nil?
553
+ nanoseconds = 0
554
+ md.captures.each_with_index do |group, index|
555
+ segment = @segments[index]
556
+ next if segment.is_a?(LiteralSegment)
557
+ group.lstrip!
558
+ raise ArgumentError, "Unable to parse '#{timespan}' using format '#{@format}'" unless group =~ /\A[0-9]+\z/
559
+ nanoseconds += segment.nanoseconds(group)
560
+ end
561
+ Timespan.new(timespan.start_with?('-') ? -nanoseconds : nanoseconds)
562
+ end
563
+
564
+ def to_s
565
+ @format
566
+ end
567
+
568
+ private
569
+
570
+ def regexp
571
+ @regexp ||= build_regexp
572
+ end
573
+
574
+ def build_regexp
575
+ bld = '\A-?'
576
+ @segments.each { |segment| segment.append_regexp(bld) }
577
+ bld << '\z'
578
+ Regexp.new(bld)
579
+ end
580
+ end
581
+
582
+ # Parses a string into a Timestamp::Format instance
583
+ class FormatParser
584
+ def self.singleton
585
+ @singleton
586
+ end
587
+
588
+ def initialize
589
+ @formats = Hash.new { |hash, str| hash[str] = internal_parse(str) }
590
+ end
591
+
592
+ def parse_format(format)
593
+ @formats[format]
594
+ end
595
+
596
+ private
597
+
598
+ NSEC_MAX = 0
599
+ MSEC_MAX = 1
600
+ SEC_MAX = 2
601
+ MIN_MAX = 3
602
+ HOUR_MAX = 4
603
+ DAY_MAX = 5
604
+
605
+ SEGMENT_CLASS_BY_ORDINAL = [
606
+ Format::NanoSecondSegment, Format::MilliSecondSegment, Format::SecondSegment, Format::MinuteSegment, Format::HourSegment, Format::DaySegment
607
+ ]
608
+
609
+ def bad_format_specifier(format, start, position)
610
+ "Bad format specifier '#{format[start,position-start]}' in '#{format}', at position #{position}"
611
+ end
612
+
613
+ def append_literal(bld, codepoint)
614
+ if bld.empty? || !bld.last.is_a?(Format::LiteralSegment)
615
+ bld << Format::LiteralSegment.new(''.concat(codepoint))
616
+ else
617
+ bld.last.concat(codepoint)
618
+ end
619
+ end
620
+
621
+ # States used by the #internal_parser function
622
+ STATE_LITERAL = 0 # expects literal or '%'
623
+ STATE_PAD = 1 # expects pad, width, or format character
624
+ STATE_WIDTH = 2 # expects width, or format character
625
+
626
+ def internal_parse(str)
627
+ bld = []
628
+ raise ArgumentError, 'Format must be a String' unless str.is_a?(String)
629
+ highest = -1
630
+ state = STATE_LITERAL
631
+ padchar = '0'
632
+ width = nil
633
+ position = -1
634
+ fstart = 0
635
+
636
+ str.codepoints do |codepoint|
637
+ position += 1
638
+ if state == STATE_LITERAL
639
+ if codepoint == 0x25 # '%'
640
+ state = STATE_PAD
641
+ fstart = position
642
+ padchar = '0'
643
+ width = nil
644
+ else
645
+ append_literal(bld, codepoint)
646
+ end
647
+ next
648
+ end
649
+
650
+ case codepoint
651
+ when 0x25 # '%'
652
+ append_literal(bld, codepoint)
653
+ state = STATE_LITERAL
654
+ when 0x2D # '-'
655
+ raise ArgumentError, bad_format_specifier(str, fstart, position) unless state == STATE_PAD
656
+ padchar = nil
657
+ state = STATE_WIDTH
658
+ when 0x5F # '_'
659
+ raise ArgumentError, bad_format_specifier(str, fstart, position) unless state == STATE_PAD
660
+ padchar = ' '
661
+ state = STATE_WIDTH
662
+ when 0x44 # 'D'
663
+ highest = DAY_MAX
664
+ bld << Format::DaySegment.new(padchar, width)
665
+ state = STATE_LITERAL
666
+ when 0x48 # 'H'
667
+ highest = HOUR_MAX unless highest > HOUR_MAX
668
+ bld << Format::HourSegment.new(padchar, width)
669
+ state = STATE_LITERAL
670
+ when 0x4D # 'M'
671
+ highest = MIN_MAX unless highest > MIN_MAX
672
+ bld << Format::MinuteSegment.new(padchar, width)
673
+ state = STATE_LITERAL
674
+ when 0x53 # 'S'
675
+ highest = SEC_MAX unless highest > SEC_MAX
676
+ bld << Format::SecondSegment.new(padchar, width)
677
+ state = STATE_LITERAL
678
+ when 0x4C # 'L'
679
+ highest = MSEC_MAX unless highest > MSEC_MAX
680
+ bld << Format::MilliSecondSegment.new(padchar, width)
681
+ state = STATE_LITERAL
682
+ when 0x4E # 'N'
683
+ highest = NSEC_MAX unless highest > NSEC_MAX
684
+ bld << Format::NanoSecondSegment.new(padchar, width)
685
+ state = STATE_LITERAL
686
+ else # only digits allowed at this point
687
+ raise ArgumentError, bad_format_specifier(str, fstart, position) unless codepoint >= 0x30 && codepoint <= 0x39
688
+ if state == STATE_PAD && codepoint == 0x30
689
+ padchar = '0'
690
+ else
691
+ n = codepoint - 0x30
692
+ if width.nil?
693
+ width = n
694
+ else
695
+ width = width * 10 + n
696
+ end
697
+ end
698
+ state = STATE_WIDTH
699
+ end
700
+ end
701
+
702
+ raise ArgumentError, bad_format_specifier(str, fstart, position) unless state == STATE_LITERAL
703
+ unless highest == -1
704
+ hc = SEGMENT_CLASS_BY_ORDINAL[highest]
705
+ bld.find { |s| s.instance_of?(hc) }.set_use_total
706
+ end
707
+ Format.new(str, bld)
708
+ end
709
+
710
+ @singleton = FormatParser.new
711
+ end
712
+
713
+ class Format
714
+ DEFAULTS = ['%D-%H:%M:%S.%-N', '%H:%M:%S.%-N', '%M:%S.%-N', '%S.%-N', '%D-%H:%M:%S', '%H:%M:%S', '%D-%H:%M', '%S'].map { |str| FormatParser.singleton.parse_format(str) }
715
+ end
716
+ end
717
+ end
718
+ end