droonga-engine 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -1
  3. data/Rakefile +6 -0
  4. data/bin/droonga-engine-absorb-data +14 -14
  5. data/bin/droonga-engine-catalog-generate +24 -12
  6. data/bin/droonga-engine-catalog-modify +13 -7
  7. data/bin/droonga-engine-join +8 -8
  8. data/bin/droonga-engine-set-role +1 -1
  9. data/bin/droonga-engine-unjoin +2 -2
  10. data/lib/droonga/address.rb +3 -0
  11. data/lib/droonga/cluster.rb +16 -10
  12. data/lib/droonga/command/droonga_engine_service.rb +5 -2
  13. data/lib/droonga/command/remote_command_base.rb +3 -3
  14. data/lib/droonga/distributed_command_planner.rb +11 -1
  15. data/lib/droonga/engine.rb +12 -11
  16. data/lib/droonga/engine/version.rb +2 -2
  17. data/lib/droonga/engine_node.rb +28 -28
  18. data/lib/droonga/engine_state.rb +41 -36
  19. data/lib/droonga/forward_buffer.rb +21 -10
  20. data/lib/droonga/node_role.rb +2 -0
  21. data/lib/droonga/plugins/groonga/select.rb +3 -0
  22. data/lib/droonga/plugins/search.rb +3 -1
  23. data/lib/droonga/plugins/search/distributed_search_planner.rb +17 -5
  24. data/lib/droonga/plugins/system/statistics.rb +1 -0
  25. data/lib/droonga/searcher.rb +13 -4
  26. data/test/command/config/single_slice/catalog.json +38 -0
  27. data/test/command/config/single_slice/droonga-engine.yaml +4 -0
  28. data/test/command/run-test.rb +3 -2
  29. data/test/command/suite/catalog/fetch.expected.single_slice +50 -0
  30. data/test/command/suite/dump/column/index.expected.single_slice +86 -0
  31. data/test/command/suite/dump/column/scalar.expected.single_slice +52 -0
  32. data/test/command/suite/dump/column/vector.expected.single_slice +55 -0
  33. data/test/command/suite/dump/record/scalar.expected.single_slice +52 -0
  34. data/test/command/suite/dump/record/vector/reference.expected.single_slice +117 -0
  35. data/test/command/suite/dump/table/array.expected.single_slice +39 -0
  36. data/test/command/suite/dump/table/double_array_trie.expected.single_slice +40 -0
  37. data/test/command/suite/dump/table/hash.expected.single_slice +40 -0
  38. data/test/command/suite/dump/table/patricia_trie.expected.single_slice +40 -0
  39. data/test/command/suite/message/error/missing-dataset.test +3 -0
  40. data/test/command/suite/search/condition/query/nonexistent_column.expected.single_slice +26 -0
  41. data/test/command/suite/search/condition/query/syntax_error.expected.single_slice +26 -0
  42. data/test/command/suite/search/error/unknown-source.expected.single_slice +28 -0
  43. data/test/command/suite/search/output/attributes/invalid.expected.single_slice +24 -0
  44. data/test/command/suite/system/absorb-data/records.catalog.json.single_slice +44 -0
  45. data/test/command/suite/system/absorb-data/records.expected.single_slice +32 -0
  46. data/test/command/suite/system/statistics/object/count/per-volume/empty.test +1 -0
  47. data/test/command/suite/system/statistics/object/count/record.expected.single_slice +11 -0
  48. data/test/command/suite/system/statistics/object/count/schema.expected.single_slice +11 -0
  49. data/test/unit/catalog/test_generator.rb +3 -2
  50. data/test/unit/helper.rb +2 -1
  51. data/test/unit/helper/stub_serf.rb +28 -0
  52. data/test/unit/plugins/system/statistics/test_object_count.rb +135 -0
  53. data/test/unit/plugins/system/statistics/test_object_count_per_volume.rb +149 -0
  54. data/test/unit/plugins/test_basic.rb +0 -406
  55. data/test/unit/test_address.rb +111 -10
  56. data/test/unit/test_cluster.rb +232 -0
  57. data/test/unit/test_differ.rb +49 -0
  58. data/test/unit/test_engine_node.rb +556 -0
  59. data/test/unit/test_engine_state.rb +151 -0
  60. data/test/unit/test_forward_buffer.rb +106 -0
  61. data/test/unit/test_node_name.rb +160 -0
  62. data/test/unit/test_node_role.rb +53 -0
  63. data/test/unit/test_reducer.rb +525 -0
  64. metadata +111 -49
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bd5aad47933f58aee93072477c535d458556b183
4
- data.tar.gz: fc6e82289c99cc3ef4cdc269d0153ca6588dec7f
3
+ metadata.gz: 695a658bbd48a05e64b9e53f2764549212c63786
4
+ data.tar.gz: 799ed768d48b6e49150fa63ca4ccc7f956837495
5
5
  SHA512:
