delano-drydock 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2008 Delano Mandelbaum
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,57 @@
1
+ = Drydock - Easy Command line apps
2
+
3
+ Inspired by "github-gem":http://github.com/defunkt/github-gem
4
+
5
+ Inspired by "bmizerany-frylock":http://github.com/bmizerany/frylock/tree
6
+
7
+ == Overview
8
+
9
+ Drydock is a DSL for command line apps.
10
+
11
+ == Install
12
+
13
+ git clone git://github.com/delano/drydock.git
14
+
15
+
16
+ == Examples
17
+
18
+ See bin/example for more.
19
+
20
+ <pre><code>
21
+ require 'rubygems'
22
+ require 'drydock'
23
+
24
+ default :welcome
25
+
26
+ before do
27
+ # You can execute a block before the requests command is executed. Instance
28
+ # variables defined here will be available to all commands.
29
+ end
30
+
31
+ command :welcome do
32
+ # Example: ruby bin/example
33
+
34
+ puts "Meatwad: Science is a mystery to man, isn't it Frylock?"
35
+ print "Frylock: At least we have some commands: "
36
+
37
+ # The commands method returns a hash of Frylock::Command objects
38
+ puts commands.keys.inject([]) { |list, command| list << command.to_s }.sort.join(', ')
39
+ end
40
+
41
+ option :f, :found, "A boolean value. Did you find the car?"
42
+ command :findcar do |options|
43
+ # +options+ is a hash containing the options defined above
44
+ # Example: ruby bin/example -f findcar
45
+
46
+ puts "Frylock: So, did they ever find your car?"
47
+
48
+ # The keys to the hash are the long string from the option definition.
49
+ # If only the short string is provided, those will be used instead (i.e. :f).
50
+ puts (!options[:found]) ? "Carl: No" :
51
+ "Carl: Oh, they found part of it, hangin' from a trestle near the turnpike."
52
+ end
53
+ </code></pre>
54
+
55
+ == License
56
+
57
+ See LICENSE.txt
data/bin/example ADDED
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ DRYDOCK_HOME = File.expand_path(File.join(File.dirname(__FILE__), '..'))
4
+ $: << File.join(DRYDOCK_HOME, 'lib')
5
+
6
+ require 'rubygems'
7
+ require 'drydock'
8
+
9
+ default :welcome
10
+
11
+
12
+ before do
13
+ # You can execute a block before the requests command is executed. Instance
14
+ # variables defined here will be available to all commands.
15
+ end
16
+
17
+
18
+ command :welcome do
19
+ # Example: ruby bin/example
20
+
21
+ puts "Meatwad: Science is a mystery to man, isn't it Frylock?"
22
+ print "Frylock: At least we have some commands: "
23
+
24
+ # The commands method returns a hash of Drydock::Command objects
25
+ puts commands.keys.inject([]) { |list, command| list << command.to_s }.sort.join(', ')
26
+ end
27
+
28
+
29
+ option :f, :found, "A boolean value. Did you find the car?"
30
+ command :findcar do |options|
31
+ # +options+ is a hash containing the options defined above
32
+ # Example: ruby bin/example -f findcar
33
+
34
+ puts "Frylock: So, did they ever find your car?"
35
+
36
+ # The keys to the hash are the long string from the option definition.
37
+ # If only the short string is provided, those will be used instead (i.e. :f).
38
+ puts (!options[:found]) ? "Carl: No" :
39
+ "Carl: Oh, they found part of it, hangin' from a trestle near the turnpike."
40
+ end
41
+
42
+
43
+
44
+ global_usage "USAGE: #{File.basename($0)} [global options] command [command options]"
45
+ global_option :s, :seconds, "Display values in seconds"
46
+ global_option :v, :verbose, "Verbosity level (i.e. -vvv is greater than -v)" do |v|
47
+ # Use instance variables to maintain values between option blocks.
48
+ # This will increment for every -v found (i.e. -vvv)
49
+ @val ||= 0
50
+ @val += 1
51
+ end
52
+
53
+
54
+ usage "ruby bin/example [--seconds] [-vv] time"
55
+ command :date do |options, argv, global_options|
56
+ # +argv+ contains the unnamed arguments
57
+ # +global_options+ contains hash of the options defined with global_options
58
+
59
+ require 'time'
60
+ now = Time.now
61
+ puts "More verbosely, the date is now: " if (global_options[:verbose] || 0) >= 2
62
+ puts (global_options[:seconds]) ? now.to_i : now.to_s
63
+ end
64
+
65
+
66
+ option :c, :check, "Check response codes for each URI"
67
+ option :d, :delim, String, "Output delimiter"
68
+ option :t, :timeout, Float, "Timeout value for HTTP request" do |v|
69
+ # You can provide an block to process the option value.
70
+ # This block must return the final value.
71
+ v = 10 if (v > 10)
72
+ v
73
+ end
74
+
75
+ usage 'echo "http://github.com/" | ruby bin/example process -c -d " " -t 15 http://solutious.com/'
76
+ command :processuris do |options, argv, global_options, stdin, cmd|
77
+ # +stdin+ is either an IO object or a custom object defined with a stdin block (see below)
78
+ # +cmd+ is the string used to evoke this command. Useful with alias_command (see below).
79
+
80
+ require 'net/http'
81
+ require 'uri'
82
+ require 'timeout'
83
+
84
+ uris = [stdin, argv].flatten # Combine the argv and stdin arrays
85
+ delim = options[:delim] || ','
86
+ timeout = options[:timeout] || 5
87
+ code = :notchecked # The default code when :check is false
88
+
89
+ if uris.empty?
90
+ puts "Frylock: You didn't provide any URIs. "
91
+ puts "Master Shake: Ya, see #{$0} #{cmd} -h"
92
+ exit 0
93
+ end
94
+
95
+ uris.each_with_index do |uri, index|
96
+ code = response_code(uri, timeout) if (options[:check])
97
+ puts [index+1, uri, code].join(delim)
98
+ end
99
+ end
100
+ alias_command :process, :processuris
101
+
102
+
103
+
104
+
105
+ stdin do |stdin, output|
106
+ # Pre-process STDIN for all commands. This example returns an array of lines.
107
+ # The command processuris uses this array.
108
+
109
+ # We only want piped data. If this is not included
110
+ # execution will wait for input from the user.
111
+ unless stdin.tty?
112
+
113
+ while !stdin.eof? do
114
+ line = stdin.readline
115
+ line.chomp!
116
+ (output ||= []) << line
117
+ end
118
+
119
+ end
120
+ output
121
+ end
122
+
123
+
124
+
125
+ # response_code
126
+ #
127
+ # return the HTTP response code for the given URI
128
+ # +uri+ A valid HTTP URI
129
+ # +duration+ The timeout threshold (in seconds) for the request.
130
+ def response_code(uri_str, duration=5)
131
+ response = :unavailable
132
+ begin
133
+ uri = (uri_str.kind_of? URI::HTTP) ? uri_str : URI.parse(uri_str)
134
+ timeout(duration) do
135
+ response = Net::HTTP.get_response(uri).code
136
+ end
137
+ rescue Exception => ex
138
+ end
139
+ response
140
+ end
141
+
142
+
143
+ at_exit do
144
+ # This is an example of how to call Frylock in your script.
145
+ begin
146
+ Drydock.run!(ARGV, STDIN)
147
+
148
+ rescue Drydock::UnknownCommand => ex
149
+ STDERR.puts "Frylock: I don't know what the #{ex.name} command is. #{$/}"
150
+ STDERR.puts "Master Shake: I'll tell you what it is, friends... it's shut up and let me eat it."
151
+
152
+ rescue Drydock::NoCommandsDefined => ex
153
+ STDERR.puts "Frylock: Carl, I don't want it. And I'd appreciate it if you'd define at least one command. #{$/}"
154
+ STDERR.puts "Carl: Fryman, don't be that way! This sorta thing happens every day! People just don't... you know, talk about it this loud."
155
+
156
+ rescue Drydock::InvalidArgument => ex
157
+ STDERR.puts "Frylock: Shake, how many arguments have you not provided a value for this year? #{$/}"
158
+ STDERR.puts "Master Shake: A *lot* more than *you* have! (#{@args.join(', ')})"
159
+
160
+ rescue Drydock::MissingArgument => ex
161
+ STDERR.puts "Frylock: I don't know what #{ex.args.join(', ')} is. #{$/}"
162
+ STDERR.puts "Master Shake: I'll tell you what it is, friends... it's shut up and let me eat it."
163
+
164
+ rescue => ex
165
+ STDERR.puts "Master Shake: Okay, but when we go in, watch your step. "
166
+ STDERR.puts "Frylock: Why?"
167
+ STDERR.puts "Meatwad: [explosion] #{ex.message}"
168
+ STDERR.puts ex.backtrace
169
+ end
170
+ end
@@ -0,0 +1,24 @@
1
+ module Drylock
2
+
3
+ class UnknownCommand < RuntimeError
4
+ attr_reader :name
5
+ def initialize(name)
6
+ @name = name || :unknown
7
+ end
8
+ end
9
+
10
+ class NoCommandsDefined < RuntimeError
11
+ end
12
+
13
+ class InvalidArgument < RuntimeError
14
+ attr_accessor :args
15
+ def initialize(args)
16
+ # We grab just the name of the argument
17
+ @args = args || []
18
+ end
19
+ end
20
+
21
+ class MissingArgument < InvalidArgument
22
+ end
23
+
24
+ end
data/lib/drydock.rb ADDED
@@ -0,0 +1,232 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+ require 'pp'
4
+
5
+ require 'drydock/exceptions'
6
+
7
+ module Drydock
8
+ class Command
9
+ attr_reader :cmd, :index
10
+ def initialize(cmd, index, &b)
11
+ @cmd = (cmd.kind_of?(Symbol)) ? cmd.to_s : cmd
12
+ @index = index
13
+ @b = b
14
+ end
15
+
16
+ def call(cmd_str, argv, stdin, global_options, options)
17
+ block_args = [options, argv, global_options, stdin, cmd_str, self]
18
+ @b.call(*block_args[0..(@b.arity-1)])
19
+ end
20
+ def to_s
21
+ @cmd.to_s
22
+ end
23
+ end
24
+ end
25
+
26
+ module Drydock
27
+ extend self
28
+
29
+ FORWARDED_METHODS = %w(command before alias_command global_option global_usage usage option stdin default commands).freeze
30
+
31
+ def default(cmd)
32
+ @default_command = canonize(cmd)
33
+ end
34
+
35
+ def stdin(&b)
36
+ @stdin_block = b
37
+ end
38
+ def before(&b)
39
+ @before_block = b
40
+ end
41
+
42
+ # global_usage
43
+ # ex: usage "Usage: frylla [global options] command [command options]"
44
+ def global_usage(msg)
45
+ @global_opts_parser ||= OptionParser.new
46
+ @global_options ||= OpenStruct.new
47
+
48
+ @global_opts_parser.banner = msg
49
+ end
50
+
51
+
52
+
53
+ # process_arguments
54
+ #
55
+ # Split the +argv+ array into global args and command args and
56
+ # find the command name.
57
+ # i.e. ./script -H push -f (-H is a global arg, push is the command, -f is a command arg)
58
+ # returns [global_options, cmd, command_options, argv]
59
+ def process_arguments(argv)
60
+ global_options = command_options = {}
61
+ cmd = nil
62
+
63
+ global_parser = @global_opts_parser
64
+
65
+ global_options = global_parser.getopts(argv)
66
+ global_options = global_options.keys.inject({}) do |hash, key|
67
+ hash[key.to_sym] = global_options[key]
68
+ hash
69
+ end
70
+
71
+ cmd_name = (argv.empty?) ? @default_command : argv.shift
72
+ raise UnknownCommand.new(cmd_name) unless command?(cmd_name)
73
+
74
+ cmd = get_command(cmd_name)
75
+ command_parser = @command_opts_parser[cmd.index]
76
+
77
+ command_options = command_parser.getopts(argv) if (!argv.empty? && command_parser)
78
+ command_options = command_options.keys.inject({}) do |hash, key|
79
+ hash[key.to_sym] = command_options[key]
80
+ hash
81
+ end
82
+
83
+ [global_options, cmd_name, command_options, argv]
84
+ end
85
+
86
+
87
+
88
+ def usage(msg)
89
+ get_current_option_parser.banner = msg
90
+ end
91
+
92
+ # get_current_option_parser
93
+ #
94
+ # Grab the options parser for the current command or create it if it doesn't exist.
95
+ def get_current_option_parser
96
+ @command_opts_parser ||= []
97
+ @command_index ||= 0
98
+ (@command_opts_parser[@command_index] ||= OptionParser.new)
99
+ end
100
+
101
+ def global_option(*args, &b)
102
+ @global_opts_parser ||= OptionParser.new
103
+ args.unshift(@global_opts_parser)
104
+ option_parser(args, &b)
105
+ end
106
+
107
+ def option(*args, &b)
108
+ args.unshift(get_current_option_parser)
109
+ option_parser(args, &b)
110
+ end
111
+
112
+ # option_parser
113
+ #
114
+ # Processes calls to option and global_option. Symbols are converted into
115
+ # OptionParser style strings (:h and :help become '-h' and '--help'). If a
116
+ # class is included, it will tell OptionParser to expect a value otherwise
117
+ # it assumes a boolean value.
118
+ #
119
+ # +args+ is passed directly to OptionParser.on so it can contain anything
120
+ # that's valid to that method. Some examples:
121
+ # [:h, :help, "Displays this message"]
122
+ # [:m, :max, Integer, "Maximum threshold"]
123
+ # ['-l x,y,z', '--lang=x,y,z', Array, "Requested languages"]
124
+ def option_parser(args=[], &b)
125
+ return if args.empty?
126
+ opts_parser = args.shift
127
+
128
+ symbol_switches = []
129
+ args.each_with_index do |arg, index|
130
+ if arg.is_a? Symbol
131
+ args[index] = (arg.to_s.length == 1) ? "-#{arg.to_s}" : "--#{arg.to_s}"
132
+ symbol_switches << args[index]
133
+ elsif arg.kind_of?(Class)
134
+ symbol_switches.each do |arg|
135
+ arg << "=S"
136
+ end
137
+ end
138
+ end
139
+
140
+ if args.size == 1
141
+ opts_parser.on(args.shift)
142
+ else
143
+ opts_parser.on(*args) do |v|
144
+ block_args = [v, opts_parser]
145
+ result = (b.nil?) ? v : b.call(*block_args[0..(b.arity-1)])
146
+ end
147
+ end
148
+ end
149
+
150
+ def command(*cmds, &b)
151
+ @command_index ||= 0
152
+ @command_opts_parser ||= []
153
+ cmds.each do |cmd|
154
+ c = Command.new(cmd, @command_index, &b)
155
+ (@commands ||= {})[cmd] = c
156
+ end
157
+
158
+ @command_index += 1
159
+ end
160
+
161
+ def alias_command(aliaz, cmd)
162
+ return unless @commands.has_key? cmd
163
+ @commands[aliaz] = @commands[cmd]
164
+ end
165
+
166
+ def run!(argv, stdin=nil)
167
+ raise NoCommandsDefined.new unless @commands
168
+ @global_options, cmd_name, @command_options, argv = process_arguments(argv)
169
+
170
+ cmd_name ||= @default_command
171
+
172
+ raise UnknownCommand.new(cmd_name) unless command?(cmd_name)
173
+
174
+ stdin = (defined? @stdin_block) ? @stdin_block.call(stdin, []) : stdin
175
+ @before_block.call if defined? @before_block
176
+
177
+
178
+ call_command(cmd_name, argv, stdin)
179
+
180
+
181
+ rescue OptionParser::InvalidOption => ex
182
+ raise Drydock::InvalidArgument.new(ex.args)
183
+ rescue OptionParser::MissingArgument => ex
184
+ raise Drydock::MissingArgument.new(ex.args)
185
+ end
186
+
187
+
188
+ def call_command(cmd_str, argv=[], stdin=nil)
189
+ return unless command?(cmd_str)
190
+ get_command(cmd_str).call(cmd_str, argv, stdin, @global_options, @command_options)
191
+ end
192
+
193
+ def get_command(cmd)
194
+ return unless command?(cmd)
195
+ @commands[canonize(cmd)]
196
+ end
197
+
198
+ def commands
199
+ @commands
200
+ end
201
+
202
+ def run
203
+ @run || true
204
+ end
205
+
206
+ def run=(v)
207
+ @run = v
208
+ end
209
+
210
+ def command?(cmd)
211
+ name = canonize(cmd)
212
+ (@commands || {}).has_key? name
213
+ end
214
+ def canonize(cmd)
215
+ return unless cmd
216
+ return cmd if cmd.kind_of?(Symbol)
217
+ cmd.tr('-', '_').to_sym
218
+ end
219
+
220
+ end
221
+
222
+ Drydock::FORWARDED_METHODS.each do |m|
223
+ eval(<<-end_eval, binding, "(Drydock)", __LINE__)
224
+ def #{m}(*args, &b)
225
+ Drydock.#{m}(*args, &b)
226
+ end
227
+ end_eval
228
+ end
229
+
230
+
231
+
232
+
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: delano-drydock
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Delano Mandelbaum
8
+ - Blake Mizerany
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2008-08-17 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Command line apps made easy
18
+ email: delano@solutious.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README.rdoc
25
+ - LICENSE.txt
26
+ files:
27
+ - LICENSE.txt
28
+ - README.rdoc
29
+ - bin/example
30
+ - lib/drydock/exceptions.rb
31
+ - lib/drydock.rb
32
+ has_rdoc: true
33
+ homepage: http://github.com/delano/drydock
34
+ post_install_message:
35
+ rdoc_options:
36
+ - --line-numbers
37
+ - --inline-source
38
+ - --title
39
+ - "Drydock: Easy command-line apps"
40
+ - --main
41
+ - README.rdoc
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.2.0
60
+ signing_key:
61
+ specification_version: 1
62
+ summary: Command line apps made easy
63
+ test_files: []
64
+