cloulu 0.0.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/bin/{cloulu → cl} +0 -0
  2. data/lib/cc_api_stub/applications.rb +53 -0
  3. data/lib/cc_api_stub/domains.rb +16 -0
  4. data/lib/cc_api_stub/frameworks.rb +22 -0
  5. data/lib/cc_api_stub/helper.rb +131 -0
  6. data/lib/cc_api_stub/login.rb +21 -0
  7. data/lib/cc_api_stub/organization_users.rb +21 -0
  8. data/lib/cc_api_stub/organizations.rb +70 -0
  9. data/lib/cc_api_stub/routes.rb +26 -0
  10. data/lib/cc_api_stub/runtimes.rb +22 -0
  11. data/lib/cc_api_stub/service_bindings.rb +22 -0
  12. data/lib/cc_api_stub/service_instances.rb +22 -0
  13. data/lib/cc_api_stub/services.rb +25 -0
  14. data/lib/cc_api_stub/spaces.rb +49 -0
  15. data/lib/cc_api_stub/users.rb +84 -0
  16. data/lib/cc_api_stub.rb +17 -0
  17. data/lib/cfoundry/auth_token.rb +63 -0
  18. data/lib/cfoundry/baseclient.rb +201 -0
  19. data/lib/cfoundry/chatty_hash.rb +46 -0
  20. data/lib/cfoundry/client.rb +46 -0
  21. data/lib/cfoundry/concerns/login_helpers.rb +13 -0
  22. data/lib/cfoundry/errors.rb +160 -0
  23. data/lib/cfoundry/rest_client.rb +299 -0
  24. data/lib/cfoundry/test_support.rb +3 -0
  25. data/lib/cfoundry/trace_helpers.rb +40 -0
  26. data/lib/cfoundry/uaaclient.rb +112 -0
  27. data/lib/cfoundry/upload_helpers.rb +187 -0
  28. data/lib/cfoundry/v1/app.rb +363 -0
  29. data/lib/cfoundry/v1/base.rb +72 -0
  30. data/lib/cfoundry/v1/client.rb +193 -0
  31. data/lib/cfoundry/v1/framework.rb +21 -0
  32. data/lib/cfoundry/v1/model.rb +178 -0
  33. data/lib/cfoundry/v1/model_magic.rb +129 -0
  34. data/lib/cfoundry/v1/runtime.rb +24 -0
  35. data/lib/cfoundry/v1/service.rb +39 -0
  36. data/lib/cfoundry/v1/service_instance.rb +32 -0
  37. data/lib/cfoundry/v1/service_plan.rb +19 -0
  38. data/lib/cfoundry/v1/user.rb +22 -0
  39. data/lib/cfoundry/v2/app.rb +392 -0
  40. data/lib/cfoundry/v2/base.rb +83 -0
  41. data/lib/cfoundry/v2/client.rb +138 -0
  42. data/lib/cfoundry/v2/domain.rb +11 -0
  43. data/lib/cfoundry/v2/framework.rb +14 -0
  44. data/lib/cfoundry/v2/model.rb +148 -0
  45. data/lib/cfoundry/v2/model_magic.rb +449 -0
  46. data/lib/cfoundry/v2/organization.rb +16 -0
  47. data/lib/cfoundry/v2/route.rb +15 -0
  48. data/lib/cfoundry/v2/runtime.rb +12 -0
  49. data/lib/cfoundry/v2/service.rb +19 -0
  50. data/lib/cfoundry/v2/service_auth_token.rb +9 -0
  51. data/lib/cfoundry/v2/service_binding.rb +10 -0
  52. data/lib/cfoundry/v2/service_instance.rb +14 -0
  53. data/lib/cfoundry/v2/service_plan.rb +12 -0
  54. data/lib/cfoundry/v2/space.rb +18 -0
  55. data/lib/cfoundry/v2/user.rb +64 -0
  56. data/lib/cfoundry/validator.rb +39 -0
  57. data/lib/cfoundry/version.rb +4 -0
  58. data/lib/cfoundry/zip.rb +56 -0
  59. data/lib/cfoundry.rb +2 -0
  60. data/lib/manifests-vmc-plugin/errors.rb +21 -0
  61. data/lib/manifests-vmc-plugin/loader/builder.rb +34 -0
  62. data/lib/manifests-vmc-plugin/loader/normalizer.rb +149 -0
  63. data/lib/manifests-vmc-plugin/loader/resolver.rb +79 -0
  64. data/lib/manifests-vmc-plugin/loader.rb +31 -0
  65. data/lib/manifests-vmc-plugin/plugin.rb +145 -0
  66. data/lib/manifests-vmc-plugin/version.rb +3 -0
  67. data/lib/manifests-vmc-plugin.rb +313 -0
  68. data/lib/mothership/base.rb +99 -0
  69. data/lib/mothership/callbacks.rb +85 -0
  70. data/lib/mothership/command.rb +146 -0
  71. data/lib/mothership/errors.rb +38 -0
  72. data/lib/mothership/help/commands.rb +53 -0
  73. data/lib/mothership/help/printer.rb +170 -0
  74. data/lib/mothership/help.rb +64 -0
  75. data/lib/mothership/inputs.rb +189 -0
  76. data/lib/mothership/parser.rb +182 -0
  77. data/lib/mothership/version.rb +3 -0
  78. data/lib/mothership.rb +64 -0
  79. data/lib/tunnel-vmc-plugin/plugin.rb +178 -0
  80. data/lib/tunnel-vmc-plugin/tunnel.rb +308 -0
  81. data/lib/tunnel-vmc-plugin/version.rb +3 -0
  82. data/lib/uaa/http.rb +168 -0
  83. data/lib/uaa/misc.rb +121 -0
  84. data/lib/uaa/scim.rb +292 -0
  85. data/lib/uaa/token_coder.rb +196 -0
  86. data/lib/uaa/token_issuer.rb +255 -0
  87. data/lib/uaa/util.rb +235 -0
  88. data/lib/uaa/version.rb +19 -0
  89. data/lib/uaa.rb +18 -0
  90. data/lib/vmc/cli/app/app.rb +45 -0
  91. data/lib/vmc/cli/app/apps.rb +99 -0
  92. data/lib/vmc/cli/app/base.rb +90 -0
  93. data/lib/vmc/cli/app/crashes.rb +42 -0
  94. data/lib/vmc/cli/app/delete.rb +95 -0
  95. data/lib/vmc/cli/app/deprecated.rb +11 -0
  96. data/lib/vmc/cli/app/env.rb +78 -0
  97. data/lib/vmc/cli/app/files.rb +137 -0
  98. data/lib/vmc/cli/app/health.rb +26 -0
  99. data/lib/vmc/cli/app/instances.rb +53 -0
  100. data/lib/vmc/cli/app/logs.rb +76 -0
  101. data/lib/vmc/cli/app/push/create.rb +165 -0
  102. data/lib/vmc/cli/app/push/interactions.rb +94 -0
  103. data/lib/vmc/cli/app/push/sync.rb +64 -0
  104. data/lib/vmc/cli/app/push.rb +109 -0
  105. data/lib/vmc/cli/app/rename.rb +35 -0
  106. data/lib/vmc/cli/app/restart.rb +20 -0
  107. data/lib/vmc/cli/app/scale.rb +71 -0
  108. data/lib/vmc/cli/app/start.rb +143 -0
  109. data/lib/vmc/cli/app/stats.rb +67 -0
  110. data/lib/vmc/cli/app/stop.rb +27 -0
  111. data/lib/vmc/cli/help.rb +11 -0
  112. data/lib/vmc/cli/interactive.rb +105 -0
  113. data/lib/vmc/cli/route/base.rb +12 -0
  114. data/lib/vmc/cli/route/map.rb +82 -0
  115. data/lib/vmc/cli/route/routes.rb +25 -0
  116. data/lib/vmc/cli/route/unmap.rb +94 -0
  117. data/lib/vmc/cli/service/base.rb +8 -0
  118. data/lib/vmc/cli/service/bind.rb +44 -0
  119. data/lib/vmc/cli/service/create.rb +126 -0
  120. data/lib/vmc/cli/service/delete.rb +86 -0
  121. data/lib/vmc/cli/service/rename.rb +35 -0
  122. data/lib/vmc/cli/service/service.rb +42 -0
  123. data/lib/vmc/cli/service/services.rb +114 -0
  124. data/lib/vmc/cli/service/unbind.rb +38 -0
  125. data/lib/vmc/cli/start/base.rb +94 -0
  126. data/lib/vmc/cli/start/colors.rb +13 -0
  127. data/lib/vmc/cli/start/info.rb +126 -0
  128. data/lib/vmc/cli/start/login.rb +97 -0
  129. data/lib/vmc/cli/start/logout.rb +17 -0
  130. data/lib/vmc/cli/start/target.rb +60 -0
  131. data/lib/vmc/cli/start/target_interactions.rb +37 -0
  132. data/lib/vmc/cli/start/targets.rb +16 -0
  133. data/lib/vmc/cli/user/base.rb +29 -0
  134. data/lib/vmc/cli/user/create.rb +39 -0
  135. data/lib/vmc/cli/user/delete.rb +27 -0
  136. data/lib/vmc/cli/user/passwd.rb +50 -0
  137. data/lib/vmc/cli/user/register.rb +42 -0
  138. data/lib/vmc/cli/user/users.rb +32 -0
  139. data/lib/vmc/cli/v2_check_cli.rb +16 -0
  140. data/lib/vmc/cli.rb +474 -0
  141. data/lib/vmc/constants.rb +13 -0
  142. data/lib/vmc/detect.rb +129 -0
  143. data/lib/vmc/errors.rb +19 -0
  144. data/lib/vmc/plugin.rb +56 -0
  145. data/lib/vmc/spacing.rb +89 -0
  146. data/lib/vmc/spec_helper.rb +1 -0
  147. data/lib/vmc/test_support.rb +6 -0
  148. data/lib/vmc/version.rb +3 -0
  149. data/lib/vmc.rb +8 -0
  150. data/vendor/errors/v1.yml +189 -0
  151. data/vendor/errors/v2.yml +360 -0
  152. metadata +303 -190
