delano-drydock 0.3.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.
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
+