6
- metadata.gz: 08909575ab516f97f404396904a53a0920bdc7a6ad7ab8673e7065d681698dee2f7029c71cb74731824892df0eeb87b8de4383d4aa9d0107823e7a00f363a7dc
7
- data.tar.gz: fc2acfa0c8c133bc70ebad99f7d326e96fcc316fdedfb45347431e5ec3a96a751d4f00db03b9c21c4c44d03d26b8da5b0263615926e9321d50f44c60277f0cf9
6
+ metadata.gz: a4141f7a2c89b0cc5558ea24fe11a52c6aa4ea93bf323065866befabc32ca2b9060f72dc3b5a4a5c9ef5e8204027d2e67df0ffbea5612011a414caee7d7a2a07
7
+ data.tar.gz: 50a6459b3f7db24bdc112536f88aac95c6b6e1c69423293135fe03d06a3cde96a686b54ffb112517bfc6aa6e2a9979d36f66999c0cfa4c78c2c71fbb466d52e0
@@ -4,7 +4,8 @@ notifications:
4
4
  env:
5
5
  - DEFAULT_TEST_TASK=test:unit
6
6
  - DEFAULT_TEST_TASK=test:command:default
7
- - DEFAULT_TEST_TASK=test:command:version1
7
+ - DEFAULT_TEST_TASK=test:command:single_slice
8
+ # - DEFAULT_TEST_TASK=test:command:version1
8
9
  rvm:
9
10
  - 1.9.3
10
11
  - 2.0.0
data/Rakefile CHANGED
@@ -50,6 +50,11 @@ namespace :test do
50
50
  run_command_test
51
51
  end
52
52
 
53
+ desc "Run command test: single slice"
54
+ task :single_slice do
55
+ run_command_test("--config", "single_slice")
56
+ end
57
+
53
58
  desc "Run command test: version1"
54
59
  task :version1 do
55
60
  run_command_test("--config", "version1")
@@ -61,6 +66,7 @@ desc "Run test"
61
66
  task :test => [
62
67
  "test:unit",
63
68
  "test:command:default",
69
+ "test:command:single_slice",
64
70
  "test:command:version1",
65
71
  ]
66
72
 
@@ -83,46 +83,46 @@ module Droonga
83
83
  parser.version = Engine::VERSION
84
84
 
85
85
  parser.separator("")
86
- parser.separator("Destination node:")
86
+ parser.separator("Destination:")
87
87
  parser.on("--host=HOST",
88
- "Host name of the destination node.") do |host|
88
+ "Host name of the destination engine node to copy data.") do |host|
89
89
  options.host = host
90
90
  end
91
91
  parser.on("--port=PORT", Integer,
92
- "Port number of the destination node.",
92
+ "Port number to communicate with the destination engine node.",
93
93
  "(#{options.port})") do |port|
94
94
  options.port = port
95
95
  end
96
96
  parser.on("--tag=TAG", Integer,
97
- "Tag name of the destination node.",
97
+ "Tag name to communicate with the destination engine node.",
98
98
  "(#{options.tag})") do |tag|
99
99
  options.tag = tag
100
100
  end
101
101
  parser.on("--dataset=DATASET",
102
- "Name of the destination dataset.",
102
+ "Name of the destination dataset for copying data.",
103
103
  "(#{options.dataset})") do |dataset|
104
104
  options.dataset = dataset
105
105
  end
106
106
 
107
107
  parser.separator("")
108
- parser.separator("Source node:")
108
+ parser.separator("Source:")
109
109
  parser.on("--source-host=HOST",
110
- "Host name of the source node.",
110
+ "Host name of the soruce engine node to copy data.",
111
111
  "(#{options.source_host})") do |host|
