gce-host 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.yardopts +6 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +210 -0
- data/Rakefile +30 -0
- data/bin/gce-host +4 -0
- data/docs/GCE.html +132 -0
- data/docs/GCE/Host.html +901 -0
- data/docs/GCE/Host/CLI.html +610 -0
- data/docs/GCE/Host/Config.html +1195 -0
- data/docs/GCE/Host/GCEClient.html +215 -0
- data/docs/GCE/Host/GCEClient/Error.html +127 -0
- data/docs/GCE/Host/GCEClient/NotFound.html +131 -0
- data/docs/GCE/Host/HashUtil.html +178 -0
- data/docs/GCE/Host/HostData.html +1658 -0
- data/docs/GCE/Host/RoleData.html +932 -0
- data/docs/GCE/Host/StringUtil.html +359 -0
- data/docs/_index.html +231 -0
- data/docs/class_list.html +58 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +57 -0
- data/docs/css/style.css +339 -0
- data/docs/file.LICENSE.html +95 -0
- data/docs/file.README.html +312 -0
- data/docs/file_list.html +63 -0
- data/docs/frames.html +26 -0
- data/docs/index.html +312 -0
- data/docs/js/app.js +219 -0
- data/docs/js/full_list.js +181 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +477 -0
- data/docs/top-level-namespace.html +112 -0
- data/example/example.conf +8 -0
- data/example/example.rb +6 -0
- data/gce-host.gemspec +26 -0
- data/lib/gce-host.rb +7 -0
- data/lib/gce/host.rb +120 -0
- data/lib/gce/host/cli.rb +151 -0
- data/lib/gce/host/config.rb +109 -0
- data/lib/gce/host/gce_client.rb +69 -0
- data/lib/gce/host/hash_util.rb +11 -0
- data/lib/gce/host/host_data.rb +227 -0
- data/lib/gce/host/role_data.rb +64 -0
- data/lib/gce/host/string_util.rb +31 -0
- data/lib/gce/host/version.rb +5 -0
- data/spec/gce_client_spec.rb +29 -0
- data/spec/host_spec.rb +199 -0
- data/spec/spec_helper.rb +22 -0
- data/terraform.tf +52 -0
- metadata +226 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
3
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
4
|
+
<head>
|
5
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
6
|
+
<title>
|
7
|
+
Top Level Namespace
|
8
|
+
|
9
|
+
— Documentation by YARD 0.8.7.6
|
10
|
+
|
11
|
+
</title>
|
12
|
+
|
13
|
+
<link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
|
14
|
+
|
15
|
+
<link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
|
16
|
+
|
17
|
+
<script type="text/javascript" charset="utf-8">
|
18
|
+
hasFrames = window.top.frames.main ? true : false;
|
19
|
+
relpath = '';
|
20
|
+
framesUrl = "frames.html#!top-level-namespace.html";
|
21
|
+
</script>
|
22
|
+
|
23
|
+
|
24
|
+
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
|
25
|
+
|
26
|
+
<script type="text/javascript" charset="utf-8" src="js/app.js"></script>
|
27
|
+
|
28
|
+
|
29
|
+
</head>
|
30
|
+
<body>
|
31
|
+
<div id="header">
|
32
|
+
<div id="menu">
|
33
|
+
|
34
|
+
<a href="_index.html">Index</a> »
|
35
|
+
|
36
|
+
|
37
|
+
<span class="title">Top Level Namespace</span>
|
38
|
+
|
39
|
+
|
40
|
+
<div class="noframes"><span class="title">(</span><a href="." target="_top">no frames</a><span class="title">)</span></div>
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<div id="search">
|
44
|
+
|
45
|
+
<a class="full_list_link" id="class_list_link"
|
46
|
+
href="class_list.html">
|
47
|
+
Class List
|
48
|
+
</a>
|
49
|
+
|
50
|
+
<a class="full_list_link" id="method_list_link"
|
51
|
+
href="method_list.html">
|
52
|
+
Method List
|
53
|
+
</a>
|
54
|
+
|
55
|
+
<a class="full_list_link" id="file_list_link"
|
56
|
+
href="file_list.html">
|
57
|
+
File List
|
58
|
+
</a>
|
59
|
+
|
60
|
+
</div>
|
61
|
+
<div class="clear"></div>
|
62
|
+
</div>
|
63
|
+
|
64
|
+
<iframe id="search_frame"></iframe>
|
65
|
+
|
66
|
+
<div id="content"><h1>Top Level Namespace
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
</h1>
|
71
|
+
|
72
|
+
<dl class="box">
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
</dl>
|
82
|
+
<div class="clear"></div>
|
83
|
+
|
84
|
+
<h2>Defined Under Namespace</h2>
|
85
|
+
<p class="children">
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
<strong class="classes">Classes:</strong> <span class='object_link'><a href="GCE.html" title="GCE (class)">GCE</a></span>
|
91
|
+
|
92
|
+
|
93
|
+
</p>
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
</div>
|
104
|
+
|
105
|
+
<div id="footer">
|
106
|
+
Generated on Wed Nov 23 20:08:31 2016 by
|
107
|
+
<a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
108
|
+
0.8.7.6 (ruby-2.3.2).
|
109
|
+
</div>
|
110
|
+
|
111
|
+
</body>
|
112
|
+
</html>
|
data/example/example.rb
ADDED
data/gce-host.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = "gce-host"
|
3
|
+
gem.version = '0.1.0'
|
4
|
+
gem.author = ['Naotoshi Seo']
|
5
|
+
gem.email = ['sonots@gmail.com']
|
6
|
+
gem.homepage = 'https://github.com/sonots/gce-host'
|
7
|
+
gem.summary = "Search hosts on GCP GCE"
|
8
|
+
gem.description = "Search hosts on GCP GCE"
|
9
|
+
gem.licenses = ['MIT']
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split("\n")
|
12
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
13
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
|
16
|
+
gem.add_runtime_dependency 'google-api-client'
|
17
|
+
gem.add_runtime_dependency 'dotenv'
|
18
|
+
|
19
|
+
gem.add_development_dependency 'yard'
|
20
|
+
gem.add_development_dependency 'rspec'
|
21
|
+
gem.add_development_dependency 'simplecov'
|
22
|
+
gem.add_development_dependency 'pry'
|
23
|
+
gem.add_development_dependency 'pry-nav'
|
24
|
+
gem.add_development_dependency 'rake'
|
25
|
+
gem.add_development_dependency 'bundler'
|
26
|
+
end
|
data/lib/gce-host.rb
ADDED
data/lib/gce/host.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
class GCE
|
4
|
+
# Search GCE hosts from labels
|
5
|
+
#
|
6
|
+
# require 'gce-host'
|
7
|
+
# # Search by hostname
|
8
|
+
# GCE::Host.new(hostname: 'test').first # => test
|
9
|
+
#
|
10
|
+
# # Search by `roles` label
|
11
|
+
# GCE::Host.new(
|
12
|
+
# role: 'admin:haikanko',
|
13
|
+
# ).each do |host|
|
14
|
+
# # ...
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# or
|
18
|
+
#
|
19
|
+
# GCE::Host.new(
|
20
|
+
# role1: 'admin',
|
21
|
+
# role2: 'haikanko',
|
22
|
+
# ).each do |host|
|
23
|
+
# # ...
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# # Or search
|
27
|
+
# GCE::Host.new(
|
28
|
+
# {
|
29
|
+
# role1: 'db',
|
30
|
+
# role2: 'master',
|
31
|
+
# },
|
32
|
+
# {
|
33
|
+
# role1: 'web',
|
34
|
+
# }
|
35
|
+
# ).each do |host|
|
36
|
+
# # ...
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# GCE::Host.me.hostname # => 'test'
|
40
|
+
class Host
|
41
|
+
include Enumerable
|
42
|
+
|
43
|
+
# @return [Host::Data] representing myself
|
44
|
+
def self.me
|
45
|
+
new(hostname: Socket.gethostname).each do |d|
|
46
|
+
return d
|
47
|
+
end
|
48
|
+
raise 'Not Found'
|
49
|
+
end
|
50
|
+
|
51
|
+
# Configure GCE::Host
|
52
|
+
#
|
53
|
+
# @param [Hash] params see GCE::Host::Config for configurable parameters
|
54
|
+
def self.configure(params = {})
|
55
|
+
Config.configure(params)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.gce_client
|
59
|
+
@gce_client ||= GCEClient.new
|
60
|
+
end
|
61
|
+
|
62
|
+
def gce_client
|
63
|
+
self.class.gce_client
|
64
|
+
end
|
65
|
+
|
66
|
+
attr_reader :conditions, :options
|
67
|
+
|
68
|
+
# @param [Array of Hash, or Hash] conditions (and options)
|
69
|
+
#
|
70
|
+
# GCE::Host.new(
|
71
|
+
# hostname: 'test',
|
72
|
+
# options: {a: 'b'}
|
73
|
+
# )
|
74
|
+
#
|
75
|
+
# GCE::Host.new(
|
76
|
+
# {
|
77
|
+
# hostname: 'foo',
|
78
|
+
# },
|
79
|
+
# {
|
80
|
+
# hostname: 'bar',
|
81
|
+
# },
|
82
|
+
# options: {a: 'b'}
|
83
|
+
# )
|
84
|
+
def initialize(*conditions)
|
85
|
+
conditions = [{}] if conditions.empty?
|
86
|
+
conditions = [conditions] if conditions.kind_of?(Hash)
|
87
|
+
@options = {}
|
88
|
+
if conditions.size == 1
|
89
|
+
@options = conditions.first.delete(:options) || {}
|
90
|
+
else
|
91
|
+
index = conditions.find_index {|condition| condition.has_key?(:options) }
|
92
|
+
@options = conditions.delete_at(index)[:options] if index
|
93
|
+
end
|
94
|
+
raise ArgumentError, "Hash expected (options)" unless @options.is_a?(Hash)
|
95
|
+
@conditions = []
|
96
|
+
conditions.each do |condition|
|
97
|
+
@conditions << Hash[condition.map {|k, v| [k, Array(v).map(&:to_s)]}]
|
98
|
+
end
|
99
|
+
raise ArgumentError, "Array of Hash, or Hash expected (conditions)" unless @conditions.all? {|h| h.kind_of?(Hash)}
|
100
|
+
end
|
101
|
+
|
102
|
+
# @yieldparam [Host::Data] data entry
|
103
|
+
def each(&block)
|
104
|
+
@conditions.each do |condition|
|
105
|
+
search(gce_client.instances(condition), condition, &block)
|
106
|
+
end
|
107
|
+
return self
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def search(instances, condition)
|
113
|
+
instances.each do |i|
|
114
|
+
d = GCE::Host::HostData.new(i)
|
115
|
+
next unless d.match?(condition)
|
116
|
+
yield d
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/gce/host/cli.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'gce-host'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
class GCE
|
5
|
+
class Host
|
6
|
+
class CLI
|
7
|
+
attr_reader :options
|
8
|
+
|
9
|
+
def initialize(argv = ARGV)
|
10
|
+
@options = parse_options(argv)
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse_options(argv = ARGV)
|
14
|
+
op = OptionParser.new
|
15
|
+
|
16
|
+
self.class.module_eval do
|
17
|
+
define_method(:usage) do |msg = nil|
|
18
|
+
puts op.to_s
|
19
|
+
puts "error: #{msg}" if msg
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
opts = {
|
25
|
+
state: ["running"]
|
26
|
+
}
|
27
|
+
|
28
|
+
op.on('--hostname one,two,three', Array, "name") {|v|
|
29
|
+
opts[:hostname] = v
|
30
|
+
}
|
31
|
+
op.on('-r', '--role one,two,three', Array, "role") {|v|
|
32
|
+
opts[:role] = v
|
33
|
+
}
|
34
|
+
op.on('--r1', '--role1 one,two,three', Array, "role1, the 1st part of role delimited by #{Config.role_value_delimiter}") {|v|
|
35
|
+
opts[:role1] = v
|
36
|
+
}
|
37
|
+
op.on('--r2', '--role2 one,two,three', Array, "role2, the 2st part of role delimited by #{Config.role_value_delimiter}") {|v|
|
38
|
+
opts[:role2] = v
|
39
|
+
}
|
40
|
+
op.on('--r3', '--role3 one,two,three', Array, "role3, the 3st part of role delimited by #{Config.role_value_delimiter}") {|v|
|
41
|
+
opts[:role3] = v
|
42
|
+
}
|
43
|
+
op.on('--instance-id one,two,three', Array, "instance_id") {|v|
|
44
|
+
opts[:instance_id] = v
|
45
|
+
}
|
46
|
+
op.on("--#{Config.status} one,two,three", Array, "filter with instance #{Config.status} (default: running)") {|v|
|
47
|
+
opts[Config.status.to_sym] = v
|
48
|
+
}
|
49
|
+
Config.optional_options.keys.each do |opt|
|
50
|
+
op.on("--#{opt.to_s.gsub('_', '-')} one,two,three", Array, opt) {|v|
|
51
|
+
opts[opt.to_sym] = v
|
52
|
+
}
|
53
|
+
end
|
54
|
+
op.on('-a', '--all', "list all hosts (remove default filter)") {|v|
|
55
|
+
[:hostname, :role, :role1, :role2, :role3, :instance_id, Config.status.to_sym].each do |key|
|
56
|
+
opts.delete(key)
|
57
|
+
end
|
58
|
+
Config.optional_options.keys.each do |opt|
|
59
|
+
opts.delete(opt.to_sym)
|
60
|
+
end
|
61
|
+
}
|
62
|
+
op.on('--private-ip', '--ip', "show private ip address instead of hostname") {|v|
|
63
|
+
opts[:private_ip] = v
|
64
|
+
}
|
65
|
+
op.on('--public-ip', "show public ip address instead of hostname") {|v|
|
66
|
+
opts[:public_ip] = v
|
67
|
+
}
|
68
|
+
op.on('-i', '--info', "show host info") {|v|
|
69
|
+
opts[:info] = v
|
70
|
+
}
|
71
|
+
op.on('-j', '--jsonl', "show host info in line delimited json") {|v|
|
72
|
+
opts[:jsonl] = v
|
73
|
+
}
|
74
|
+
op.on('--json', "show host info in json") {|v|
|
75
|
+
opts[:json] = v
|
76
|
+
}
|
77
|
+
op.on('--pretty-json', "show host info in pretty json") {|v|
|
78
|
+
opts[:pretty_json] = v
|
79
|
+
}
|
80
|
+
op.on('--debug', "debug mode") {|v|
|
81
|
+
opts[:debug] = v
|
82
|
+
}
|
83
|
+
op.on('-h', '--help', "show help") {|v|
|
84
|
+
opts[:help] = v
|
85
|
+
}
|
86
|
+
|
87
|
+
begin
|
88
|
+
args = op.parse(argv)
|
89
|
+
rescue OptionParser::InvalidOption => e
|
90
|
+
usage e.message
|
91
|
+
end
|
92
|
+
|
93
|
+
if opts[:help]
|
94
|
+
usage
|
95
|
+
end
|
96
|
+
|
97
|
+
opts
|
98
|
+
end
|
99
|
+
|
100
|
+
def run
|
101
|
+
hosts = GCE::Host.new(condition)
|
102
|
+
if options[:info]
|
103
|
+
hosts.each do |host|
|
104
|
+
$stdout.puts host.info
|
105
|
+
end
|
106
|
+
elsif options[:jsonl]
|
107
|
+
hosts.each do |host|
|
108
|
+
$stdout.puts host.to_hash.to_json
|
109
|
+
end
|
110
|
+
elsif options[:json]
|
111
|
+
$stdout.puts hosts.map(&:to_hash).to_json
|
112
|
+
elsif options[:pretty_json]
|
113
|
+
$stdout.puts JSON.pretty_generate(hosts.map(&:to_hash))
|
114
|
+
elsif options[:private_ip]
|
115
|
+
hosts.each do |host|
|
116
|
+
$stdout.puts host.private_ip_address
|
117
|
+
end
|
118
|
+
elsif options[:public_ip]
|
119
|
+
hosts.each do |host|
|
120
|
+
$stdout.puts host.public_ip_address
|
121
|
+
end
|
122
|
+
else
|
123
|
+
hosts.each do |host|
|
124
|
+
$stdout.puts host.hostname
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def condition
|
132
|
+
return @condition if @condition
|
133
|
+
_condition = HashUtil.except(options, :info, :jsonl, :json, :pretty_json, :debug, :private_ip, :public_ip)
|
134
|
+
@condition = {}
|
135
|
+
_condition.each do |key, val|
|
136
|
+
if label = Config.optional_options[key.to_s]
|
137
|
+
field = StringUtil.underscore(label)
|
138
|
+
@condition[field.to_sym] = val
|
139
|
+
else
|
140
|
+
@condition[key.to_sym] = val
|
141
|
+
end
|
142
|
+
end
|
143
|
+
if options[:debug]
|
144
|
+
$stderr.puts(options: options)
|
145
|
+
$stderr.puts(condition: @condition)
|
146
|
+
end
|
147
|
+
@condition
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'dotenv'
|
2
|
+
Dotenv.load
|
3
|
+
|
4
|
+
class GCE
|
5
|
+
class Host
|
6
|
+
class Config
|
7
|
+
def self.configure(params)
|
8
|
+
params.each do |key, val|
|
9
|
+
send("#{key}=", val)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.config_file
|
14
|
+
@config_file ||= ENV.fetch('GCE_HOST_CONFIG_FILE', '/etc/sysconfig/gce-host')
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.auth_method
|
18
|
+
@auth_method ||= ENV['AUTH_METHOD'] || config.fetch('AUTH_METHOD', 'application_default')
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.credential_file
|
22
|
+
@credential_file ||= ENV['GOOGLE_CREDENTIAL_FILE'] || config.fetch('GOOGLE_CREDENTIAL_FILE', nil)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.project
|
26
|
+
return @project if @project
|
27
|
+
# ref. terraform https://www.terraform.io/docs/providers/google/
|
28
|
+
@project ||= ENV['GOOGLE_PROJECT'] || config.fetch('GOOGLE_PROJECT', nil)
|
29
|
+
if @project.nil? and credential_file and File.readable?(credential_file)
|
30
|
+
@project ||= (JSON.parse(File.read(credential_file)) || {})['project_id']
|
31
|
+
end
|
32
|
+
@project
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.log_level
|
36
|
+
@log_level ||= ENV['LOG_LEVEL'] || config.fetch('LOG_LEVEL', 'info')
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.retries
|
40
|
+
@retries ||= ENV['RETRIES'] || config.fetch('RETRIES', 5)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.timeout_sec
|
44
|
+
@timeout_sec ||= ENV['TIMEOUT_SEC'] || config.fetch('TIMEOUT_SEC', 300)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.open_timeout_sec
|
48
|
+
@open_timeout_sec ||= ENV['OPEN_TIMEOUT_SEC'] || config.fetch('OPEN_TIMEOUT_SEC', 300)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.roles_key
|
52
|
+
@roles_key ||= ENV['ROLES_KEY'] || config.fetch('ROLES_KEY', 'roles')
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.optional_array_keys
|
56
|
+
@optional_array_keys ||= (ENV['OPTIONAL_ARRAY_KEYS'] || config.fetch('OPTIONAL_ARRAY_KEYS', '')).split(',')
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.optional_string_keys
|
60
|
+
@optional_string_keys ||= (ENV['OPTIONAL_STRING_KEYS'] || config.fetch('OPTIONAL_STRING_KEYS', '')).split(',')
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.role_value_delimiter
|
64
|
+
@role_value_delimiter ||= ENV['ROLE_VALUE_DELIMITER'] || config.fetch('ROLE_VALUE_DELIMITER', ':')
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.array_value_delimiter
|
68
|
+
@array_value_delimiter ||= ENV['ARRAY_VALUE_DELIMITER'] || config.fetch('ARRAY_VALUE_DELIMITER', ',')
|
69
|
+
end
|
70
|
+
|
71
|
+
# this makes configurable to change status to state to make compatible with AWS
|
72
|
+
# usually, users do not need to care of this
|
73
|
+
def self.status
|
74
|
+
@status ||= ENV['STATUS'] || config.fetch('STATUS', 'status')
|
75
|
+
end
|
76
|
+
|
77
|
+
# private
|
78
|
+
|
79
|
+
def self.optional_array_options
|
80
|
+
@optional_array_options ||= Hash[optional_array_keys.map {|key|
|
81
|
+
[StringUtil.singularize(StringUtil.underscore(key)), key]
|
82
|
+
}]
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.optional_string_options
|
86
|
+
@optional_string_options ||= Hash[optional_string_keys.map {|key|
|
87
|
+
[StringUtil.underscore(key), key]
|
88
|
+
}]
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.optional_options
|
92
|
+
@optional_options ||= optional_array_options.merge(optional_string_options)
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.config
|
96
|
+
return @config if @config
|
97
|
+
@config = {}
|
98
|
+
if File.exist?(config_file)
|
99
|
+
File.readlines(config_file).each do |line|
|
100
|
+
next if line.start_with?('#')
|
101
|
+
key, val = line.chomp.split('=', 2)
|
102
|
+
@config[key] = val
|
103
|
+
end
|
104
|
+
end
|
105
|
+
@config
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|