naksh 0.2.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/AUTHORS +1 -0
- data/COPYING +674 -0
- data/COPYING-DOCS +15 -0
- data/README.md +45 -0
- data/Rakefile +129 -0
- data/TODO +19 -0
- data/bin/naksh +2 -0
- data/data/default.mo +0 -0
- data/data/default.po +16 -0
- data/data/default2.mo +0 -0
- data/data/default2.po +27 -0
- data/data/starter.pot +29 -0
- data/docs/bash_flow.dia +0 -0
- data/docs/gtk_manual.txt +13 -0
- data/docs/master_plan.dia +0 -0
- data/docs/master_plan.png +0 -0
- data/docs/master_plan.svg +74 -0
- data/docs/master_plan.svgz +0 -0
- data/docs/userguide/en/about.xml +42 -0
- data/docs/userguide/en/gnu-fdl-1.2.xml +543 -0
- data/docs/userguide/en/naksh.xml +881 -0
- data/docs/userguide/en/naksh_start_window.png +0 -0
- data/docs/userguide/en/old_naksh.xml +180 -0
- data/docs/userguide/en/userguide-en.omf +37 -0
- data/lib/naksh.rb +165 -0
- data/lib/naksh/buffer.rb +153 -0
- data/lib/naksh/command_handling.rb +117 -0
- data/lib/naksh/commands/bash/README +2 -0
- data/lib/naksh/commands/bash/echo.nkc.rb +50 -0
- data/lib/naksh/commands/bsd.rb +25 -0
- data/lib/naksh/commands/dos.rb +25 -0
- data/lib/naksh/commands/fuse.rb +25 -0
- data/lib/naksh/commands/gnu.rb +30 -0
- data/lib/naksh/commands/gnu/README +2 -0
- data/lib/naksh/commands/gnu/cat.nkc.rb +50 -0
- data/lib/naksh/commands/gnu/cd.nkc.rb +11 -0
- data/lib/naksh/commands/gnu/echo.documentation.json +57 -0
- data/lib/naksh/commands/gnu/echo.documentation.rb +60 -0
- data/lib/naksh/commands/gnu/echo.nkc.rb +52 -0
- data/lib/naksh/commands/gnu/ls.nkc.rb +11 -0
- data/lib/naksh/commands/gnu/mv.nkc.rb +15 -0
- data/lib/naksh/commands/gnu/pwd.nkc.rb +11 -0
- data/lib/naksh/commands/naksh/addpath.nkc.rb +15 -0
- data/lib/naksh/commands/naksh/broken.nkc.rb +10 -0
- data/lib/naksh/commands/naksh/empty.nkc.rb +4 -0
- data/lib/naksh/commands/naksh/exit.nkc.rb +10 -0
- data/lib/naksh/commands/naksh/help.nkc.rb +21 -0
- data/lib/naksh/commands/naksh/man.nkc.rb +8 -0
- data/lib/naksh/commands/ruby.rb +31 -0
- data/lib/naksh/commands/unix.rb +131 -0
- data/lib/naksh/configuration/gconf.rb +51 -0
- data/lib/naksh/configuration/hash.rb +38 -0
- data/lib/naksh/defaults.rb +40 -0
- data/lib/naksh/history.rb +85 -0
- data/lib/naksh/interfaces.rb +59 -0
- data/lib/naksh/interfaces/fox.rb +49 -0
- data/lib/naksh/interfaces/gtk.rb +103 -0
- data/lib/naksh/interfaces/gtk/abtwin.rb +53 -0
- data/lib/naksh/interfaces/gtk/gnomeapp.rb +31 -0
- data/lib/naksh/interfaces/gtk/helpwin.rb +70 -0
- data/lib/naksh/interfaces/gtk/io.rb +94 -0
- data/lib/naksh/interfaces/gtk/mainwin.rb +56 -0
- data/lib/naksh/interfaces/gtk/mainwin/console.rb +121 -0
- data/lib/naksh/interfaces/gtk/mainwin/main_menu.rb +143 -0
- data/lib/naksh/interfaces/gtk/optwin.rb +78 -0
- data/lib/naksh/interfaces/tab.rb +58 -0
- data/lib/naksh/interfaces/wxw.rb +75 -0
- data/lib/naksh/interfaces/wxw/abtwin.rb +38 -0
- data/lib/naksh/interfaces/wxw/helpwin.rb +40 -0
- data/lib/naksh/interfaces/wxw/io.rb +61 -0
- data/lib/naksh/interfaces/wxw/mainwin.rb +42 -0
- data/lib/naksh/interfaces/wxw/mainwin/main_menu.rb +126 -0
- data/lib/naksh/interfaces/wxw/mainwin/sessions.rb +49 -0
- data/lib/naksh/interfaces/wxw/optwin.rb +37 -0
- data/lib/naksh/path_entry.rb +101 -0
- data/lib/naksh/regexp.rb +31 -0
- data/lib/naksh/session.rb +72 -0
- data/lib/naksh/stdlibext.rb +48 -0
- data/lib/naksh/syntax.rb +57 -0
- data/lib/naksh/syntax/bash.rb +219 -0
- data/lib/naksh/syntax/bash/antlr/bottomup.g +24 -0
- data/lib/naksh/syntax/bash/antlr/topdown.g +52 -0
- data/lib/naksh/syntax/bash/dhaka/evaluator.rb +353 -0
- data/lib/naksh/syntax/bash/dhaka/grammar.rb +71 -0
- data/lib/naksh/syntax/bash/dhaka/lexer.rb +52 -0
- data/lib/naksh/syntax/bash/treetop/bash.treetop +17 -0
- data/lib/old/abbr_call.rb +39 -0
- data/lib/old/autocomplete.rb +29 -0
- data/lib/old/command.rb +106 -0
- data/lib/old/command_holder.rb +75 -0
- data/lib/old/commands.rb +24 -0
- data/lib/old/old_executor.rb +95 -0
- data/lib/old/options.rb +31 -0
- data/lib/old/rush.rb +113 -0
- data/lib/old/sortissimo.rb +205 -0
- data/lib/old/systems.rb +25 -0
- data/lib/old/systems/ruby.rb +26 -0
- data/lib/old/un.rb +240 -0
- data/lib/rust/Rakefile +6 -0
- data/lib/rust/commands/builtins.rb +46 -0
- data/lib/rust/commands/builtins/cd.rb +17 -0
- data/lib/rust/commands/builtins/exit.rb +13 -0
- data/lib/rust/commands/builtins/help.rb +14 -0
- data/lib/rust/commands/builtins/parser.rb +13 -0
- data/lib/rust/commands/builtins/pwd.rb +13 -0
- data/lib/rust/commands/builtins/type.rb +13 -0
- data/lib/rust/commands/commands/ls.rb +13 -0
- data/lib/rust/commands/commands/read.rb +13 -0
- data/lib/rust/commands/commands/rm.rb +14 -0
- data/lib/rust/commands/commands/test.rb +20 -0
- data/lib/rust/helpers/array.rb +10 -0
- data/lib/rust/helpers/command_center.rb +78 -0
- data/lib/rust/helpers/constants.rb +58 -0
- data/lib/rust/helpers/io.rb +132 -0
- data/lib/rust/helpers/parser.rb +45 -0
- data/lib/rust/helpers/rush_control.rb +40 -0
- data/lib/rust/helpers/string.rb +50 -0
- data/lib/rust/helpers/trollop.rb +475 -0
- data/lib/rust/parsers/bash.rb +220 -0
- data/lib/rust/parsers/bash/stdlibext.rb +32 -0
- data/lib/rust/parsers/ren.rb +57 -0
- data/lib/rust/rust.rb +75 -0
- data/lib/rust/syntax/command.rb +23 -0
- data/lib/rust/syntax/paths.rb +31 -0
- data/lib/rust/syntax/pipes.rb +148 -0
- data/naksh.gemspec +55 -0
- data/test/naksh/arg_parser.rspec.rb +27 -0
- data/test/naksh/commands/bash/echo.rspec.rb +32 -0
- data/test/naksh/commands/sortlist +127 -0
- data/test/naksh/external_command.rspec.rb +46 -0
- data/test/naksh/send_error.rspec.rb +60 -0
- data/test/naksh/suggest.rspec.rb +38 -0
- data/test/naksh/syntax/bash.rspec.rb +69 -0
- data/test/naksh/syntax/bash/execute.rspec.rb +51 -0
- data/test/naksh/syntax/rspec.rb +63 -0
- data/test/run_rspecs.rb +20 -0
- metadata +217 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/local/bin/ruby
|
|
2
|
+
#
|
|
3
|
+
# Created by me on 2011-12-23.
|
|
4
|
+
# Copyright (c) 2011. All pwnage reserved.
|
|
5
|
+
|
|
6
|
+
module Parser
|
|
7
|
+
|
|
8
|
+
def run(*arr)
|
|
9
|
+
parts, styles = arr[0..-2], arr.last
|
|
10
|
+
res = []
|
|
11
|
+
parts.each_with_index do |part, i|
|
|
12
|
+
cmd = part[0]
|
|
13
|
+
args = part[1]
|
|
14
|
+
res << [cmd, args.join(' ')].join(' ').strip
|
|
15
|
+
end
|
|
16
|
+
run_standard(res, styles)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def run_standard(parts, styles)
|
|
20
|
+
# Please note that this will
|
|
21
|
+
# work for even single commands!
|
|
22
|
+
# :stding is STDIN and :stdout is STDOUT
|
|
23
|
+
|
|
24
|
+
# this is the actual pipage
|
|
25
|
+
inp = STDIN
|
|
26
|
+
res = []
|
|
27
|
+
output = nil
|
|
28
|
+
parts.each_with_index do |part, i|
|
|
29
|
+
if builtins.include? part.split(' ').shift.to_sym
|
|
30
|
+
::CommandCenter.run_as_internal(part)
|
|
31
|
+
else
|
|
32
|
+
# pipe_to returns the pid, in, out, and err
|
|
33
|
+
pipes = ::Pipes.pipe_to(part, :stdin => inp, :style => styles[i])
|
|
34
|
+
inp = pipes[2]
|
|
35
|
+
res.concat pipes[1..-1]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
output = res[-2].read if res[-2] # only pipes get put here, so need to check for that
|
|
39
|
+
# close them all off
|
|
40
|
+
res.each {|pipe| pipe.close unless pipe.closed? }
|
|
41
|
+
|
|
42
|
+
output.to_s# + "\n"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/local/bin/ruby
|
|
2
|
+
#
|
|
3
|
+
# Created by me on 2011-12-12.
|
|
4
|
+
# Copyright (c) 2011. All pwnage reserved.
|
|
5
|
+
|
|
6
|
+
##################
|
|
7
|
+
# Here are the .rustrc command things
|
|
8
|
+
# its a little bit rusty, I know...
|
|
9
|
+
##################
|
|
10
|
+
module CommandCenter
|
|
11
|
+
class << self
|
|
12
|
+
$keys ||= {}
|
|
13
|
+
|
|
14
|
+
#######
|
|
15
|
+
public
|
|
16
|
+
#######
|
|
17
|
+
|
|
18
|
+
def keys
|
|
19
|
+
$keys
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def capture(keystroke, &b)
|
|
23
|
+
$keys ||= {}
|
|
24
|
+
if keystroke =~ /C-(\w)/
|
|
25
|
+
key_trap = $1
|
|
26
|
+
end
|
|
27
|
+
$keys[key_trap.unpack('c')[0] - 96] = b
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def add_to_buffer(text)
|
|
31
|
+
Rust.io.line += text
|
|
32
|
+
Rust.io.backspace_limit += text.length
|
|
33
|
+
print text
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
$prompt = "[] "
|
|
37
|
+
$path = ['/usr/local/bin', '/bin', '/usr/bin']
|
|
38
|
+
$parser = ::Ren # ::Naksh::Syntax::Bash
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/local/bin/ruby
|
|
2
|
+
#
|
|
3
|
+
# Created by me on 2011-12-07.
|
|
4
|
+
# Copyright (c) 2011. All pwnage reserved.
|
|
5
|
+
|
|
6
|
+
#require File.join(File.dirname(__FILE__), "../syntax/pipes.rb")
|
|
7
|
+
|
|
8
|
+
class String
|
|
9
|
+
|
|
10
|
+
# takes "command1 | command2" and makes it
|
|
11
|
+
# rubyish, like Command.new(command2) | Command.new(command2)
|
|
12
|
+
# OBSOLETE
|
|
13
|
+
def repipe!
|
|
14
|
+
commands = self.split('|')
|
|
15
|
+
commands.map! {|com| "Command.new('#{com.strip.sub(' ', '\', \'')}')"}
|
|
16
|
+
self.replace(commands.reverse.join(' | ').strip)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# OBSOLETE
|
|
20
|
+
def to_commands!
|
|
21
|
+
splits = self.split(' ')
|
|
22
|
+
self.replace "Command.new(#{splits[0]}, #{splits[1..-1]})"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# "ls" | "wc"
|
|
30
|
+
=begin
|
|
31
|
+
Pipes.pipe_to(self.to_s) do |p, i, o, e|
|
|
32
|
+
Pipes.pipe_to(other.to_s, :stdin => o) {|pid, inp, out, err|
|
|
33
|
+
Pipes.pipe_to(third.to_s, :stdin => inp) {|pid2, inp2, out2, err2| puts out2.read }
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def |(other)
|
|
41
|
+
(self =~ /(.+)\s\|\s(.+)/ ? $1 | $2 : self) + ' -> ' + other
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def |(others, stdin=STDIN)
|
|
45
|
+
::Pipes.pipe_to(self.to_s) do |p, i, o, e|
|
|
46
|
+
::Pipes.pipe_to(others.to_s, :stdin => o) {|pid, inp, out, err| puts out.read }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
public :|
|
|
50
|
+
=end
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
## lib/trollop.rb -- trollop command-line processing library
|
|
2
|
+
## Author:: William Morgan (mailto: wmorgan-trollop@masanjin.net)
|
|
3
|
+
## Copyright:: Copyright 2011 William Morgan
|
|
4
|
+
## License:: GNU GPL version 2
|
|
5
|
+
|
|
6
|
+
module Trollop
|
|
7
|
+
|
|
8
|
+
VERSION = "1.7"
|
|
9
|
+
|
|
10
|
+
## Thrown by Parser in the event of a commandline error. Not needed if
|
|
11
|
+
## you're using the Trollop::options entry.
|
|
12
|
+
class CommandlineError < StandardError; end
|
|
13
|
+
|
|
14
|
+
## Thrown by Parser if the user passes in '-h' or '--help'. Handled
|
|
15
|
+
## automatically by Trollop#options.
|
|
16
|
+
class HelpNeeded < StandardError; end
|
|
17
|
+
|
|
18
|
+
## Thrown by Parser if the user passes in '-h' or '--version'. Handled
|
|
19
|
+
## automatically by Trollop#options.
|
|
20
|
+
class VersionNeeded < StandardError; end
|
|
21
|
+
|
|
22
|
+
## Regex for floating point numbers
|
|
23
|
+
FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))$/
|
|
24
|
+
|
|
25
|
+
## Regex for parameters
|
|
26
|
+
PARAM_RE = /^-(-|\.$|[^\d\.])/
|
|
27
|
+
|
|
28
|
+
## The commandline parser. In typical usage, the methods in this class
|
|
29
|
+
## will be handled internally by Trollop#options, in which case only
|
|
30
|
+
## the methods #opt, #banner and #version will be called.
|
|
31
|
+
class Parser
|
|
32
|
+
## The set of values specifiable as the :type parameter to #opt.
|
|
33
|
+
TYPES = [:flag, :boolean, :bool, :int, :integer, :string, :double, :float]
|
|
34
|
+
|
|
35
|
+
## The values from the commandline that were not interpreted by #parse.
|
|
36
|
+
attr_reader :leftovers
|
|
37
|
+
|
|
38
|
+
## The complete configuration hashes for each option. (Mainly useful
|
|
39
|
+
## for testing.)
|
|
40
|
+
attr_reader :specs
|
|
41
|
+
|
|
42
|
+
## Initializes the parser, and instance-evaluates any block given.
|
|
43
|
+
def initialize *a, &b
|
|
44
|
+
@version = nil
|
|
45
|
+
@leftovers = []
|
|
46
|
+
@specs = {}
|
|
47
|
+
@long = {}
|
|
48
|
+
@short = {}
|
|
49
|
+
@order = []
|
|
50
|
+
@constraints = []
|
|
51
|
+
|
|
52
|
+
#instance_eval(&b) if b # can't take arguments
|
|
53
|
+
cloaker(&b).bind(self).call(*a) if b
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
## Add an option. 'name' is the argument name, a unique identifier
|
|
57
|
+
## for the option that you will use internally. 'desc' a string
|
|
58
|
+
## description which will be displayed in help messages. Takes the
|
|
59
|
+
## following optional arguments:
|
|
60
|
+
##
|
|
61
|
+
## * :long: Specify the long form of the argument, i.e. the form
|
|
62
|
+
## with two dashes. If unspecified, will be automatically derived
|
|
63
|
+
## based on the argument name.
|
|
64
|
+
## * :short: Specify the short form of the argument, i.e. the form
|
|
65
|
+
## with one dash. If unspecified, will be automatically derived
|
|
66
|
+
## based on the argument name.
|
|
67
|
+
## * :type: Require that the argument take a parameter of type
|
|
68
|
+
## 'type'. Can by any member of the TYPES constant or a
|
|
69
|
+
## corresponding class (e.g. Integer for :int). If unset, the
|
|
70
|
+
## default argument type is :flag, meaning the argument does not
|
|
71
|
+
## take a parameter. Not necessary if :default: is specified.
|
|
72
|
+
## * :default: Set the default value for an argument. Without a
|
|
73
|
+
## default value, the hash returned by #parse (and thus
|
|
74
|
+
## Trollop#options) will not contain the argument unless it is
|
|
75
|
+
## given on the commandline. The argument type is derived
|
|
76
|
+
## automatically from the class of the default value given, if
|
|
77
|
+
## any. Specifying a :flag argument on the commandline whose
|
|
78
|
+
## default value is true will change its value to false.
|
|
79
|
+
## * :required: if set to true, the argument must be provided on the
|
|
80
|
+
## commandline.
|
|
81
|
+
def opt name, desc="", opts={}
|
|
82
|
+
raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? name
|
|
83
|
+
|
|
84
|
+
## fill in :type
|
|
85
|
+
opts[:type] =
|
|
86
|
+
case opts[:type]
|
|
87
|
+
when :flag, :boolean, :bool: :flag
|
|
88
|
+
when :int, :integer: :int
|
|
89
|
+
when :string: :string
|
|
90
|
+
when :double, :float: :float
|
|
91
|
+
when Class
|
|
92
|
+
case opts[:type].to_s # sigh... there must be a better way to do this
|
|
93
|
+
when 'TrueClass', 'FalseClass': :flag
|
|
94
|
+
when 'String': :string
|
|
95
|
+
when 'Integer': :int
|
|
96
|
+
when 'Float': :float
|
|
97
|
+
else
|
|
98
|
+
raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'"
|
|
99
|
+
end
|
|
100
|
+
when nil: nil
|
|
101
|
+
else
|
|
102
|
+
raise ArgumentError, "unsupported argument type '#{opts[:type]}'" unless TYPES.include?(opts[:type])
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
type_from_default =
|
|
106
|
+
case opts[:default]
|
|
107
|
+
when Integer: :int
|
|
108
|
+
when Numeric: :float
|
|
109
|
+
when TrueClass, FalseClass: :flag
|
|
110
|
+
when String: :string
|
|
111
|
+
when nil: nil
|
|
112
|
+
else
|
|
113
|
+
raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
raise ArgumentError, ":type specification and default type don't match" if opts[:type] && type_from_default && opts[:type] != type_from_default
|
|
117
|
+
|
|
118
|
+
opts[:type] = (opts[:type] || type_from_default || :flag)
|
|
119
|
+
|
|
120
|
+
## fill in :long
|
|
121
|
+
opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.gsub("_", "-")
|
|
122
|
+
opts[:long] =
|
|
123
|
+
case opts[:long]
|
|
124
|
+
when /^--([^-].*)$/
|
|
125
|
+
$1
|
|
126
|
+
when /^[^-]/
|
|
127
|
+
opts[:long]
|
|
128
|
+
else
|
|
129
|
+
raise ArgumentError, "invalid long option name #{opts[:long].inspect}"
|
|
130
|
+
end
|
|
131
|
+
raise ArgumentError, "long option name #{opts[:long].inspect} is already taken; please specify a (different) :long" if @long[opts[:long]]
|
|
132
|
+
|
|
133
|
+
## fill in :short
|
|
134
|
+
opts[:short] = opts[:short].to_s if opts[:short] unless opts[:short] == :none
|
|
135
|
+
opts[:short] =
|
|
136
|
+
case opts[:short]
|
|
137
|
+
when nil
|
|
138
|
+
c = opts[:long].split(//).find { |c| c !~ /[\d]/ && !@short.member?(c) }
|
|
139
|
+
raise ArgumentError, "can't generate a short option name for #{opts[:long].inspect}: out of unique characters" unless c
|
|
140
|
+
c
|
|
141
|
+
when /^-(.)$/
|
|
142
|
+
$1
|
|
143
|
+
when /^.$/
|
|
144
|
+
opts[:short]
|
|
145
|
+
when :none
|
|
146
|
+
nil
|
|
147
|
+
else
|
|
148
|
+
raise ArgumentError, "invalid short option name '#{opts[:short].inspect}'"
|
|
149
|
+
end
|
|
150
|
+
if opts[:short]
|
|
151
|
+
raise ArgumentError, "short option name #{opts[:short].inspect} is already taken; please specify a (different) :short" if @short[opts[:short]]
|
|
152
|
+
raise ArgumentError, "a short option name can't be a number or a dash" if opts[:short] =~ /[\d-]/
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
## fill in :default for flags
|
|
156
|
+
opts[:default] = false if opts[:type] == :flag && opts[:default].nil?
|
|
157
|
+
|
|
158
|
+
opts[:desc] ||= desc
|
|
159
|
+
@long[opts[:long]] = name
|
|
160
|
+
@short[opts[:short]] = name if opts[:short]
|
|
161
|
+
@specs[name] = opts
|
|
162
|
+
@order << [:opt, name]
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
## Sets the version string. If set, the user can request the version
|
|
166
|
+
## on the commandline. Should be of the form "<program name>
|
|
167
|
+
## <version number>".
|
|
168
|
+
def version s=nil; @version = s if s; @version end
|
|
169
|
+
|
|
170
|
+
## Adds text to the help display.
|
|
171
|
+
def banner s; @order << [:text, s] end
|
|
172
|
+
alias :text :banner
|
|
173
|
+
|
|
174
|
+
## Marks two (or more!) options as requiring each other. Only
|
|
175
|
+
## handles undirected dependcies. Directed dependencies are better
|
|
176
|
+
## modeled with #die.
|
|
177
|
+
def depends *syms
|
|
178
|
+
syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
|
|
179
|
+
@constraints << [:depends, syms]
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
## Marks two (or more!) options as conflicting.
|
|
183
|
+
def conflicts *syms
|
|
184
|
+
syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
|
|
185
|
+
@constraints << [:conflicts, syms]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
## yield successive arg, parameter pairs
|
|
189
|
+
def each_arg args # :nodoc:
|
|
190
|
+
remains = []
|
|
191
|
+
i = 0
|
|
192
|
+
|
|
193
|
+
until i >= args.length
|
|
194
|
+
case args[i]
|
|
195
|
+
when /^--$/ # arg terminator
|
|
196
|
+
remains += args[(i + 1) .. -1]
|
|
197
|
+
break
|
|
198
|
+
when /^--(\S+?)=(\S+)$/ # long argument with equals
|
|
199
|
+
yield "--#{$1}", $2
|
|
200
|
+
i += 1
|
|
201
|
+
when /^--(\S+)$/ # long argument
|
|
202
|
+
if args[i + 1] && args[i + 1] !~ PARAM_RE
|
|
203
|
+
remains << args[i + 1] unless yield args[i], args[i + 1]
|
|
204
|
+
i += 2
|
|
205
|
+
else # long argument no parameter
|
|
206
|
+
yield args[i], nil
|
|
207
|
+
i += 1
|
|
208
|
+
end
|
|
209
|
+
when /^-(\S+)$/ # one or more short arguments
|
|
210
|
+
shortargs = $1.split(//)
|
|
211
|
+
shortargs.each_with_index do |a, j|
|
|
212
|
+
if j == (shortargs.length - 1) && args[i + 1] && args[i + 1] !~ PARAM_RE
|
|
213
|
+
remains << args[i + 1] unless yield "-#{a}", args[i + 1]
|
|
214
|
+
i += 1 # once more below
|
|
215
|
+
else
|
|
216
|
+
yield "-#{a}", nil
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
i += 1
|
|
220
|
+
else
|
|
221
|
+
remains << args[i]
|
|
222
|
+
i += 1
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
remains
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def parse cmdline #:nodoc:
|
|
229
|
+
vals = {}
|
|
230
|
+
required = {}
|
|
231
|
+
found = {}
|
|
232
|
+
|
|
233
|
+
opt :version, "Print version and exit" if @version unless @specs[:version] || @long["version"]
|
|
234
|
+
opt :help, "Show this message" unless @specs[:help] || @long["help"]
|
|
235
|
+
|
|
236
|
+
@specs.each do |sym, opts|
|
|
237
|
+
required[sym] = true if opts[:required]
|
|
238
|
+
vals[sym] = opts[:default]
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
## resolve symbols
|
|
242
|
+
args = []
|
|
243
|
+
@leftovers = each_arg cmdline do |arg, param|
|
|
244
|
+
sym =
|
|
245
|
+
case arg
|
|
246
|
+
when /^-([^-])$/
|
|
247
|
+
@short[$1]
|
|
248
|
+
when /^--([^-]\S*)$/
|
|
249
|
+
@long[$1]
|
|
250
|
+
else
|
|
251
|
+
raise CommandlineError, "invalid argument syntax: '#{arg}'"
|
|
252
|
+
end
|
|
253
|
+
raise CommandlineError, "unknown argument '#{arg}'" unless sym
|
|
254
|
+
raise CommandlineError, "option '#{arg}' specified multiple times" if found[sym]
|
|
255
|
+
args << [sym, arg, param]
|
|
256
|
+
found[sym] = true
|
|
257
|
+
|
|
258
|
+
@specs[sym][:type] != :flag # take params on all except flags
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
## check for version and help args
|
|
262
|
+
raise VersionNeeded if args.any? { |sym, *a| sym == :version }
|
|
263
|
+
raise HelpNeeded if args.any? { |sym, *a| sym == :help }
|
|
264
|
+
|
|
265
|
+
## check constraint satisfaction
|
|
266
|
+
@constraints.each do |type, syms|
|
|
267
|
+
constraint_sym = syms.find { |sym| found[sym] }
|
|
268
|
+
next unless constraint_sym
|
|
269
|
+
|
|
270
|
+
case type
|
|
271
|
+
when :depends
|
|
272
|
+
syms.each { |sym| raise CommandlineError, "--#{@long[constraint_sym]} requires --#{@long[sym]}" unless found[sym] }
|
|
273
|
+
when :conflicts
|
|
274
|
+
syms.each { |sym| raise CommandlineError, "--#{@long[constraint_sym]} conflicts with --#{@long[sym]}" if found[sym] && sym != constraint_sym }
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
required.each do |sym, val|
|
|
279
|
+
raise CommandlineError, "option '#{sym}' must be specified" unless found[sym]
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
## parse parameters
|
|
283
|
+
args.each do |sym, arg, param|
|
|
284
|
+
opts = @specs[sym]
|
|
285
|
+
|
|
286
|
+
raise CommandlineError, "option '#{arg}' needs a parameter" unless param || opts[:type] == :flag
|
|
287
|
+
|
|
288
|
+
case opts[:type]
|
|
289
|
+
when :flag
|
|
290
|
+
vals[sym] = !opts[:default]
|
|
291
|
+
when :int
|
|
292
|
+
raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^\d+$/
|
|
293
|
+
vals[sym] = param.to_i
|
|
294
|
+
when :float
|
|
295
|
+
raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE
|
|
296
|
+
vals[sym] = param.to_f
|
|
297
|
+
when :string
|
|
298
|
+
vals[sym] = param.to_s
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
vals
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def width #:nodoc:
|
|
306
|
+
@width ||=
|
|
307
|
+
if $stdout.tty?
|
|
308
|
+
begin
|
|
309
|
+
require 'curses'
|
|
310
|
+
Curses::init_screen
|
|
311
|
+
x = Curses::cols
|
|
312
|
+
Curses::close_screen
|
|
313
|
+
x
|
|
314
|
+
rescue Exception
|
|
315
|
+
80
|
|
316
|
+
end
|
|
317
|
+
else
|
|
318
|
+
80
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
## Print the help message to 'stream'.
|
|
323
|
+
def educate stream=$stdout
|
|
324
|
+
width # just calculate it now; otherwise we have to be careful not to
|
|
325
|
+
# call this unless the cursor's at the beginning of a line.
|
|
326
|
+
|
|
327
|
+
left = {}
|
|
328
|
+
@specs.each do |name, spec|
|
|
329
|
+
left[name] = "--#{spec[:long]}" +
|
|
330
|
+
(spec[:short] ? ", -#{spec[:short]}" : "") +
|
|
331
|
+
case spec[:type]
|
|
332
|
+
when :flag
|
|
333
|
+
""
|
|
334
|
+
when :int
|
|
335
|
+
" <i>"
|
|
336
|
+
when :string
|
|
337
|
+
" <s>"
|
|
338
|
+
when :float
|
|
339
|
+
" <f>"
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
leftcol_width = left.values.map { |s| s.length }.max || 0
|
|
344
|
+
rightcol_start = leftcol_width + 6 # spaces
|
|
345
|
+
|
|
346
|
+
unless @order.size > 0 && @order.first.first == :text
|
|
347
|
+
stream.puts "#@version\n" if @version
|
|
348
|
+
stream.puts "Options:"
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
@order.each do |what, opt|
|
|
352
|
+
if what == :text
|
|
353
|
+
stream.puts wrap(opt)
|
|
354
|
+
next
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
spec = @specs[opt]
|
|
358
|
+
stream.printf " %#{leftcol_width}s: ", left[opt]
|
|
359
|
+
desc = spec[:desc] +
|
|
360
|
+
if spec[:default]
|
|
361
|
+
if spec[:desc] =~ /\.$/
|
|
362
|
+
" (Default: #{spec[:default]})"
|
|
363
|
+
else
|
|
364
|
+
" (default: #{spec[:default]})"
|
|
365
|
+
end
|
|
366
|
+
else
|
|
367
|
+
""
|
|
368
|
+
end
|
|
369
|
+
stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def wrap_line str, opts={} # :nodoc:
|
|
374
|
+
prefix = opts[:prefix] || 0
|
|
375
|
+
width = opts[:width] || (self.width - 1)
|
|
376
|
+
start = 0
|
|
377
|
+
ret = []
|
|
378
|
+
until start > str.length
|
|
379
|
+
nextt =
|
|
380
|
+
if start + width >= str.length
|
|
381
|
+
str.length
|
|
382
|
+
else
|
|
383
|
+
x = str.rindex(/\s/, start + width)
|
|
384
|
+
x = str.index(/\s/, start) if x && x < start
|
|
385
|
+
x || str.length
|
|
386
|
+
end
|
|
387
|
+
ret << (ret.empty? ? "" : " " * prefix) + str[start ... nextt]
|
|
388
|
+
start = nextt + 1
|
|
389
|
+
end
|
|
390
|
+
ret
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def wrap str, opts={} # :nodoc:
|
|
394
|
+
if str == ""
|
|
395
|
+
[""]
|
|
396
|
+
else
|
|
397
|
+
str.split("\n").map { |s| wrap_line s, opts }.flatten
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
## instance_eval but with ability to handle block arguments
|
|
402
|
+
## thanks to why: http://redhanded.hobix.com/inspect/aBlockCostume.html
|
|
403
|
+
def cloaker &b #:nodoc:
|
|
404
|
+
(class << self; self; end).class_eval do
|
|
405
|
+
define_method :cloaker_, &b
|
|
406
|
+
meth = instance_method :cloaker_
|
|
407
|
+
remove_method :cloaker_
|
|
408
|
+
meth
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
## The top-level entry method into Trollop. Creates a Parser object,
|
|
414
|
+
## passes the block to it, then parses ARGV with it, handling any
|
|
415
|
+
## errors or requests for help or version information appropriately
|
|
416
|
+
## (and then exiting). Modifies ARGV in place. Returns a hash of
|
|
417
|
+
## option values.
|
|
418
|
+
##
|
|
419
|
+
## The block passed in should contain one or more calls to #opt
|
|
420
|
+
## (Parser#opt), one or more calls to text (Parser#text), and
|
|
421
|
+
## probably a call to version (Parser#version).
|
|
422
|
+
##
|
|
423
|
+
## See the synopsis in README.txt for examples.
|
|
424
|
+
def options *a, &b
|
|
425
|
+
arr = a.shift
|
|
426
|
+
@p = Parser.new(*a, &b)
|
|
427
|
+
begin
|
|
428
|
+
vals = @p.parse arr
|
|
429
|
+
arr.clear
|
|
430
|
+
@p.leftovers.each { |l| arr << l }
|
|
431
|
+
vals
|
|
432
|
+
rescue CommandlineError => e
|
|
433
|
+
$stderr.puts "Error: #{e.message}."
|
|
434
|
+
$stderr.puts "Try --help for help."
|
|
435
|
+
exit(-1)
|
|
436
|
+
rescue HelpNeeded
|
|
437
|
+
@p.educate
|
|
438
|
+
exit
|
|
439
|
+
rescue VersionNeeded
|
|
440
|
+
puts @p.version
|
|
441
|
+
exit
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
## Informs the user that their usage of 'arg' was wrong, as detailed by
|
|
446
|
+
## 'msg', and dies. Example:
|
|
447
|
+
##
|
|
448
|
+
## options do
|
|
449
|
+
## opt :volume, :default => 0.0
|
|
450
|
+
## end
|
|
451
|
+
##
|
|
452
|
+
## die :volume, "too loud" if opts[:volume] > 10.0
|
|
453
|
+
## die :volume, "too soft" if opts[:volume] < 0.1
|
|
454
|
+
##
|
|
455
|
+
## In the one-argument case, simply print that message, a notice
|
|
456
|
+
## about -h, and die. Example:
|
|
457
|
+
##
|
|
458
|
+
## options do
|
|
459
|
+
## opt :whatever # ...
|
|
460
|
+
## end
|
|
461
|
+
##
|
|
462
|
+
## Trollop::die "need at least one filename" if ARGV.empty?
|
|
463
|
+
def die arg, msg=nil
|
|
464
|
+
if msg
|
|
465
|
+
$stderr.puts "Error: argument --#{@p.specs[arg][:long]} #{msg}."
|
|
466
|
+
else
|
|
467
|
+
$stderr.puts "Error: #{arg}."
|
|
468
|
+
end
|
|
469
|
+
$stderr.puts "Try --help for help."
|
|
470
|
+
exit(-1)
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
module_function :options, :die
|
|
474
|
+
|
|
475
|
+
end # module
|