112
112
  options.source_host = host
113
113
  end
114
114
  parser.on("--source-port=PORT", Integer,
115
- "Port number of the source node.",
115
+ "Port number to communicate with the soruce engine node.",
116
116
  "(#{options.source_port})") do |host|
117
117
  options.source_host = host
118
118
  end
119
119
  parser.on("--source-tag=TAG",
120
- "Tag name of the source node.",
120
+ "Tag name to communicate with the soruce engine node.",
121
121
  "(#{options.source_tag})") do |tag|
122
122
  options.source_tag = tag
123
123
  end
124
124
  parser.on("--source-dataset=DATASET",
125
- "Name of the source dataset.",
125
+ "Name of the soruce dataset for copying data.",
126
126
  "(#{options.source_dataset})") do |dataset|
127
127
  options.source_dataset = dataset
128
128
  end
@@ -130,7 +130,7 @@ module Droonga
130
130
  parser.separator("")
131
131
  parser.separator("Connection:")
132
132
  parser.on("--receiver-host=HOST",
133
- "Host name of this computer.",
133
+ "Host name of the computer you are running this command.",
134
134
  "(#{options.receiver_host})") do |host|
135
135
  options.receiver_host = host
136
136
  end
@@ -138,18 +138,18 @@ module Droonga
138
138
  parser.separator("")
139
139
  parser.separator("Miscellaneous:")
140
140
  parser.on("--records-per-second=N", Integer,
141
- "Maximum number of records per second to be absorbed.",
141
+ "Maximum number of records to be copied per one second.",
142
142
  "'#{Client::RateLimiter::NO_LIMIT}' means no limit.",
143
143
  "(#{options.messages_per_second})") do |n|
144
144
  options.messages_per_second = n
145
145
  end
146
146
  parser.on("--progress-interval-seconds=N", Integer,
147
- "Interval seconds to report progress.",
147
+ "Interval seconds to report progress of data copying.",
148
148
  "(#{options.progress_interval_seconds})") do |n|
149
149
  options.progress_interval_seconds = n
150
150
  end
151
151
  parser.on("--[no-]verbose",
152
- "Output details for internal operations.",
152
+ "Output details for internal operations or not.",
153
153
  "(#{options.verbose})") do |verbose|
154
154
  options.verbose = verbose
155
155
  end
@@ -43,59 +43,71 @@ end
43
43
  parser = OptionParser.new
44
44
  parser.version = Droonga::Engine::VERSION
45
45
  parser.on("--output=PATH",
46
- "Output catalog.json to PATH.",
46
+ "The output path of generated catalog.json to be saved as.",
47
47
  "\"-\" means the standard output.",
48
+ "Any existing file at the specified path will be overwritten without confirmation.",
48
49
  "(#{options.output_path})") do |path|
49
50
  options.output_path = path
50
51
  end
51
52
  parser.on("--dataset=NAME",
52
- "Add a dataset its name is NAME.",
53
- "And set the NAME to the current dataset.",
53
+ "The name of a new dataset.",
54
+ "This can be specified multiple times to define multiple datasets.",
54
55
  "(#{Droonga::Catalog::Generator::DEFAULT_DATASET})") do |name|
55
56
  current_dataset = datasets[name] = {}
56
57
  end
57
58
  parser.on("--n-workers=N", Integer,
58
- "Use N workers for the current dataset.",
59
+ "Number of workers for each volume in the dataset ",
60
+ "specified by the preceding --dataset option.",
59
61
  "(#{Droonga::Catalog::Generator::DEFAULT_N_WORKERS})") do |n|
60
62
  current_dataset[:n_workers] = n
61
63
  end
62
64
  parser.on("--hosts=NAME1,NAME2,...", Array,
63
- "Use given hosts for replicas of the current dataset.",
65
+ "Host names of engine nodes to be used as replicas in the dataset ",
66
+ "specified by the preceding --dataset option.",
64
67
  "(#{Droonga::Catalog::Generator::DEFAULT_HOSTS.join(",")})") do |hosts|
65
68
  current_dataset[:hosts] = hosts
66
69
  end
