jashmenn-git-style-binaries 0.1.3

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.
@@ -0,0 +1,196 @@
1
+ require 'git-style-binary'
2
+
3
+ module GitStyleBinary
4
+ def self.command(&block)
5
+ returning Command.new(:constraints => [block]) do |c|
6
+ c.name ||= (GitStyleBinary.name_of_command_being_loaded || GitStyleBinary.current_command_name)
7
+ GitStyleBinary.known_commands[c.name] = c
8
+
9
+ if !GitStyleBinary.current_command || GitStyleBinary.current_command.is_primary?
10
+ GitStyleBinary.current_command = c
11
+ end
12
+ end
13
+ end
14
+
15
+ def self.primary(&block)
16
+ returning Primary.new(:constraints => [block]) do |c|
17
+ c.name ||= (GitStyleBinary.name_of_command_being_loaded || GitStyleBinary.current_command_name)
18
+ GitStyleBinary.known_commands[c.name] = c
19
+
20
+ GitStyleBinary.primary_command = c unless GitStyleBinary.primary_command
21
+ GitStyleBinary.current_command = c unless GitStyleBinary.current_command
22
+ end
23
+ end
24
+
25
+ class Command
26
+ class << self
27
+ def defaults
28
+ lambda do
29
+ name_desc "#{command.full_name}\#{command.short_desc ? ' - ' + command.short_desc : ''}" # eval jit
30
+ version_string = defined?(VERSION) ? VERSION : "0.0.1"
31
+ version "#{version_string} (c) #{Time.now.year}"
32
+ banner <<-EOS
33
+ #{"SYNOPSIS".colorize(:red)}
34
+ #{command.full_name.colorize(:blue)} #{all_options_string}
35
+
36
+ #{"SUBCOMMANDS".colorize(:red)}
37
+ \#{GitStyleBinary.pretty_known_subcommands.join("\n ")}
38
+
39
+ See '#{command.full_name} help COMMAND' for more information on a specific command.
40
+ EOS
41
+
42
+ opt :verbose, "verbose", :default => false
43
+ end
44
+ end
45
+ end
46
+
47
+ attr_reader :constraints
48
+ attr_reader :opts
49
+ attr_accessor :name
50
+
51
+ def initialize(o={})
52
+ o.each do |k,v|
53
+ eval "@#{k.to_s}= v"
54
+ end
55
+ end
56
+
57
+ def parser
58
+ @parser ||= begin
59
+ p = Parser.new
60
+ p.command = self
61
+ p
62
+ end
63
+ end
64
+
65
+ def constraints
66
+ @constraints ||= []
67
+ end
68
+
69
+ def run
70
+ GitStyleBinary.load_primary unless is_primary?
71
+ GitStyleBinary.load_subcommand if is_primary? && running_subcommand?
72
+ load_all_parser_constraints
73
+ @opts = process_args_with_subcmd
74
+ call_parser_run_block
75
+ self
76
+ end
77
+
78
+ def running_subcommand?
79
+ GitStyleBinary.valid_subcommand?(GitStyleBinary.current_command_name)
80
+ end
81
+
82
+ def load_all_parser_constraints
83
+ @loaded_all_parser_constraints ||= begin
84
+ load_parser_default_constraints
85
+ load_parser_primary_constraints
86
+ load_parser_local_constraints
87
+ true
88
+ end
89
+ end
90
+
91
+ def load_parser_default_constraints
92
+ parser.consume_all([self.class.defaults])
93
+ end
94
+
95
+ def load_parser_primary_constraints
96
+ parser.consume_all(GitStyleBinary.primary_command.constraints)
97
+ end
98
+
99
+ def load_parser_local_constraints
100
+ cur = GitStyleBinary.current_command # see, why isn't 'this' current_command?
101
+
102
+ unless self.is_primary? && cur == self
103
+ # TODO TODO - the key lies in this function. figure out when you hav emore engergy
104
+ # soo UGLY. see #process_parser! unify with that method
105
+ # parser.consume_all(constraints) rescue ArgumentError
106
+ parser.consume_all(cur.constraints)
107
+ end
108
+ end
109
+
110
+ def call_parser_run_block
111
+ runs = GitStyleBinary.current_command.parser.runs
112
+ parser.runs.last.call(self) # ... not too happy with this
113
+ end
114
+
115
+ def process_args_with_subcmd(args = ARGV, *a, &b)
116
+ cmd = GitStyleBinary.current_command_name
117
+ vals = process_args(args, *a, &b)
118
+ parser.leftovers.shift if parser.leftovers[0] == cmd
119
+ vals
120
+ end
121
+
122
+ # TOOooootally ugly! why? bc load_parser_local_constraints doesn't work
123
+ # when loading the indivdual commands because it depends on
124
+ # #current_command. This really sucks and is UGLY.
125
+ # the todo is to put in 'load_all_parser_constraints' and this works
126
+ def process_parser!
127
+ # load_all_parser_constraints
128
+
129
+ load_parser_default_constraints
130
+ load_parser_primary_constraints
131
+ # load_parser_local_constraints
132
+ parser.consume_all(constraints)
133
+
134
+ # hack
135
+ parser.consume {
136
+ opt :version, "Print version and exit" if @version unless @specs[:version] || @long["version"]
137
+ opt :help, "Show this message" unless @specs[:help] || @long["help"]
138
+ resolve_default_short_options
139
+ } # hack
140
+ end
141
+
142
+ def process_args(args = ARGV, *a, &b)
143
+ p = parser
144
+ begin
145
+ vals = p.parse args
146
+ args.clear
147
+ p.leftovers.each { |l| args << l }
148
+ vals # ugly todo
149
+ rescue Trollop::CommandlineError => e
150
+ $stderr.puts "Error: #{e.message}."
151
+ $stderr.puts "Try --help for help."
152
+ exit(-1)
153
+ rescue Trollop::HelpNeeded
154
+ p.educate
155
+ exit
156
+ rescue Trollop::VersionNeeded
157
+ puts p.version
158
+ exit
159
+ end
160
+ end
161
+
162
+ def is_primary?
163
+ false
164
+ end
165
+
166
+ def argv
167
+ parser.leftovers
168
+ end
169
+
170
+ def short_desc
171
+ parser.short_desc
172
+ end
173
+
174
+ def full_name
175
+ # ugly, should be is_primary?
176
+ GitStyleBinary.primary_name == name ? GitStyleBinary.primary_name : GitStyleBinary.primary_name + "-" + name
177
+ end
178
+
179
+ def die arg, msg=nil
180
+ p = parser # create local copy
181
+ Trollop.instance_eval { @p = p }
182
+ Trollop::die(arg, msg)
183
+ end
184
+
185
+ end
186
+
187
+ class Primary < Command
188
+ def is_primary?
189
+ true
190
+ end
191
+ def primary
192
+ self
193
+ end
194
+ end
195
+
196
+ end
@@ -0,0 +1,32 @@
1
+ module GitStyleBinary
2
+ module Commands
3
+ class Help
4
+ # not loving this syntax, but works for now
5
+ GitStyleBinary.command do
6
+ short_desc "get help for a specific command"
7
+ run do |command|
8
+
9
+ # this is slightly ugly b/c it has to muck around in the internals to
10
+ # get information about commands other than itself. This isn't a
11
+ # typical case
12
+ def educate_about_command(name)
13
+ load_all_commands
14
+ if GitStyleBinary.known_commands.has_key?(name)
15
+ cmd = GitStyleBinary.known_commands[name]
16
+ cmd.process_parser!
17
+ cmd.parser.educate
18
+ else
19
+ puts "Unknown command '#{name}'"
20
+ end
21
+ end
22
+
23
+ if command.argv.size > 0
24
+ command.argv.first == "help" ? educate : educate_about_command(command.argv.first)
25
+ else
26
+ educate
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,78 @@
1
+ module GitStyleBinary
2
+ module Helpers
3
+ module NameResolver
4
+
5
+ def basename(filename=zero)
6
+ File.basename(filename).match(/(.*?)(\-|$)/).captures.first
7
+ end
8
+ alias_method :primary_name, :basename
9
+
10
+ # checks the bin directory for all files starting with +basename+ and
11
+ # returns an array of strings specifying the subcommands
12
+ def subcommand_names(filename=zero)
13
+ subfiles = Dir[File.join(binary_directory, basename + "-*")]
14
+ cmds = subfiles.collect{|file| File.basename(file).sub(/^#{basename}-/, '')}.sort
15
+ cmds += built_in_command_names
16
+ cmds.uniq
17
+ end
18
+
19
+ def binary_directory(filename=zero)
20
+ File.dirname(filename)
21
+ end
22
+
23
+ def built_in_commands_directory
24
+ File.dirname(__FILE__) + "/../commands"
25
+ end
26
+
27
+ def built_in_command_names
28
+ Dir[built_in_commands_directory + "/*.rb"].collect{|f| File.basename(f.sub(/\.rb$/,''))}
29
+ end
30
+
31
+ def list_subcommands(filename=zero)
32
+ subcommand_names(filename).join(", ")
33
+ end
34
+
35
+ # load first from users binary directory. then load built-in commands if
36
+ # available
37
+ def binary_filename_for(name)
38
+ user_file = File.join(binary_directory, "#{basename}-#{name}")
39
+ return user_file if File.exists?(user_file)
40
+ built_in = File.join(built_in_commands_directory, "#{name}.rb")
41
+ return built_in if File.exists?(built_in)
42
+ user_file
43
+ end
44
+
45
+ def current_command_name(filename=zero,argv=ARGV)
46
+ current = File.basename(zero)
47
+ first_arg = ARGV[0]
48
+ return first_arg if valid_subcommand?(first_arg)
49
+ return basename if basename == current
50
+ current.sub(/^#{basename}-/, '')
51
+ end
52
+
53
+ # returns the command name with the prefix if needed
54
+ def full_current_command_name(filename=zero,argv=ARGV)
55
+ cur = current_command_name(filename, argv)
56
+ subcmd = cur == basename(filename) ? false : true # is this a subcmd?
57
+ "%s%s%s" % [basename(filename), subcmd ? "-" : "", subcmd ? current_command_name(filename, argv) : ""]
58
+ end
59
+
60
+ def valid_subcommand?(name)
61
+ subcommand_names.include?(name)
62
+ end
63
+
64
+ def zero
65
+ $0
66
+ end
67
+
68
+ def pretty_known_subcommands
69
+ GitStyleBinary.known_commands.collect do |k,cmd|
70
+ next if k == basename
71
+ cmd.process_parser!
72
+ ("%-s%-10s" % [basename, k]).colorize(:blue) + "\n " + ("%s" % [cmd.short_desc]) + "\n"
73
+ end.compact.sort
74
+ end
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,37 @@
1
+ module GitStyleBinary
2
+ module Helpers
3
+ module Pager
4
+
5
+ # by Nathan Weizenbaum - http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby
6
+ def run_pager
7
+ return if PLATFORM =~ /win32/
8
+ return unless STDOUT.tty?
9
+ STDOUT.use_color = true
10
+
11
+ read, write = IO.pipe
12
+
13
+ unless Kernel.fork # Child process
14
+ STDOUT.reopen(write)
15
+ STDERR.reopen(write) if STDERR.tty?
16
+ read.close
17
+ write.close
18
+ return
19
+ end
20
+
21
+ # Parent process, become pager
22
+ STDIN.reopen(read)
23
+ read.close
24
+ write.close
25
+
26
+ ENV['LESS'] = 'FSRX' # Don't page if the input is short enough
27
+
28
+ Kernel.select [STDIN] # Wait until we have input before we start the pager
29
+ pager = ENV['PAGER'] || 'less -erXF'
30
+ exec pager rescue exec "/bin/sh", "-c", pager
31
+ end
32
+
33
+ module_function :run_pager
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,204 @@
1
+ module GitStyleBinary
2
+ class Parser < Trollop::Parser
3
+ attr_reader :runs
4
+ attr_reader :short_desc
5
+ attr_accessor :command
6
+
7
+ def initialize *a, &b
8
+ super
9
+ @runs = []
10
+ end
11
+
12
+ def banner s=nil; @banner = s if s; @banner end
13
+ def short_desc s=nil; @short_desc = s if s; @short_desc end
14
+ def name_desc s=nil; @name_desc = s if s; @name_desc end
15
+
16
+ # Set the theme. Valid values are +:short+ or +:long+. Default +:long+
17
+ attr_writer :theme
18
+
19
+ def theme
20
+ @theme ||= :long
21
+ end
22
+
23
+ ## Adds text to the help display.
24
+ def text s; @order << [:text, s] end
25
+
26
+ def spec_names
27
+ @specs.collect{|name, spec| spec[:long]}
28
+ end
29
+
30
+ # should probably be somewhere else
31
+ def load_all_commands
32
+ GitStyleBinary.subcommand_names.each do |name|
33
+ cmd_file = GitStyleBinary.binary_filename_for(name)
34
+ GitStyleBinary.load_command_file(name, cmd_file)
35
+ end
36
+ end
37
+
38
+ ## Print the help message to 'stream'.
39
+ def educate(stream=$stdout)
40
+ load_all_commands
41
+ width # just calculate it now; otherwise we have to be careful not to
42
+ # call this unless the cursor's at the beginning of a line.
43
+ GitStyleBinary::Helpers::Pager.run_pager
44
+ self.send("educate_#{theme}", stream)
45
+ end
46
+
47
+ def educate_long(stream=$stdout)
48
+ left = {}
49
+
50
+ @specs.each do |name, spec|
51
+ left[name] =
52
+ ((spec[:short] ? "-#{spec[:short]}, " : "") +
53
+ "--#{spec[:long]}" +
54
+ case spec[:type]
55
+ when :flag; ""
56
+ when :int; "=<i>"
57
+ when :ints; "=<i+>"
58
+ when :string; "=<s>"
59
+ when :strings; "=<s+>"
60
+ when :float; "=<f>"
61
+ when :floats; "=<f+>"
62
+ end).colorize(:red)
63
+ end
64
+
65
+ leftcol_width = left.values.map { |s| s.length }.max || 0
66
+ rightcol_start = leftcol_width + 6 # spaces
67
+ leftcol_start = 6
68
+ leftcol_spaces = " " * leftcol_start
69
+
70
+ unless @order.size > 0 && @order.first.first == :text
71
+
72
+ if @name_desc
73
+ stream.puts "NAME".colorize(:red)
74
+ stream.puts "#{leftcol_spaces}"+ colorize_known_words(eval(%Q["#{@name_desc}"])) + "\n"
75
+ stream.puts
76
+ end
77
+
78
+ if @version
79
+ stream.puts "VERSION".colorize(:red)
80
+ stream.puts "#{leftcol_spaces}#@version\n"
81
+ end
82
+
83
+ stream.puts
84
+
85
+ # banner = wrap(colorize_known_words(eval(%Q["#{@banner}"])) + "\n", :prefix => leftcol_start) if @banner # lazy banner
86
+ banner = colorize_known_words_array(wrap(eval(%Q["#{@banner}"]) + "\n", :prefix => leftcol_start)) if @banner # lazy banner
87
+ stream.puts banner
88
+
89
+ stream.puts
90
+ stream.puts "OPTIONS".colorize(:red)
91
+ else
92
+ stream.puts "#@banner\n" if @banner
93
+ end
94
+
95
+ @order.each do |what, opt|
96
+ if what == :text
97
+ stream.puts wrap(opt)
98
+ next
99
+ end
100
+
101
+ spec = @specs[opt]
102
+ stream.printf " %-#{leftcol_width}s\n", left[opt]
103
+ desc = spec[:desc] +
104
+ if spec[:default]
105
+ if spec[:desc] =~ /\.$/
106
+ " (Default: #{spec[:default]})"
107
+ else
108
+ " (default: #{spec[:default]})"
109
+ end
110
+ else
111
+ ""
112
+ end
113
+ stream.puts wrap(" %s" % [desc], :prefix => leftcol_start, :width => width - rightcol_start - 1 )
114
+ stream.puts
115
+ stream.puts
116
+ end
117
+
118
+ end
119
+
120
+ def educate_short(stream=$stdout)
121
+ left = {}
122
+
123
+ @specs.each do |name, spec|
124
+ left[name] = "--#{spec[:long]}" +
125
+ (spec[:short] ? ", -#{spec[:short]}" : "") +
126
+ case spec[:type]
127
+ when :flag; ""
128
+ when :int; " <i>"
129
+ when :ints; " <i+>"
130
+ when :string; " <s>"
131
+ when :strings; " <s+>"
132
+ when :float; " <f>"
133
+ when :floats; " <f+>"
134
+ end
135
+ end
136
+
137
+ leftcol_width = left.values.map { |s| s.length }.max || 0
138
+ rightcol_start = leftcol_width + 6 # spaces
139
+
140
+ unless @order.size > 0 && @order.first.first == :text
141
+ stream.puts "#@version\n" if @version
142
+ stream.puts eval(%Q["#{@banner}"]) + "\n" if @banner # lazy banner
143
+ stream.puts "Options:"
144
+ else
145
+ stream.puts "#@banner\n" if @banner
146
+ end
147
+
148
+ @order.each do |what, opt|
149
+ if what == :text
150
+ stream.puts wrap(opt)
151
+ next
152
+ end
153
+
154
+ spec = @specs[opt]
155
+ stream.printf " %#{leftcol_width}s: ", left[opt]
156
+ desc = spec[:desc] +
157
+ if spec[:default]
158
+ if spec[:desc] =~ /\.$/
159
+ " (Default: #{spec[:default]})"
160
+ else
161
+ " (default: #{spec[:default]})"
162
+ end
163
+ else
164
+ ""
165
+ end
166
+ stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
167
+ end
168
+
169
+ end
170
+
171
+
172
+ def colorize_known_words_array(txts)
173
+ txts.collect{|txt| colorize_known_words(txt)}
174
+ end
175
+
176
+ def colorize_known_words(txt)
177
+ txt = txt.gsub(/^([A-Z]+\s*)$/, '\1'.colorize(:red)) # all caps words on their own line
178
+ txt = txt.gsub(/\b(#{bin_name})\b/, '\1'.colorize(:blue)) # the current command name
179
+ txt = txt.gsub(/\[([^\s]+)\]/, "[".colorize(:magenta) + '\1'.colorize(:green) + "]".colorize(:magenta)) # synopsis options
180
+ end
181
+
182
+ def consume(&block)
183
+ cloaker(&block).bind(self).call
184
+ end
185
+
186
+ def consume_all(blocks)
187
+ blocks.each {|b| consume(&b)}
188
+ end
189
+
190
+ def bin_name
191
+ GitStyleBinary.full_current_command_name
192
+ end
193
+
194
+ def all_options_string
195
+ # '#{spec_names.collect(&:to_s).collect{|name| "[".colorize(:magenta) + "--" + name + "]".colorize(:magenta)}.join(" ")} COMMAND [ARGS]'
196
+ '#{spec_names.collect(&:to_s).collect{|name| "[" + "--" + name + "]"}.join(" ")} COMMAND [ARGS]'
197
+ end
198
+
199
+ def run(&block)
200
+ @runs << block
201
+ end
202
+
203
+ end
204
+ end
@@ -0,0 +1,74 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+ require 'rubygems'
3
+ require 'trollop'
4
+ require 'ext/core'
5
+ require 'ext/colorize'
6
+ require 'git-style-binary/autorunner'
7
+ Dir[File.dirname(__FILE__) + "/git-style-binary/helpers/*.rb"].each {|f| require f}
8
+
9
+ module GitStyleBinary
10
+
11
+ class << self
12
+ include Helpers::NameResolver
13
+ attr_accessor :current_command
14
+ attr_accessor :primary_command
15
+ attr_writer :known_commands
16
+
17
+ # If set to false GitStyleBinary will not automatically run at exit.
18
+ attr_writer :run
19
+
20
+ # Automatically run at exit?
21
+ def run?
22
+ @run ||= false
23
+ end
24
+
25
+ def parser
26
+ @p ||= Parser.new
27
+ end
28
+
29
+ def known_commands
30
+ @known_commands ||= {}
31
+ end
32
+
33
+ def load_primary
34
+ unless @loaded_primary
35
+ @loaded_primary = true
36
+ primary_file = File.join(binary_directory, basename)
37
+ load primary_file
38
+
39
+ if !GitStyleBinary.primary_command # you still dont have a primary load a default
40
+ GitStyleBinary.primary do
41
+ run do |command|
42
+ educate
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def load_subcommand
50
+ unless @loaded_subcommand
51
+ @loaded_subcommand = true
52
+ cmd_file = GitStyleBinary.binary_filename_for(GitStyleBinary.current_command_name)
53
+ load cmd_file
54
+ end
55
+ end
56
+
57
+ def load_command_file(name, file)
58
+ self.name_of_command_being_loaded = name
59
+ load file
60
+ self.name_of_command_being_loaded = nil
61
+ end
62
+
63
+ # UGLY eek
64
+ attr_accessor :name_of_command_being_loaded
65
+
66
+ end
67
+ end
68
+
69
+ at_exit do
70
+ unless $! || GitStyleBinary.run?
71
+ command = GitStyleBinary::AutoRunner.run
72
+ exit 0
73
+ end
74
+ end
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + "/../../lib")
3
+ VERSION="0.0.2" # just to test it
4
+ require 'git-style-binary/command'
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + "/../../lib")
3
+ require 'git-style-binary/command'
4
+
5
+ GitStyleBinary.command do
6
+ short_desc "download a flickr image"
7
+ banner <<-EOS
8
+ SYNOPSIS
9
+ #{command.full_name} #{all_options_string} url
10
+
11
+ Downloads an image from flickr
12
+
13
+ EOS
14
+ run do |command|
15
+ puts "would download: #{command.argv.inspect}"
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + "/../../lib")
3
+
4
+ require 'git-style-binary/command'
5
+ GitStyleBinary.primary do
6
+ version "0.0.1 (c) 2009 Nate Murray - local"
7
+ opt :test_primary, "test an option on the primary", :type => String
8
+
9
+ run do |command|
10
+ puts "Primary Options: #{command.opts.inspect}"
11
+ end
12
+ end
13
+
14
+ # OR
15
+
16
+ # require 'git-style-binary/primary'
17
+ # command = GitStyleBinary::primary("wordpress") do
18
+ # version "#{$0} 0.0.1 (c) 2009 Nate Murray"
19
+ # banner <<-EOS
20
+ # usage: #{$0} #{all_options.collect(:&to_s).join(" ")} COMMAND [ARGS]
21
+ #
22
+ # The wordpress subcommands commands are:
23
+ # {subcommand_names.pretty_print}
24
+ #
25
+ # See 'wordpress help COMMAND' for more information on a specific command.
26
+ # EOS
27
+ # opt :verbose, "verbose", :default => false
28
+ # opt :dry, "dry run", :default => false
29
+ # opt :test_global, "a basic global string option", :type => String
30
+ # end
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + "/../../lib")
3
+ require 'git-style-binary/command'
4
+
5
+ GitStyleBinary.command do
6
+ short_desc "do something with categories"
7
+ banner <<-EOS
8
+ SYNOPSIS
9
+ #{command.full_name} #{all_options_string}
10
+
11
+ Does something with categories
12
+
13
+ EOS
14
+ run do |command|
15
+ puts "does something with categories"
16
+ end
17
+ end