cloulu 0.0.0 → 0.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 (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