67
70
  parser.on("--port=PORT", Integer,
68
- "Use the PORT as the port for the current dataset.",
71
+ "Port number to communicate with engine nodes in the dataset ",
72
+ "specified by the preceding --dataset option.",
69
73
  "(#{Droonga::Catalog::Generator::DEFAULT_PORT})") do |port|
70
74
  current_dataset[:port] = port
71
75
  end
72
76
  parser.on("--tag=TAG",
73
- "Use the TAG as the tag for the current dataset.",
77
+ "Tag name to communicate with engine nodes in the dataset ",
78
+ "specified by the preceding --dataset option.",
74
79
  "(#{Droonga::Catalog::Generator::DEFAULT_TAG})") do |tag|
75
80
  current_dataset[:tag] = tag
76
81
  end
77
82
  parser.on("--n-slices=N", Integer,
78
- "Use N slices for each replica.",
83
+ "Number of slices for each replica in the dataset ",
84
+ "specified by the preceding --dataset option.",
79
85
  "(#{Droonga::Catalog::Generator::DEFAULT_N_SLICES})") do |n|
80
86
  current_dataset[:n_slices] = n
81
87
  end
82
88
  parser.on("--plugins=PLUGIN1,PLUGIN2,...", Array,
83
- "Use PLUGINS for the current dataset.",
89
+ "Plugin names activated for the dataset ",
90
+ "specified by the preceding --dataset option.",
84
91
  "(#{Droonga::Catalog::Generator::DEFAULT_PLUGINS.join(",")})") do |plugins|
85
92
  current_dataset[:plugins] = plugins
86
93
  end
87
94
  parser.on("--schema=PATH",
88
- "Use schema in JSON at PATH for the current dataset.") do |path|
95
+ "The path to a JSON file including schema definition for the dataset ",
96
+ "specified by the preceding --dataset option.") do |path|
89
97
  File.open(path) do |input|
90
98
  current_dataset[:schema] = JSON.parse(input.read)
91
99
  end
92
100
  end
93
101
  parser.on("--fact=TABLE",
94
- "Use TABLE as the fact table for the current dataset.") do |table|
102
+ "Name of the fact table in the dataset ",
103
+ "specified by the preceding --dataset option.") do |table|
95
104
  current_dataset[:fact] = table
96
105
  end
97
106
  parser.on("--replicas=PATH",
98
- "Use replicas in JSON at PATH for the current dataset.") do |path|
107
+ "The path to a JSON file including replicas definition for the dataset ",
108
+ "specified by the preceding --dataset option.",
109
+ "If this option is used, other options to define replicas in the dataset ",
110
+ "(--hosts, --port, --tag and --n-slices) are ignored.") do |path|
99
111
  File.open(path) do |input|
100
112
  current_dataset[:replicas] = JSON.parse(input.read)
101
113
  end
@@ -46,14 +46,15 @@ end
46
46
  parser = OptionParser.new
47
47
  parser.version = Droonga::Engine::VERSION
48
48
  parser.on("--source=PATH",
49
- "Path to an existing catalog.json.",
49
+ "The path to the catalog.json to be modified.",
50
50
  "\"-\" means the standard input.",
51
51
  "(#{options.source_path})") do |path|
52
52
  options.source_path = path
53
53
  end
54
54
  parser.on("--output=PATH",
55
- "Output catalog.json to PATH.",
55
+ "The output path of modified catalog.json to be saved as.",
56
56
  "\"-\" means the standard output.",
57
+ "Any existing file at the specified path will be overwritten without confirmation.",
57
58
  "(#{options.output_path})") do |path|
58
59
  options.output_path = path
59
60
  end
@@ -63,21 +64,26 @@ parser.on("--[no-]update",
63
64
  options.update = update
64
65
  end
65
66
  parser.on("--dataset=NAME",
66
- "Add a dataset its name is NAME.",
67
- "And set the NAME to the current dataset.",
67
+ "The name of an existing dataset to be modified.",
68
+ "This can be specified multiple times to modify multiple datasets.",
68
69
  "(#{Droonga::Catalog::Generator::DEFAULT_DATASET})") do |name|
69
70
  current_dataset = datasets[name] = {}
70
71
  end
71
72
  parser.on("--replica-hosts=NAME1,NAME2,...", Array,
72
- "Use given hosts as replicas for the current dataset.") do |hosts|
73
+ "Host names of engine nodes to be used as replicas in the dataset ",
74
+ "specified by the preceding --dataset option.",
75
+ "If you specify this option, all existing replica nodes ",
76
+ "defined in the dataset are replaced.") do |hosts|
73
77
  current_dataset[:replica_hosts] = hosts
