runger_config 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +562 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +1121 -0
  5. data/lib/anyway/auto_cast.rb +53 -0
  6. data/lib/anyway/config.rb +473 -0
  7. data/lib/anyway/dynamic_config.rb +31 -0
  8. data/lib/anyway/ejson_parser.rb +40 -0
  9. data/lib/anyway/env.rb +73 -0
  10. data/lib/anyway/ext/deep_dup.rb +48 -0
  11. data/lib/anyway/ext/deep_freeze.rb +44 -0
  12. data/lib/anyway/ext/flatten_names.rb +37 -0
  13. data/lib/anyway/ext/hash.rb +40 -0
  14. data/lib/anyway/ext/string_constantize.rb +24 -0
  15. data/lib/anyway/loaders/base.rb +21 -0
  16. data/lib/anyway/loaders/doppler.rb +63 -0
  17. data/lib/anyway/loaders/ejson.rb +89 -0
  18. data/lib/anyway/loaders/env.rb +18 -0
  19. data/lib/anyway/loaders/yaml.rb +84 -0
  20. data/lib/anyway/loaders.rb +79 -0
  21. data/lib/anyway/option_parser_builder.rb +29 -0
  22. data/lib/anyway/optparse_config.rb +92 -0
  23. data/lib/anyway/rails/autoload.rb +42 -0
  24. data/lib/anyway/rails/config.rb +23 -0
  25. data/lib/anyway/rails/loaders/credentials.rb +64 -0
  26. data/lib/anyway/rails/loaders/secrets.rb +37 -0
  27. data/lib/anyway/rails/loaders/yaml.rb +9 -0
  28. data/lib/anyway/rails/loaders.rb +5 -0
  29. data/lib/anyway/rails/settings.rb +83 -0
  30. data/lib/anyway/rails.rb +24 -0
  31. data/lib/anyway/railtie.rb +28 -0
  32. data/lib/anyway/rbs.rb +92 -0
  33. data/lib/anyway/settings.rb +111 -0
  34. data/lib/anyway/testing/helpers.rb +36 -0
  35. data/lib/anyway/testing.rb +13 -0
  36. data/lib/anyway/tracing.rb +188 -0
  37. data/lib/anyway/type_casting.rb +144 -0
  38. data/lib/anyway/utils/deep_merge.rb +21 -0
  39. data/lib/anyway/utils/which.rb +18 -0
  40. data/lib/anyway/version.rb +5 -0
  41. data/lib/anyway.rb +3 -0
  42. data/lib/anyway_config.rb +54 -0
  43. data/lib/generators/anyway/app_config/USAGE +9 -0
  44. data/lib/generators/anyway/app_config/app_config_generator.rb +17 -0
  45. data/lib/generators/anyway/config/USAGE +13 -0
  46. data/lib/generators/anyway/config/config_generator.rb +51 -0
  47. data/lib/generators/anyway/config/templates/config.rb.tt +12 -0
  48. data/lib/generators/anyway/config/templates/config.yml.tt +13 -0
  49. data/lib/generators/anyway/install/USAGE +4 -0
  50. data/lib/generators/anyway/install/install_generator.rb +47 -0
  51. data/lib/generators/anyway/install/templates/application_config.rb.tt +17 -0
  52. data/sig/anyway_config.rbs +149 -0
  53. data/sig/manifest.yml +6 -0
  54. metadata +202 -0
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ # Contains a mapping between type IDs/names and deserializers
5
+ class TypeRegistry
6
+ class << self
7
+ def default
8
+ @default ||= TypeRegistry.new
9
+ end
10
+ end
11
+
12
+ def initialize
13
+ @registry = {}
14
+ end
15
+
16
+ def accept(name_or_object, &block)
17
+ if !block && !name_or_object.respond_to?(:call)
18
+ raise ArgumentError, "Please, provide a type casting block or an object implementing #call(val) method"
19
+ end
20
+
21
+ registry[name_or_object] = block || name_or_object
22
+ end
23
+
24
+ def deserialize(raw, type_id, array: false)
25
+ return if raw.nil?
26
+
27
+ caster =
28
+ if type_id.is_a?(Symbol) || type_id.nil?
29
+ registry.fetch(type_id) { raise ArgumentError, "Unknown type: #{type_id}" }
30
+ else
31
+ raise ArgumentError, "Type must implement #call(val): #{type_id}" unless type_id.respond_to?(:call)
32
+ type_id
33
+ end
34
+
35
+ if array
36
+ raw_arr = raw.is_a?(String) ? raw.split(/\s*,\s*/) : Array(raw)
37
+ raw_arr.map { caster.call(_1) }
38
+ else
39
+ caster.call(raw)
40
+ end
41
+ end
42
+
43
+ def dup
44
+ new_obj = self.class.allocate
45
+ new_obj.instance_variable_set(:@registry, registry.dup)
46
+ new_obj
47
+ end
48
+
49
+ private
50
+
51
+ attr_reader :registry
52
+ end
53
+
54
+ TypeRegistry.default.tap do |obj|
55
+ obj.accept(nil, &:itself)
56
+ obj.accept(:string, &:to_s)
57
+ obj.accept(:integer, &:to_i)
58
+ obj.accept(:float, &:to_f)
59
+
60
+ obj.accept(:date) do
61
+ require "date" unless defined?(::Date)
62
+
63
+ next _1 if _1.is_a?(::Date)
64
+
65
+ next _1.to_date if _1.respond_to?(:to_date)
66
+
67
+ ::Date.parse(_1)
68
+ end
69
+
70
+ obj.accept(:datetime) do
71
+ require "date" unless defined?(::Date)
72
+
73
+ next _1 if _1.is_a?(::DateTime)
74
+
75
+ next _1.to_datetime if _1.respond_to?(:to_datetime)
76
+
77
+ ::DateTime.parse(_1)
78
+ end
79
+
80
+ obj.accept(:uri) do
81
+ require "uri" unless defined?(::URI)
82
+
83
+ next _1 if _1.is_a?(::URI)
84
+
85
+ ::URI.parse(_1)
86
+ end
87
+
88
+ obj.accept(:boolean) do
89
+ _1.to_s.match?(/\A(true|t|yes|y|1)\z/i)
90
+ end
91
+ end
92
+
93
+ unless "".respond_to?(:safe_constantize)
94
+ require "anyway/ext/string_constantize"
95
+ using Anyway::Ext::StringConstantize
96
+ end
97
+
98
+ # TypeCaster is an object responsible for type-casting.
99
+ # It uses a provided types registry and mapping, and also
100
+ # accepts a fallback typecaster.
101
+ class TypeCaster
102
+ using Ext::DeepDup
103
+ using Ext::Hash
104
+
105
+ def initialize(mapping, registry: TypeRegistry.default, fallback: ::Anyway::AutoCast)
106
+ @mapping = mapping.deep_dup
107
+ @registry = registry
108
+ @fallback = fallback
109
+ end
110
+
111
+ def coerce(key, val, config: mapping)
112
+ caster_config = config[key.to_sym]
113
+
114
+ return fallback.coerce(key, val) unless caster_config
115
+
116
+ case caster_config
117
+ in Hash[array:, type:, **nil]
118
+ registry.deserialize(val, type, array: array)
119
+ in Hash[config: subconfig]
120
+ subconfig = subconfig.safe_constantize if subconfig.is_a?(::String)
121
+ raise ArgumentError, "Config is not found: #{subconfig}" unless subconfig
122
+
123
+ subconfig.new(val)
124
+ in Hash
125
+ return val unless val.is_a?(Hash)
126
+
127
+ caster_config.each do |k, v|
128
+ ks = k.to_s
129
+ next unless val.key?(ks)
130
+
131
+ val[ks] = coerce(k, val[ks], config: caster_config)
132
+ end
133
+
134
+ val
135
+ else
136
+ registry.deserialize(val, caster_config)
137
+ end
138
+ end
139
+
140
+ private
141
+
142
+ attr_reader :mapping, :registry, :fallback
143
+ end
144
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ using Anyway::Ext::DeepDup
5
+
6
+ module Utils
7
+ def self.deep_merge!(source, other)
8
+ other.each do |key, other_value|
9
+ this_value = source[key]
10
+
11
+ if this_value.is_a?(::Hash) && other_value.is_a?(::Hash)
12
+ deep_merge!(this_value, other_value)
13
+ else
14
+ source[key] = other_value.deep_dup
15
+ end
16
+ end
17
+
18
+ source
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway
4
+ module Utils
5
+ # Cross-platform solution
6
+ # taken from https://stackoverflow.com/a/5471032
7
+ def self.which(cmd)
8
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
9
+ ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
10
+ exts.each do |ext|
11
+ exe = File.join(path, "#{cmd}#{ext}")
12
+ return exe if File.executable?(exe) && !File.directory?(exe)
13
+ end
14
+ end
15
+ nil
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyway # :nodoc:
4
+ VERSION = "2.6.0"
5
+ end
data/lib/anyway.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "anyway_config"
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-next"
4
+
5
+ require "ruby-next/language/setup"
6
+ RubyNext::Language.setup_gem_load_path(transpile: true)
7
+
8
+ require "anyway/version"
9
+
10
+ require "anyway/ext/deep_dup"
11
+ require "anyway/ext/deep_freeze"
12
+ require "anyway/ext/hash"
13
+ require "anyway/ext/flatten_names"
14
+
15
+ require "anyway/utils/deep_merge"
16
+ require "anyway/utils/which"
17
+
18
+ require "anyway/settings"
19
+ require "anyway/tracing"
20
+ require "anyway/config"
21
+ require "anyway/auto_cast"
22
+ require "anyway/type_casting"
23
+ require "anyway/env"
24
+ require "anyway/loaders"
25
+ require "anyway/rbs"
26
+
27
+ module Anyway # :nodoc:
28
+ class << self
29
+ def env
30
+ @env ||= ::Anyway::Env.new
31
+ end
32
+
33
+ def loaders
34
+ @loaders ||= ::Anyway::Loaders::Registry.new
35
+ end
36
+ end
37
+
38
+ # Configure default loaders
39
+ loaders.append :yml, Loaders::YAML
40
+ loaders.append :ejson, Loaders::EJSON if Utils.which("ejson")
41
+ loaders.append :env, Loaders::Env
42
+
43
+ if ENV.key?("DOPPLER_TOKEN") && ENV["ANYWAY_CONFIG_DISABLE_DOPPLER"] != "true"
44
+ loaders.append :doppler, Loaders::Doppler
45
+ end
46
+ end
47
+
48
+ if defined?(::Rails::VERSION)
49
+ require "anyway/rails"
50
+ else
51
+ require "anyway/rails/autoload"
52
+ end
53
+
54
+ require "anyway/testing" if ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test"
@@ -0,0 +1,9 @@
1
+ Description:
2
+ Generates a config class with the given name and list of parameters
3
+ and put it into `app/configs` folder.
4
+
5
+ Example:
6
+ rails generate app_config my_service param1 param2 ...
7
+
8
+ This will create:
9
+ app/configs/my_service_config.rb
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "generators/anyway/config/config_generator"
4
+
5
+ module Anyway
6
+ module Generators
7
+ class AppConfigGenerator < ConfigGenerator
8
+ source_root ConfigGenerator.source_root
9
+
10
+ private
11
+
12
+ def config_root
13
+ "app/configs"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ Description:
2
+ Generates a config class with the given name and list of parameters.
3
+
4
+ Use `--yml` / `--no-yml` option to specify whether you want to create a YAML config as well
5
+ (if no option is specified, the generator would ask you to choose during the run).
6
+
7
+ Use `--app` option to create config class in `app/configs` folder.
8
+
9
+ Example:
10
+ rails generate config my_service param1 param2 ...
11
+
12
+ This will create:
13
+ config/configs/my_service_config.rb
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Anyway
6
+ module Generators
7
+ class ConfigGenerator < ::Rails::Generators::NamedBase
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ class_option :yml, type: :boolean
11
+ class_option :app, type: :boolean, default: false
12
+ argument :parameters, type: :array, default: [], banner: "param1 param2"
13
+
14
+ # check_class_collision suffix: "Config"
15
+
16
+ def run_install_if_needed
17
+ return if ::Rails.root.join(static_config_root, "application_config.rb").exist?
18
+ generate "anyway:install"
19
+ end
20
+
21
+ def create_config
22
+ template "config.rb", File.join(config_root, class_path, "#{file_name}_config.rb")
23
+ end
24
+
25
+ def create_yml
26
+ create_yml = options.fetch(:yml) { yes?("Would you like to generate a #{file_name}.yml file?") }
27
+ return unless create_yml
28
+ template "config.yml", File.join("config", "#{file_name}.yml")
29
+ end
30
+
31
+ private
32
+
33
+ def static_config_root
34
+ Anyway::Settings.autoload_static_config_path || Anyway::DEFAULT_CONFIGS_PATH
35
+ end
36
+
37
+ def config_root
38
+ if options[:app]
39
+ "app/configs"
40
+ else
41
+ static_config_root
42
+ end
43
+ end
44
+
45
+ def needs_config_name?
46
+ raise "No longer needed" if Gem::Version.new(Anyway::VERSION) >= Gem::Version.new("3.0.0")
47
+ file_name.include?("_")
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ <% module_namespacing do -%>
4
+ class <%= class_name %>Config < ApplicationConfig
5
+ <%- if needs_config_name? %>
6
+ config_name :<%= file_name %>
7
+ <%- end -%>
8
+ <%- unless parameters.empty? -%>
9
+ attr_config <%= parameters.map { |param| ":#{param}" }.join(", ") %>
10
+ <%- end -%>
11
+ end
12
+ <% end -%>
@@ -0,0 +1,13 @@
1
+ default: &default
2
+ <%- parameters.each do |param| -%>
3
+ # <%= param %>: ""
4
+ <%- end -%>
5
+
6
+ development:
7
+ <<: *default
8
+
9
+ test:
10
+ <<: *default
11
+
12
+ production:
13
+ <<: *default
@@ -0,0 +1,4 @@
1
+ Description:
2
+ Generates a base config class (ApplicationConfig) for your application and
3
+ add the required configuriton (.gitignore, config/application.rb).
4
+
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Anyway
6
+ module Generators
7
+ class InstallGenerator < ::Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ class_option :configs_path, type: :string
11
+
12
+ def copy_application_config
13
+ template "application_config.rb", File.join(static_config_root, "application_config.rb")
14
+ end
15
+
16
+ def add_local_files_to_gitignore
17
+ if File.exist?(File.join(destination_root, ".gitignore"))
18
+ append_to_file ".gitignore", "\n/config/*.local.yml\n/config/credentials/local.*\n"
19
+ end
20
+ end
21
+
22
+ # rubocop:disable Layout/HeredocIndentation
23
+ def add_setup_autoload_to_config
24
+ maybe_comment_indented = default_configs_path? ? " # " : " "
25
+ inject_into_file "config/application.rb", after: %r{< Rails::Application\n} do
26
+ <<-RUBY
27
+ # Configure the path for configuration classes that should be used before initialization
28
+ # NOTE: path should be relative to the project root (Rails.root)
29
+ #{maybe_comment_indented}config.anyway_config.autoload_static_config_path = "#{static_config_root}"
30
+ #{maybe_comment_indented.sub(/\s+$/, "")}
31
+ RUBY
32
+ end
33
+ end
34
+ # rubocop:enable Layout/HeredocIndentation
35
+
36
+ private
37
+
38
+ def static_config_root
39
+ options[:configs_path] || Anyway::Settings.autoload_static_config_path || Anyway::DEFAULT_CONFIGS_PATH
40
+ end
41
+
42
+ def default_configs_path?
43
+ static_config_root == (Anyway::Settings.autoload_static_config_path || Anyway::DEFAULT_CONFIGS_PATH)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Base class for application config classes
4
+ class ApplicationConfig < Anyway::Config
5
+ class << self
6
+ # Make it possible to access a singleton config instance
7
+ # via class methods (i.e., without explicitly calling `instance`)
8
+ delegate_missing_to :instance
9
+
10
+ private
11
+
12
+ # Returns a singleton config instance
13
+ def instance
14
+ @instance ||= new
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,149 @@
1
+ module Anyway
2
+ def self.env: -> Env
3
+ def self.loaders: -> Loaders::Registry
4
+
5
+ class Settings
6
+ def self.default_config_path=: (String | Pathname | ^(untyped) -> String val) -> void
7
+ def self.future: -> Future
8
+ def self.current_environment: -> String?
9
+ def self.default_environmental_key: -> String?
10
+ def self.known_environments: -> Array[String]?
11
+
12
+ class Future
13
+ def self.setting: (untyped name, untyped default_value) -> untyped
14
+ def self.settings: -> Hash[untyped, untyped]
15
+ def use: (*untyped names) -> untyped
16
+ end
17
+ end
18
+
19
+ module Tracing
20
+ class Trace
21
+ def merge!: (Trace another_trace) -> void
22
+ end
23
+
24
+ def inspect: -> String
25
+ def self.capture: ?{ -> Hash[untyped, untyped]? } -> Trace
26
+ def self.trace_stack: -> Array[untyped]
27
+ def self.current_trace: -> Trace?
28
+ def self.source_stack: -> Array[untyped]
29
+ def self.current_trace_source: -> ({type: Symbol} & Hash[Symbol, untyped])
30
+ def self.with_trace_source: (untyped src) { -> void } -> untyped
31
+ def trace!: [V] (Symbol, *String paths, **untyped) ?{ -> V} -> V
32
+ def self.trace!: [V] (Symbol, *String paths, **untyped) ?{ -> V} -> V
33
+ end
34
+
35
+ module RBSGenerator
36
+ def to_rbs: -> String
37
+ end
38
+
39
+ module OptparseConfig
40
+ def option_parser: -> OptionParser
41
+ def parse_options!: (Array[String]) -> void
42
+
43
+ module ClassMethods
44
+ def ignore_options: (*Symbol args) -> void
45
+ def describe_options: (**(String | {desc: String, type: Module})) -> void
46
+ def flag_options: (*Symbol args) -> void
47
+ def extend_options: { (OptionParser, Config) -> void } -> void
48
+ end
49
+ end
50
+
51
+ module DynamicConfig
52
+ module ClassMethods
53
+ def for: (String | Symbol name, ?auto_cast: bool, **untyped) -> Hash[untyped, untyped]
54
+ end
55
+ end
56
+
57
+ type valueType = Symbol | nil
58
+ type arrayType = {array: bool, type: valueType}
59
+ type configType = {config: Class | String}
60
+ type hashType = Hash[Symbol, valueType | arrayType | hashType]
61
+
62
+ type mappingType = valueType | arrayType | hashType | configType
63
+ type envType = String | Symbol | Array[String | Symbol] | {except: String | Symbol | Array[String | Symbol]}
64
+
65
+ type requiredType = Array[Symbol | Hash[Symbol, requiredType]]
66
+
67
+ class Config
68
+ extend RBSGenerator
69
+ extend DynamicConfig::ClassMethods
70
+ extend OptparseConfig::ClassMethods
71
+ include DynamicConfig
72
+ include OptparseConfig
73
+
74
+ def self.attr_config: (*Symbol args, **untyped) -> void
75
+ def self.defaults: -> Hash[String, untyped]
76
+ def self.config_attributes: -> Array[Symbol]?
77
+ def self.required: (*Symbol names, ?env: envType, **requiredType) -> void
78
+ def self.required_attributes: -> Array[Symbol]
79
+ def self.on_load: (*Symbol callbacks) ?{ () [self: instance] -> void } -> void
80
+ def self.config_name: (?(Symbol | String) val) -> String?
81
+ def self.env_prefix: (?(Symbol | String) val) -> String
82
+ def self.coerce_types: (**mappingType) -> void
83
+ def self.coercion_mapping: -> Hash[untyped, untyped]?
84
+ def self.disable_auto_cast!: -> void
85
+
86
+ attr_reader config_name: String
87
+ attr_reader env_prefix: String
88
+
89
+ def initialize: (?Hash[Symbol | String, untyped] overrides) -> void
90
+ | (NilClass) -> void
91
+ def reload: (?Hash[Symbol | String, untyped] overrides) -> Config
92
+ def clear: -> void
93
+ def load: (Hash[Symbol | String, untyped] overrides) -> Config
94
+ | (NilClass) -> Config
95
+ def dig: (*(Symbol | String) keys) -> untyped
96
+ def to_h: -> Hash[untyped, untyped]
97
+ def dup: -> Config
98
+ def deconstruct_keys: (untyped keys) -> Hash[untyped, untyped]
99
+ def to_source_trace: -> Hash[String, untyped]
100
+ def inspect: -> String
101
+ def pretty_print: (untyped q) -> untyped
102
+ def as_env: -> Hash[String, String]
103
+
104
+ private
105
+ attr_reader values: Hash[untyped, untyped]
106
+ def raise_validation_error: (String msg) -> void
107
+ def flatten_hash: (Hash[untyped, untyped], String, Hash[String, String]) -> Hash[String, String]
108
+
109
+ class Error < StandardError
110
+ end
111
+
112
+ class ValidationError < Error
113
+ end
114
+ end
115
+
116
+ class Env
117
+ def clear: -> void
118
+ def fetch: (String prefix) -> untyped
119
+ def fetch_with_trace: (String prefix) -> [untyped, Tracing::Trace?]
120
+ end
121
+
122
+ module Loaders
123
+ class Base
124
+ include Tracing
125
+
126
+ def self.call: (?local: bool, **untyped) -> untyped
127
+ def initialize: (local: bool) -> void
128
+ def use_local?: -> bool
129
+ end
130
+
131
+ interface _Loader
132
+ def call: (**untyped) -> Hash[untyped, untyped]
133
+ end
134
+
135
+ class Registry
136
+ def prepend: (Symbol id, _Loader loader) -> void
137
+ | (Symbol id) { (**untyped) -> Hash[untyped, untyped] } -> void
138
+ def append: (Symbol id, _Loader loader) -> void
139
+ | (Symbol id) { (**untyped) -> Hash[untyped, untyped] } -> void
140
+ def insert_before: (Symbol another_id, Symbol id, _Loader loader) -> void
141
+ | (Symbol another_id, Symbol id) { (**untyped) -> Hash[untyped, untyped] } -> void
142
+ def insert_after: (Symbol another_id, Symbol id, _Loader loader) -> void
143
+ | (Symbol another_id, Symbol id) { (**untyped) -> Hash[untyped, untyped] } -> void
144
+ def override: (Symbol id, _Loader loader) -> void
145
+ | (Symbol id) { (**untyped) -> Hash[untyped, untyped] } -> void
146
+ def delete: (Symbol id) -> void
147
+ end
148
+ end
149
+ end
data/sig/manifest.yml ADDED
@@ -0,0 +1,6 @@
1
+ dependencies:
2
+ - name: optparse
3
+ - name: json
4
+ - name: pathname
5
+ - name: date
6
+ - name: time