@@ -0,0 +1,313 @@
1
+ require "yaml"
2
+ require "set"
3
+
4
+ require "manifests-vmc-plugin/loader"
5
+
6
+
7
+ module VMCManifests
8
+ MANIFEST_FILE = "manifest.yml"
9
+
10
+ @@showed_manifest_usage = false
11
+
12
+ def manifest
13
+ return @manifest if @manifest
14
+
15
+ if manifest_file && File.exists?(manifest_file)
16
+ @manifest = load_manifest(manifest_file)
17
+ end
18
+ end
19
+
20
+ def save_manifest(save_to = manifest_file)
21
+ fail "No manifest to save!" unless @manifest
22
+
23
+ File.open(save_to, "w") do |io|
24
+ YAML.dump(@manifest, io)
25
+ end
26
+ end
27
+
28
+ # find the manifest file to work with
29
+ def manifest_file
30
+ return @manifest_file if @manifest_file
31
+
32
+ unless path = input[:manifest]
33
+ where = Dir.pwd
34
+ while true
35
+ if File.exists?(File.join(where, MANIFEST_FILE))
36
+ path = File.join(where, MANIFEST_FILE)
37
+ break
38
+ elsif File.basename(where) == "/"
39
+ path = nil
40
+ break
41
+ else
42
+ where = File.expand_path("../", where)
43
+ end
44
+ end
45
+ end
46
+
47
+ return unless path
48
+
49
+ @manifest_file = File.expand_path(path)
50
+ end
51
+
52
+ # load and resolve a given manifest file
53
+ def load_manifest(file)
54
+ Loader.new(file, self).manifest
55
+ end
56
+
57
+ # dynamic symbol resolution
58
+ def resolve_symbol(sym)
59
+ case sym
60
+ when "target-url"
61
+ client_target
62
+
63
+ when "target-base"
64
+ target_base
65
+
66
+ when "random-word"
67
+ sprintf("%04x", rand(0x0100000))
68
+
69
+ when /^ask (.+)/
70
+ ask($1)
71
+ end
72
+ end
73
+
74
+ # find apps by an identifier, which may be either a tag, a name, or a path
75
+ def find_apps(identifier)
76
+ return [] unless manifest
77
+
78
+ apps = apps_by(:name, identifier)
79
+
80
+ if apps.empty?
81
+ apps = apps_by(:path, from_manifest(identifier))
82
+ end
83
+
84
+ apps
85
+ end
86
+
87
+ # return all the apps described by the manifest
88
+ def all_apps
89
+ manifest[:applications]
90
+ end
91
+
92
+ def current_apps
93
+ manifest[:applications].select do |app|
94
+ next unless app[:path]
95
+ from_manifest(app[:path]) == Dir.pwd
96
+ end
97
+ end
98
+
99
+ # splits the user's input, resolving paths with the manifest,
100
+ # into internal/external apps
101
+ #
102
+ # internal apps are returned as their data in the manifest
103
+ #
104
+ # external apps are the strings that the user gave, to be
105
+ # passed along wholesale to the wrapped command
106
+ def apps_in_manifest(input = nil, use_name = true, &blk)
107
+ names_or_paths =
108
+ if input.has?(:apps)
109
+ # names may be given but be [], which will still cause
110
+ # interaction, so use #direct instead of #[] here
111
+ input.direct(:apps)
112
+ elsif input.has?(:app)
113
+ [input.direct(:app)]
114
+ elsif input.has?(:name)
115
+ [input.direct(:name)]
116
+ else
117
+ []
118
+ end
119
+
120
+ internal = []
121
+ external = []
122
+
123
+ names_or_paths.each do |x|
124
+ if x.is_a?(String)
125
+ if x =~ %r([/\\])
126
+ apps = find_apps(File.expand_path(x))
127
+
128
+ if apps.empty?
129
+ fail("Path #{b(x)} is not present in manifest #{b(relative_manifest_file)}.")
130
+ end
131
+ else
132
+ apps = find_apps(x)
133
+ end
134
+
135
+ if !apps.empty?
136
+ internal += apps
137
+ else
138
+ external << x
139
+ end
140
+ else
141
+ external << x
142
+ end
143
+ end
144
+
145
+ [internal, external]
146
+ end
147
+
148
+ def create_manifest_for(app, path)
149
+ meta = {
150
+ "name" => app.name,
151
+ "framework" => app.framework.name,
152
+ "runtime" => app.runtime.name,
153
+ "memory" => human_size(app.memory * 1024 * 1024, 0),
154
+ "instances" => app.total_instances,
155
+ "url" => app.url ? app.url.sub(target_base, '${target-base}') : "none",
156
+ "path" => path
157
+ }
158
+
159
+ services = app.services
160
+
161
+ unless services.empty?
162
+ meta["services"] = {}
163
+
164
+ services.each do |i|
165
+ if v2?
166
+ p = i.service_plan
167
+ s = p.service
168
+
169
+ meta["services"][i.name] = {
170
+ "label" => s.label,
171
+ "provider" => s.provider,
172
+ "version" => s.version,
173
+ "plan" => p.name
174
+ }
175
+ else
176
+ meta["services"][i.name] = {
177
+ "vendor" => i.vendor,
178
+ "version" => i.version,
179
+ "tier" => i.tier
180
+ }
181
+ end
182
+ end
183
+ end
184
+
185
+ if cmd = app.command
186
+ meta["command"] = cmd
187
+ end
188
+
189
+ meta
190
+ end
191
+
192
+ private
193
+
194
+ def relative_manifest_file
195
+ Pathname.new(manifest_file).relative_path_from(Pathname.pwd)
196
+ end
197
+
198
+ def show_manifest_usage
199
+ return if @@showed_manifest_usage
200
+
201
+ path = relative_manifest_file
202
+ line "Using manifest file #{c(path, :name)}"
203
+ line
204
+
205
+ @@showed_manifest_usage = true
206
+ end
207
+
208
+ def no_apps
209
+ fail "No applications or manifest to operate on."
210
+ end
211
+
212
+ def warn_reset_changes
213
+ line c("Not applying manifest changes without --reset", :warning)
214
+ line "See `vmc diff` for more details."
215
+ line
216
+ end
217
+
218
+ def apps_by(attr, val)
219
+ manifest[:applications].select do |info|
220
+ info[attr] == val
221
+ end
222
+ end
223
+
224
+ # expand a path relative to the manifest file's directory
225
+ def from_manifest(path)
226
+ File.expand_path(path, File.dirname(manifest_file))
227
+ end
228
+
229
+
230
+ def ask_to_save(input, app)
231
+ return if manifest_file
232
+ return unless ask("Save configuration?", :default => false)
233
+
234
+ manifest = create_manifest_for(app, input[:path])
235
+
236
+ with_progress("Saving to #{c("manifest.yml", :name)}") do
237
+ File.open("manifest.yml", "w") do |io|
238
+ YAML.dump(
239
+ { "applications" => [manifest] },
240
+ io)
241
+ end
242
+ end
243
+ end
244
+
245
+ def env_hash(val)
246
+ if val.is_a?(Hash)
247
+ val
248
+ else
249
+ hash = {}
250
+
251
+ val.each do |pair|
252
+ name, val = pair.split("=", 2)
253
+ hash[name] = val
254
+ end
255
+
256
+ hash
257
+ end
258
+ end
259
+
260
+ def setup_env(app, info)
261
+ return unless info[:env]
262
+ app.env = env_hash(info[:env])
263
+ end
264
+
265
+ def setup_services(app, info)
266
+ return if !info[:services] || info[:services].empty?
267
+
268
+ offerings = client.services
269
+
270
+ to_bind = []
271
+
272
+ info[:services].each do |name, svc|
273
+ name = name.to_s
274
+
275
+ if instance = client.service_instance_by_name(name)
276
+ to_bind << instance
277
+ else
278
+ offering = offerings.find { |o|
279
+ o.label == (svc[:label] || svc[:type] || svc[:vendor]) &&
280
+ (!svc[:version] || o.version == svc[:version]) &&
281
+ (o.provider == (svc[:provider] || "core"))
282
+ }
283
+
284
+ fail "Unknown service offering: #{svc.inspect}." unless offering
285
+
286
+ if v2?
287
+ plan = offering.service_plans.find { |p|
288
+ p.name == (svc[:plan] || "D100")
289
+ }
290
+
291
+ fail "Unknown service plan: #{svc[:plan]}." unless plan
292
+ end
293
+
294
+ invoke :create_service,
295
+ :name => name,
296
+ :offering => offering,
297
+ :plan => plan,
298
+ :app => app
299
+ end
300
+ end
301
+
302
+ to_bind.each do |s|
303
+ next if app.binds?(s)
304
+
305
+ # TODO: splat
306
+ invoke :bind_service, :app => app, :service => s
307
+ end
308
+ end
309
+
310
+ def target_base
311
+ client_target.sub(/^[^\.]+\./, "")
312
+ end
313
+ end
@@ -0,0 +1,99 @@
1
+ require "mothership/command"
2
+ require "mothership/inputs"
3
+
4
+ class Mothership
5
+ # all commands
6
+ @@commands = {}
7
+
8
+ attr_accessor :input
9
+
10
+ # Initialize with the command being executed.
11
+ def initialize(command = nil, input = nil)
12
+ @command = command
13
+ @input = input
14
+ end
15
+
16
+ class << self
17
+ # all of the defined commands
18
+ def commands
19
+ @@commands
20
+ end
21
+
22
+ # start defining a new command with the given description
23
+ def desc(description)
24
+ @command = Command.new(self, description)
25
+ end
26
+
27
+ # define an input for the current command or the global command
28
+ def input(name, options = {}, &interact)
29
+ raise "no current command" unless @command
30
+
31
+ @command.add_input(name, options, &interact)
32
+ end
33
+
34
+ # specify a module that defines interactions for each input
35
+ def interactions(mod)
36
+ @command.interactions = mod
37
+ end
38
+
39
+ # register a command
40
+ def method_added(name)
41
+ return unless @command
42
+
43
+ @command.name = name
44
+ @@commands[name] = @command
45
+
46
+ @command = nil
47
+ end
48
+
49
+ def alias_command(new, orig = nil)
50
+ @@commands[new] = orig ? @@commands[orig] : @command
51
+ end
52
+ end
53
+
54
+ def execute(cmd, argv, global = {})
55
+ cmd.invoke({}, Parser.new(cmd).parse_argv(argv), global)
56
+ rescue Mothership::Error => e
57
+ $stderr.puts e
58
+ $stderr.puts ""
59
+ Mothership::Help.command_usage(cmd, $stderr)
60
+
61
+ exit_status 1
62
+ end
63
+
64
+ # wrap this with your error handling/etc.
65
+ def run(name)
66
+ send(name)
67
+ end
68
+
69
+ # invoke a command with inputs
70
+ def invoke(
71
+ name, inputs = {}, given = {}, global = @input ? @input.global : {})
72
+ if cmd = @@commands[name]
73
+ cmd.invoke(inputs, given, global)
74
+ else
75
+ unknown_command(name)
76
+ end
77
+ end
78
+
79
+ # explicitly perform the interaction of an input
80
+ #
81
+ # if no input specified, assume current input and reuse the arguments
82
+ #
83
+ # example:
84
+ # input(:foo, :default => proc { |foos|
85
+ # foos.find { |f| f.name == "XYZ" } ||
86
+ # interact
87
+ # }) { |foos|
88
+ # ask("Foo?", :choices => foos, :display => proc(&:name))
89
+ # }
90
+ def interact(input = nil, *args)
91
+ unless input
92
+ input, args = @input.current_input
93
+ end
94
+
95
+ raise "no active input" unless input
96
+
97
+ @input.interact(input, self, *args)
98
+ end
99
+ end
@@ -0,0 +1,85 @@
1
+ class Mothership
2
+ # temporary filters via #with_filters
3
+ #
4
+ # command => { tag => [callbacks] }
5
+ @@filters = Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = [] } }
6
+
7
+ class << self
8
+ # register a callback that's evaluated before a command is run
9
+ def before(name, &callback)
10
+ @@commands[name].before << [callback, self]
11
+ end
12
+
13
+ # register a callback that's evaluated after a command is run
14
+ def after(name, &callback)
15
+ @@commands[name].after << [callback, self]
16
+ end
17
+
18
+ # register a callback that's evaluated around a command, controlling its
19
+ # evaluation (i.e. inputs)
20
+ def around(name, &callback)
21
+ @@commands[name].around << [callback, self]
22
+ end
23
+
24
+ # register a callback that's evaluated when a command uses the given
25
+ # filter
26
+ def filter(name, tag, &callback)
27
+ @@commands[name].filters[tag] << [callback, self]
28
+ end
29
+
30
+ # change an argument's status, i.e. optional, splat, or required
31
+ def change_argument(name, arg, to)
32
+ changed = false
33
+
34
+ @@commands[name].arguments.each do |a|
35
+ if a[:name] == arg
36
+ a[:type] = to
37
+ changed = true
38
+ end
39
+ end
40
+
41
+ changed
42
+ end
43
+
44
+ # add an input/flag to a command
45
+ def add_input(cmd, name, options = {}, &interact)
46
+ @@commands[cmd].add_input(name, options, &interact)
47
+ end
48
+ end
49
+
50
+ # filter a value through any plugins
51
+ def filter(tag, val)
52
+ if @@filters.key?(@command.name) &&
53
+ @@filters[@command.name].key?(tag)
54
+ @@filters[@command.name][tag].each do |f, ctx|
55
+ val = ctx.instance_exec(val, &f)
56
+ end
57
+ end
58
+
59
+ @command.filters[tag].each do |f, c|
60
+ val = c.new.instance_exec(val, &f)
61
+ end
62
+
63
+ val
64
+ end
65
+
66
+ # temporary dynamically-scoped filters
67
+ def with_filters(filters)
68
+ filters.each do |cmd, callbacks|
69
+ callbacks.each do |tag, callback|
70
+ @@filters[cmd][tag] << [callback, self]
71
+ end
72
+ end
73
+
74
+ yield
75
+ ensure
76
+ filters.each do |cmd, callbacks|
77
+ callbacks.each do |tag, callback|
78
+ @@filters[cmd][tag].pop
79
+ @@filters[cmd].delete(tag) if @@filters[cmd][tag].empty?
80
+ end
81
+
82
+ @@filters.delete(cmd) if @@filters[cmd].empty?
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,146 @@
1
+ require "mothership/inputs"
2
+
3
+ class Mothership
4
+ class Command
5
+ attr_accessor :name, :description, :interactions
6
+
7
+ attr_reader :context, :inputs, :arguments, :flags
8
+
9
+ attr_reader :before, :after, :around, :filters
10
+
11
+ def initialize(context, description = nil)
12
+ @context = context
13
+ @description = description
14
+ @aliases = []
15
+
16
+ # inputs accepted by command
17
+ @inputs = {}
18
+
19
+ # inputs that act as arguments
20
+ @arguments = []
21
+
22
+ # flag -> input (e.g. --name -> :name)
23
+ @flags = {}
24
+
25
+ # various callbacks
26
+ @before = []
27
+ @after = []
28
+ @around = []
29
+ @filters = Hash.new { |h, k| h[k] = [] }
30
+ end
31
+
32
+ def inspect
33
+ "\#<Command '#{@name}'>"
34
+ end
35
+
36
+ def display_name
37
+ @name.to_s.gsub("_", "-")
38
+ end
39
+
40
+ def usage
41
+ args = [display_name]
42
+ @arguments.each do |a|
43
+ name = (a[:value] || a[:name]).to_s.upcase
44
+ input = @inputs[a[:name]]
45
+
46
+ if a[:type] == :splat
47
+ args << "#{name}..."
48
+ elsif a[:type] == :optional || input.key?(:default) || \
49
+ input.key?(:interact)
50
+ args << "[#{name}]"
51
+ else
52
+ args << name
53
+ end
54
+ end
55
+
56
+ args.join(" ")
57
+ end
58
+
59
+ def invoke(inputs = {}, given = {}, global = {})
60
+ @before.each { |f, c| c.new.instance_exec(&f) }
61
+
62
+ name = @name
63
+
64
+ ctx = @context.new(self)
65
+ ctx.extend @interactions if @interactions
66
+
67
+ ctx.input = Inputs.new(self, ctx, inputs, given, global)
68
+
69
+
70
+ action = proc do |*given_inputs|
71
+ ctx.input = given_inputs.first || ctx.input
72
+ ctx.run(name)
73
+ end
74
+
75
+ cmd = self
76
+ @around.each do |a, c|
77
+ before = action
78
+
79
+ sub = c.new(cmd, ctx.input)
80
+ action = proc do |*given_inputs|
81
+ ctx.input = given_inputs.first || ctx.input
82
+ sub.instance_exec(before, ctx.input, &a)
83
+ end
84
+ end
85
+
86
+ res = ctx.instance_exec(ctx.input, &action)
87
+
88
+ @after.each { |f, c| c.new.instance_exec(&f) }
89
+
90
+ res
91
+ end
92
+
93
+ def add_input(name, options = {}, &interact)
94
+ options[:interact] = interact if interact
95
+ options[:description] = options.delete(:desc) if options.key?(:desc)
96
+
97
+ options[:type] ||=
98
+ case options[:default]
99
+ when true, false
100
+ :boolean
101
+ when Integer
102
+ :integer
103
+ when Float
104
+ :floating
105
+ end
106
+
107
+ unless options[:hidden]
108
+ @flags["--#{name.to_s.gsub("_", "-")}"] = name
109
+
110
+ if options[:singular]
111
+ @flags["--#{options[:singular]}"] = name
112
+ end
113
+
114
+ if aliases = options[:aliases] || options[:alias]
115
+ Array(aliases).each do |a|
116
+ @flags[a] = name
117
+ end
118
+ end
119
+ end
120
+
121
+ # :argument => true means accept as single argument
122
+ # :argument => :foo is shorthand for :argument => {:type => :foo}
123
+ if opts = options[:argument]
124
+ type =
125
+ case opts
126
+ when true
127
+ :normal
128
+ when Symbol
129
+ opts
130
+ when Hash
131
+ opts[:type]
132
+ end
133
+
134
+ options[:argument] = type
135
+
136
+ @arguments <<
137
+ { :name => name,
138
+ :type => type,
139
+ :value => options[:value]
140
+ }
141
+ end
142
+
143
+ @inputs[name] = options
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,38 @@
1
+ class Mothership
2
+ class Error < RuntimeError
3
+ end
4
+
5
+ class MissingArgument < Error
6
+ def initialize(cmd, arg)
7
+ @command = cmd
8
+ @argument = arg
9
+ end
10
+
11
+ def to_s
12
+ "#{@command.display_name}: missing input '#{@argument}'"
13
+ end
14
+ end
15
+
16
+ class ExtraArguments < Error
17
+ def initialize(cmd, extra)
18
+ @command = cmd
19
+ @extra = extra
20
+ end
21
+
22
+ def to_s
23
+ "#{@command.display_name}: too many arguments; extra: #{@extra.join(" ")}"
24
+ end
25
+ end
26
+
27
+ class TypeMismatch < Error
28
+ def initialize(cmd, input, type)
29
+ @command = cmd
30
+ @input = input
31
+ @type = type
32
+ end
33
+
34
+ def to_s
35
+ "#{@command.display_name}: expected #{@type} value for #{@input}"
36
+ end
37
+ end
38
+ end