74
78
  end
75
79
  parser.on("--add-replica-hosts=NAME1,NAME2,...", Array,
76
- "Use given hosts to be added as replicas to the current dataset.") do |hosts|
80
+ "Host names of engine nodes to be added to the cluster as replicas, ",
81
+ "in the dataset specified by the preceding --dataset option.") do |hosts|
77
82
  current_dataset[:add_replica_hosts] = hosts
78
83
  end
79
84
  parser.on("--remove-replica-hosts=NAME1,NAME2,...", Array,
80
- "Use given hosts to be removed as replicas from the current dataset.") do |hosts|
85
+ "Host names of engine nodes to be removed from the cluster, ",
86
+ "in the dataset specified by the preceding --dataset option.") do |hosts|
81
87
  current_dataset[:remove_replica_hosts] = hosts
82
88
  end
83
89
  parser.parse!(ARGV)
@@ -92,7 +92,7 @@ module Droonga
92
92
  private
93
93
  def parse_options
94
94
  options = Slop.parse(:help => true) do |option|
95
- option.on("no-copy", "Don't copy data from the source cluster.",
95
+ option.on("no-copy", "Don't copy data from the source node.",
96
96
  :default => false)
97
97
 
98
98
  option.separator("Target:")
@@ -100,33 +100,33 @@ module Droonga
100
100
  "Host name of the new node to be joined.",
101
101
  :required => true)
102
102
  option.on("replica-source-host=",
103
- "Host name of the soruce node in the cluster to be connected.",
103
+ "Host name of the soruce node in the cluster to join.",
104
104
  :required => true)
105
105
 
106
106
  option.on(:port=,
107
- "Port number of the source cluster to be connected.",
107
+ "Port number to communicate with engine nodes.",
108
108
  :as => Integer,
109
109
  :default => NodeName::DEFAULT_PORT)
110
110
  option.on(:tag=,
111
- "Tag name of the soruce cluster to be connected.",
111
+ "Tag name to communicate with engine nodes.",
112
112
  :default => NodeName::DEFAULT_TAG)
113
113
  option.on(:dataset=,
114
- "Dataset name of for the node to be joined.",
114
+ "Dataset name the node is going to join as a replica in.",
115
115
  :default => Catalog::Dataset::DEFAULT_NAME)
116
116
 
117
117
  option.separator("Connections:")
118
118
  option.on("receiver-host=",
119
- "Host name of this host.",
119
+ "Host name of the computer you are running this command.",
120
120
  :default => Socket.gethostname)
121
121
 
122
122
  option.separator("Miscellaneous:")
123
123
  option.on("records-per-second=",
124
- "Maximum number of records per second to be copied. " +
124
+ "Maximum number of records to be copied per one second. " +
125
125
  "'#{Client::RateLimiter::NO_LIMIT}' means no limit.",
126
126
  :as => Integer,
127
127
  :default => DataAbsorberClient::DEFAULT_MESSAGES_PER_SECOND)
128
128
  option.on("progress-interval-seconds=",
129
- "Interval seconds to report progress.",
129
+ "Interval seconds to report progress of data copying.",
130
130
  :as => Integer,
131
131
  :default => DataAbsorberClient::DEFAULT_PROGRESS_INTERVAL_SECONDS)
132
132
  option.on(:verbose, "Output details for internal operations.",
@@ -23,7 +23,7 @@ module Droonga
23
23
  def run
24
24
  parse_options do |option|
25
25
  option.on(:role=,
26
- "New role for the target node.",
26
+ "New role for the engine node.",
27
27
  :required => true)
28
28
  end
29
29
 
@@ -31,10 +31,10 @@ module Droonga
31
31
  def run
32
32
  parse_options do |option|
33
33
  option.on("receiver-host=",
34
- "Host name of this host.",
34
+ "Host name of the computer you are running this command.",
35
35
  :default => Socket.gethostname)
36
36
  option.on(:dataset=,
37
- "Dataset name of for the node to be unjoined.",
37
+ "Dataset name the node is going to be removed from.",
38
38
  :default => Catalog::Dataset::DEFAULT_NAME)
39
39
  end
40
40
 
@@ -68,6 +68,9 @@ module Droonga
68
68
  end
69
69
 
70
70
  def ==(other)
71
+ if other.is_a?(String)
72
+ return to_s == other
73
+ end
71
74
  other.is_a?(self.class) and to_a == other.to_a
72
75
  end
73
76
  end
@@ -17,6 +17,7 @@ require "droonga/loggable"
17
17
  require "droonga/changable"
18
18
  require "droonga/path"
19
19
  require "droonga/file_observer"
20
+ require "droonga/address"
20
21
  require "droonga/engine_node"
21
22
  require "droonga/differ"
22
23
 
@@ -63,12 +64,15 @@ module Droonga
63
64
 
64
65
  attr_accessor :catalog
65
66
 
66
- def initialize(loop, params)
67
- @loop = loop
67
+ def initialize(params)
68
+ @loop = params[:loop]
68
69
 
69
70
  @params = params
70
71
  @catalog = params[:catalog]
71
- @state = nil
72
+ @state = params[:state] || {}
73
+
74
+ @engine_nodes = nil
75
+ @on_change = nil
72
76
 
73
77
  reload
74
78
  end
@@ -158,7 +162,7 @@ module Droonga
158
162
 
159
163
  def forward(message, destination)
160
164
  receiver = destination["to"]
161
- receiver_node_name = receiver.match(/\A[^:]+:\d+\/[^.]+/).to_s
165
+ receiver_node_name = Address.parse(receiver).node
162
166
  raise NotStartedYet.new unless @engine_nodes
163
167
  @engine_nodes.each do |node|
164
168
  if node.name == receiver_node_name
@@ -215,21 +219,23 @@ module Droonga
215
219
  end
216
220
 
217
221
  def all_node_names
218
- raise NoCatalogLoaded.new unless @catalog
219
222
  @catalog.all_nodes
220
223
  end
221
224
 
222
225
  def create_engine_nodes
223
226
  all_node_names.collect do |name|
224
227
  node_state = @state[name] || {}
225
- EngineNode.new(@loop,
226
- name,
227
- node_state,
228
- :auto_close_timeout =>
229
- @params[:internal_connection_lifetime])
228
+ create_engine_node(:name => name,
229
+ :state => node_state)
230
230
  end
231
231
  end
232
232
 
233
+ def create_engine_node(params)
234
+ EngineNode.new(params.merge(:loop => @loop,
235
+ :auto_close_timeout =>
236
+ @params[:internal_connection_lifetime]))
237
+ end
238
+
233
239
  def log_tag
234
240
  "cluster_state"
235
241
  end
@@ -157,8 +157,11 @@ module Droonga
157
157
  end
158
158
 
159
159
  def run_engine
160
- @engine = Engine.new(@loop, @engine_name, @internal_engine_name,
161
- :internal_connection_lifetime => @internal_connection_lifetime)
160
+ @engine = Engine.new(:loop => @loop,
161
+ :name => @engine_name,
162
+ :internal_name => @internal_engine_name,
163
+ :internal_connection_lifetime =>
164
+ @internal_connection_lifetime)
162
165
  @engine.on_ready = lambda do
163
166
  @worker_process_agent.ready
164
167
  end
@@ -31,14 +31,14 @@ module Droonga
31
31
 
32
32
  option.separator("Connections:")
33
33
  option.on(:host=,
34
- "Host name of the target node.",
34
+ "Host name of the node to be operated.",
35
35
  :required => true)
36
36
  option.on(:port=,
37
- "Port number of the source cluster to be connected.",
37
+ "Port number to communicate with the engine node.",
38
38
  :as => Integer,
39
39
  :default => NodeName::DEFAULT_PORT)
40
40
  option.on(:tag=,
41
- "Tag name of the soruce cluster to be connected.",
41
+ "Tag name to communicate with the engine node.",
42
42
  :default => NodeName::DEFAULT_TAG)
43
43
 
44
44
  option.separator("Miscellaneous:")