butler 1.8.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/CHANGELOG +4 -0
- data/GPL.txt +340 -0
- data/LICENSE.txt +52 -0
- data/README +37 -0
- data/Rakefile +334 -0
- data/bin/botcontrol +230 -0
- data/data/butler/config_template.yaml +4 -0
- data/data/butler/dialogs/backup.rb +19 -0
- data/data/butler/dialogs/botcontrol.rb +4 -0
- data/data/butler/dialogs/config.rb +1 -0
- data/data/butler/dialogs/create.rb +53 -0
- data/data/butler/dialogs/delete.rb +3 -0
- data/data/butler/dialogs/en/backup.yaml +6 -0
- data/data/butler/dialogs/en/botcontrol.yaml +5 -0
- data/data/butler/dialogs/en/create.yaml +11 -0
- data/data/butler/dialogs/en/delete.yaml +2 -0
- data/data/butler/dialogs/en/help.yaml +17 -0
- data/data/butler/dialogs/en/info.yaml +13 -0
- data/data/butler/dialogs/en/list.yaml +4 -0
- data/data/butler/dialogs/en/notyetimplemented.yaml +2 -0
- data/data/butler/dialogs/en/rename.yaml +3 -0
- data/data/butler/dialogs/en/start.yaml +3 -0
- data/data/butler/dialogs/en/sync_plugins.yaml +3 -0
- data/data/butler/dialogs/en/uninstall.yaml +5 -0
- data/data/butler/dialogs/en/unknown_command.yaml +2 -0
- data/data/butler/dialogs/help.rb +11 -0
- data/data/butler/dialogs/info.rb +27 -0
- data/data/butler/dialogs/interactive.rb +1 -0
- data/data/butler/dialogs/list.rb +10 -0
- data/data/butler/dialogs/notyetimplemented.rb +1 -0
- data/data/butler/dialogs/rename.rb +4 -0
- data/data/butler/dialogs/selectbot.rb +2 -0
- data/data/butler/dialogs/start.rb +5 -0
- data/data/butler/dialogs/sync_plugins.rb +30 -0
- data/data/butler/dialogs/uninstall.rb +17 -0
- data/data/butler/dialogs/unknown_command.rb +1 -0
- data/data/butler/plugins/core/logout.rb +41 -0
- data/data/butler/plugins/core/plugins.rb +134 -0
- data/data/butler/plugins/core/privilege.rb +103 -0
- data/data/butler/plugins/core/user.rb +166 -0
- data/data/butler/plugins/dev/eval.rb +64 -0
- data/data/butler/plugins/dev/nometa.rb +14 -0
- data/data/butler/plugins/dev/onhandlers.rb +93 -0
- data/data/butler/plugins/dev/raw.rb +36 -0
- data/data/butler/plugins/dev/rawlog.rb +77 -0
- data/data/butler/plugins/games/eightball.rb +54 -0
- data/data/butler/plugins/games/mastermind.rb +174 -0
- data/data/butler/plugins/irc/action.rb +36 -0
- data/data/butler/plugins/irc/join.rb +38 -0
- data/data/butler/plugins/irc/notice.rb +36 -0
- data/data/butler/plugins/irc/part.rb +38 -0
- data/data/butler/plugins/irc/privmsg.rb +36 -0
- data/data/butler/plugins/irc/quit.rb +36 -0
- data/data/butler/plugins/operator/deop.rb +41 -0
- data/data/butler/plugins/operator/devoice.rb +41 -0
- data/data/butler/plugins/operator/limit.rb +47 -0
- data/data/butler/plugins/operator/op.rb +41 -0
- data/data/butler/plugins/operator/voice.rb +41 -0
- data/data/butler/plugins/public/help.rb +69 -0
- data/data/butler/plugins/public/login.rb +72 -0
- data/data/butler/plugins/public/usage.rb +49 -0
- data/data/butler/plugins/service/clones.rb +56 -0
- data/data/butler/plugins/service/define.rb +47 -0
- data/data/butler/plugins/service/log.rb +183 -0
- data/data/butler/plugins/service/svn.rb +91 -0
- data/data/butler/plugins/util/cycle.rb +98 -0
- data/data/butler/plugins/util/load.rb +41 -0
- data/data/butler/plugins/util/pong.rb +29 -0
- data/data/butler/strings/random/acknowledge.en.yaml +5 -0
- data/data/butler/strings/random/gratitude.en.yaml +3 -0
- data/data/butler/strings/random/hello.en.yaml +4 -0
- data/data/butler/strings/random/ignorance.en.yaml +7 -0
- data/data/butler/strings/random/ignorance_about.en.yaml +3 -0
- data/data/butler/strings/random/insult.en.yaml +3 -0
- data/data/butler/strings/random/rejection.en.yaml +12 -0
- data/data/man/botcontrol.1 +17 -0
- data/lib/access.rb +187 -0
- data/lib/access/admin.rb +16 -0
- data/lib/access/privilege.rb +122 -0
- data/lib/access/role.rb +102 -0
- data/lib/access/savable.rb +18 -0
- data/lib/access/user.rb +180 -0
- data/lib/access/yamlbase.rb +126 -0
- data/lib/butler.rb +188 -0
- data/lib/butler/bot.rb +247 -0
- data/lib/butler/control.rb +93 -0
- data/lib/butler/dialog.rb +64 -0
- data/lib/butler/initialvalues.rb +40 -0
- data/lib/butler/irc/channel.rb +135 -0
- data/lib/butler/irc/channels.rb +96 -0
- data/lib/butler/irc/client.rb +351 -0
- data/lib/butler/irc/hostmask.rb +53 -0
- data/lib/butler/irc/message.rb +184 -0
- data/lib/butler/irc/parser.rb +125 -0
- data/lib/butler/irc/parser/commands.rb +83 -0
- data/lib/butler/irc/parser/generic.rb +343 -0
- data/lib/butler/irc/socket.rb +378 -0
- data/lib/butler/irc/string.rb +186 -0
- data/lib/butler/irc/topic.rb +15 -0
- data/lib/butler/irc/user.rb +265 -0
- data/lib/butler/irc/users.rb +112 -0
- data/lib/butler/plugin.rb +249 -0
- data/lib/butler/plugin/configproxy.rb +35 -0
- data/lib/butler/plugin/mapper.rb +85 -0
- data/lib/butler/plugin/matcher.rb +55 -0
- data/lib/butler/plugin/onhandlers.rb +70 -0
- data/lib/butler/plugin/trigger.rb +58 -0
- data/lib/butler/plugins.rb +147 -0
- data/lib/butler/version.rb +17 -0
- data/lib/cloptions.rb +217 -0
- data/lib/cloptions/adapters.rb +24 -0
- data/lib/cloptions/switch.rb +132 -0
- data/lib/configuration.rb +223 -0
- data/lib/dialogline.rb +296 -0
- data/lib/dialogline/localizations.rb +24 -0
- data/lib/durations.rb +57 -0
- data/lib/event.rb +295 -0
- data/lib/event/at.rb +64 -0
- data/lib/event/every.rb +56 -0
- data/lib/event/timed.rb +112 -0
- data/lib/installer.rb +75 -0
- data/lib/iterator.rb +34 -0
- data/lib/log.rb +68 -0
- data/lib/log/comfort.rb +85 -0
- data/lib/log/converter.rb +23 -0
- data/lib/log/entry.rb +152 -0
- data/lib/log/fakeio.rb +55 -0
- data/lib/log/file.rb +54 -0
- data/lib/log/filereader.rb +81 -0
- data/lib/log/forward.rb +49 -0
- data/lib/log/methods.rb +39 -0
- data/lib/log/nolog.rb +18 -0
- data/lib/log/splitter.rb +26 -0
- data/lib/ostructfixed.rb +26 -0
- data/lib/ruby/array/columnize.rb +38 -0
- data/lib/ruby/dir/mktree.rb +28 -0
- data/lib/ruby/enumerable/join.rb +13 -0
- data/lib/ruby/exception/detailed.rb +24 -0
- data/lib/ruby/file/append.rb +11 -0
- data/lib/ruby/file/write.rb +11 -0
- data/lib/ruby/hash/zip.rb +15 -0
- data/lib/ruby/kernel/bench.rb +15 -0
- data/lib/ruby/kernel/daemonize.rb +42 -0
- data/lib/ruby/kernel/non_verbose.rb +17 -0
- data/lib/ruby/kernel/safe_fork.rb +18 -0
- data/lib/ruby/range/stepped.rb +11 -0
- data/lib/ruby/string/arguments.rb +72 -0
- data/lib/ruby/string/chunks.rb +15 -0
- data/lib/ruby/string/post_arguments.rb +44 -0
- data/lib/ruby/string/unescaped.rb +17 -0
- data/lib/scheduler.rb +164 -0
- data/lib/scriptfile.rb +101 -0
- data/lib/templater.rb +86 -0
- data/test/cloptions.rb +134 -0
- data/test/cv.rb +28 -0
- data/test/irc/client.rb +85 -0
- data/test/irc/client_login.txt +53 -0
- data/test/irc/client_subscribe.txt +8 -0
- data/test/irc/message.rb +30 -0
- data/test/irc/messages.txt +64 -0
- data/test/irc/parser.rb +13 -0
- data/test/irc/profile_parser.rb +12 -0
- data/test/irc/users.rb +28 -0
- metadata +256 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class String
|
|
2
|
+
# convert \" to " and similar
|
|
3
|
+
Escapes = Hash.new{|h,k|k}.update({
|
|
4
|
+
'\\\\' => '\\',
|
|
5
|
+
'\\"' => '"',
|
|
6
|
+
"\\'" => "'",
|
|
7
|
+
'\e' => "\e",
|
|
8
|
+
'\r' => "\r",
|
|
9
|
+
'\n' => "\n",
|
|
10
|
+
'\f' => "\f",
|
|
11
|
+
'\t' => "\t",
|
|
12
|
+
'\ ' => " ",
|
|
13
|
+
})
|
|
14
|
+
def unescaped
|
|
15
|
+
gsub(/\\.|[^\\]/) { |m| Escapes[m] }
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/scheduler.rb
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
# See LICENSE.txt for permissions.
|
|
5
|
+
#++
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
require 'durations'
|
|
10
|
+
require 'event'
|
|
11
|
+
require 'thread'
|
|
12
|
+
require 'log/comfort'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# == Synopsis
|
|
17
|
+
# scheduler = Dispatcher::Scheduled.new(*events)
|
|
18
|
+
# scheduler.every 5.minutes do puts "Another 5 minutes wasted!" end
|
|
19
|
+
# scheduler.add(event)
|
|
20
|
+
# scheduler.delete(event)
|
|
21
|
+
# scheduler.suspend
|
|
22
|
+
# scheduler.resume
|
|
23
|
+
# scheduler.terminate # important, else you'll have a forever sleeping thread left
|
|
24
|
+
#
|
|
25
|
+
# == Description
|
|
26
|
+
# Provides methods to execute scheduled Events
|
|
27
|
+
#
|
|
28
|
+
class Scheduler
|
|
29
|
+
include Log::Comfort
|
|
30
|
+
|
|
31
|
+
def initialize(delete_finished=true, *events)
|
|
32
|
+
events.each { |event| event.scheduler = self }
|
|
33
|
+
@terminated = false
|
|
34
|
+
@instructions = Queue.new
|
|
35
|
+
@mutex = Mutex.new
|
|
36
|
+
@events = []
|
|
37
|
+
@alarm = Thread.new {} # dead thread
|
|
38
|
+
@min_sleep = 0.1
|
|
39
|
+
@execute = method(:execute)
|
|
40
|
+
@sleeper = Thread.new(&method(:sleeper))
|
|
41
|
+
@auto_delete = delete_finished
|
|
42
|
+
add(*events)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def sleeper
|
|
46
|
+
while instr = @instructions.shift
|
|
47
|
+
@alarm.kill # just overkill...
|
|
48
|
+
|
|
49
|
+
now = Time.now+@min_sleep
|
|
50
|
+
@mutex.synchronize {
|
|
51
|
+
i = nil
|
|
52
|
+
@events.each_with_index { |event,i|
|
|
53
|
+
break unless event.due?(now)
|
|
54
|
+
event.one_done
|
|
55
|
+
Thread.new(event, &@execute)
|
|
56
|
+
}
|
|
57
|
+
@events.first(i+1).each { |event|
|
|
58
|
+
@events.delete(event) if event.finished?
|
|
59
|
+
} if (@auto_delete && i)
|
|
60
|
+
unless @events.empty?
|
|
61
|
+
@events.sort!
|
|
62
|
+
if seconds = @events.first.seconds_left then
|
|
63
|
+
if seconds < @min_sleep then
|
|
64
|
+
@instructions.push(:wakeup)
|
|
65
|
+
else
|
|
66
|
+
@alarm = Thread.new {
|
|
67
|
+
sleep(seconds)
|
|
68
|
+
@instructions.push(:wakeup)
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# not yet implemented
|
|
78
|
+
def suspend
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# not yet implemented
|
|
82
|
+
def resume
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# not yet implemented
|
|
86
|
+
def restart
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def terminate
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def reschedule(event)
|
|
93
|
+
@mutex.synchronize {
|
|
94
|
+
#first = @events.first
|
|
95
|
+
@events.sort!
|
|
96
|
+
#first != @events.first
|
|
97
|
+
}
|
|
98
|
+
@instructions.push(:reschedule)
|
|
99
|
+
self
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Adds the events to the scheduler
|
|
103
|
+
def add(*events)
|
|
104
|
+
events.each { |event| event.scheduler = self }
|
|
105
|
+
@instructions.push(:reschedule) if @mutex.synchronize {
|
|
106
|
+
first = @events.first
|
|
107
|
+
@events.push(*events)
|
|
108
|
+
@events.sort!
|
|
109
|
+
first != @events.first
|
|
110
|
+
}
|
|
111
|
+
self
|
|
112
|
+
end
|
|
113
|
+
alias << add
|
|
114
|
+
|
|
115
|
+
def delete(event)
|
|
116
|
+
wakeup = false
|
|
117
|
+
@mutex.synchronize {
|
|
118
|
+
wakeup = @events.first == event
|
|
119
|
+
@events.delete(event)
|
|
120
|
+
}
|
|
121
|
+
@instructions.push(:reschedule) if wakeup
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def reject!(&block)
|
|
125
|
+
@instructions.push(:reschedule) if @mutex.synchronize {
|
|
126
|
+
first = @events.first
|
|
127
|
+
@events.reject!(&block)
|
|
128
|
+
@events.min
|
|
129
|
+
first != @events.first
|
|
130
|
+
}
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def execute(event)
|
|
134
|
+
event.call(event)
|
|
135
|
+
rescue Exception => e
|
|
136
|
+
exception(e) # log it
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Creates a new Dispatcher::Event.every, adds it and
|
|
140
|
+
# returns the event
|
|
141
|
+
def every(*args, &block)
|
|
142
|
+
add(event = Event::Every.new(*args, &block))
|
|
143
|
+
event
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Creates a new Dispatcher::Event.timed, adds it and
|
|
147
|
+
# returns the event
|
|
148
|
+
def timed(*args, &block)
|
|
149
|
+
add(event = Event::Timed.new(*args, &block))
|
|
150
|
+
event
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Creates a new Dispatcher::Event.timed, adds it and
|
|
154
|
+
# returns the event
|
|
155
|
+
def at(*args, &block)
|
|
156
|
+
add(event = Event::At.new(*args, &block))
|
|
157
|
+
event
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# The amount of scheduled events
|
|
161
|
+
def length
|
|
162
|
+
@events.length
|
|
163
|
+
end
|
|
164
|
+
end
|
data/lib/scriptfile.rb
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
# See LICENSE.txt for permissions.
|
|
5
|
+
#++
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
require 'ruby/kernel/non_verbose'
|
|
10
|
+
|
|
11
|
+
# use data after __END__ in .rb files like normal files
|
|
12
|
+
# WARNING:
|
|
13
|
+
# - if you have an __END__ alone in a multiline comment or string
|
|
14
|
+
# your .rb file will be damaged.
|
|
15
|
+
# - Current implementation *will* append an __END__ to the file, regardeless
|
|
16
|
+
# of what you do (that will change)
|
|
17
|
+
#
|
|
18
|
+
# Example:
|
|
19
|
+
# require 'scriptfile'
|
|
20
|
+
# ScriptFile.open(__FILE__) { |fh|
|
|
21
|
+
# p [:tell, fh.tell]
|
|
22
|
+
# p [:read, fh.read]
|
|
23
|
+
# p [:tell, fh.tell]
|
|
24
|
+
# p [:truncate, fh.truncate]
|
|
25
|
+
# p [:tell, fh.tell]
|
|
26
|
+
# p [:read, fh.read]
|
|
27
|
+
# p [:tell, fh.tell]
|
|
28
|
+
# p [:seek, fh.seek]
|
|
29
|
+
# p [:puts, fh.puts("ScriptFile rocks")]
|
|
30
|
+
# p [:seek, fh.seek]
|
|
31
|
+
# p [:read, fh.read]
|
|
32
|
+
# }
|
|
33
|
+
#
|
|
34
|
+
class ScriptFile < File
|
|
35
|
+
(IO.instance_methods(false)+File.instance_methods(false) - %w[see tell pos truncate]).each { |m| private m }
|
|
36
|
+
|
|
37
|
+
class <<self
|
|
38
|
+
def read(file)
|
|
39
|
+
open(file) { |fh| fh.read }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def open(file, mode="r")
|
|
43
|
+
fh = new(file, mode)
|
|
44
|
+
block_given? ? yield(fh) : fh
|
|
45
|
+
ensure
|
|
46
|
+
fh.close if fh and block_given?
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def initialize(file, mode="r")
|
|
51
|
+
super(file, "r+") # "r#{'+' unless mode=='r'}"
|
|
52
|
+
@offset = 0
|
|
53
|
+
while line = gets
|
|
54
|
+
@offset += line.size
|
|
55
|
+
break if line =~ /^__END__$/
|
|
56
|
+
end
|
|
57
|
+
unless line then
|
|
58
|
+
file_seek(-1, IO::SEEK_CUR)
|
|
59
|
+
unless read(1) == "\n" then
|
|
60
|
+
puts
|
|
61
|
+
@offset += 1
|
|
62
|
+
end
|
|
63
|
+
print "__END__\n"
|
|
64
|
+
@offset += 8
|
|
65
|
+
end
|
|
66
|
+
case mode
|
|
67
|
+
when /^w/: truncate
|
|
68
|
+
when /^a/: read
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
public :each, :each_byte, :each_line
|
|
73
|
+
public :gets, :getc, :read, :readchar, :readline, :readlines
|
|
74
|
+
public :puts, :putc, :print, :printf, :write
|
|
75
|
+
public :ctime, :atime, :mtime
|
|
76
|
+
|
|
77
|
+
alias file_seek seek unless method_defined?(:file_seek)
|
|
78
|
+
private :file_seek
|
|
79
|
+
|
|
80
|
+
def seek(offset=0, type=IO::SEEK_SET)
|
|
81
|
+
raise ArgumentError, "negative seeks currently not supported" if offset < 0
|
|
82
|
+
raise ArgumentError, "no type but IO::SEEK_SET supported at the moment" if type != IO::SEEK_SET
|
|
83
|
+
super(offset+@offset)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def truncate(bytes=0)
|
|
87
|
+
super(bytes+@offset)
|
|
88
|
+
seek(bytes)
|
|
89
|
+
0
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def tell
|
|
93
|
+
super-@offset
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def pos
|
|
97
|
+
super-@offset
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
public :close
|
|
101
|
+
end
|
data/lib/templater.rb
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
# See LICENSE.txt for permissions.
|
|
5
|
+
#++
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
require 'erb'
|
|
10
|
+
require 'ostruct'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Templater
|
|
15
|
+
Raiser = proc { |e|
|
|
16
|
+
raise
|
|
17
|
+
}
|
|
18
|
+
Teller = proc { |e|
|
|
19
|
+
"<<#{e.class}: #{e}>>"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class Variables < OpenStruct
|
|
23
|
+
def initialize(delegator=nil, variables={}, &on_error)
|
|
24
|
+
super(variables)
|
|
25
|
+
@delegator = delegator
|
|
26
|
+
@on_error = on_error || RaiseExceptions
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def method_missing(m, *a, &b)
|
|
30
|
+
if @table.key?(m) || m.to_s =~ /=$/ then
|
|
31
|
+
super
|
|
32
|
+
elsif @delegator && @delegator.respond_to?(m) then
|
|
33
|
+
@delegator.send(m, *a, &b) if @delegator
|
|
34
|
+
else
|
|
35
|
+
raise NameError, "undefined local variable or method `#{m}'"
|
|
36
|
+
end
|
|
37
|
+
rescue Exception => e
|
|
38
|
+
@on_error.call(e)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def inspect
|
|
42
|
+
@table.inspect
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Option defaults
|
|
47
|
+
Opt = {
|
|
48
|
+
:safe_level => nil,
|
|
49
|
+
:trim_mode => '%<>',
|
|
50
|
+
:eoutvar => '_erbout'
|
|
51
|
+
}
|
|
52
|
+
# binding method
|
|
53
|
+
Binder = Object.instance_method(:binding)
|
|
54
|
+
|
|
55
|
+
attr_reader :string
|
|
56
|
+
def initialize(string, opt={})
|
|
57
|
+
opt, string = string, nil if string.kind_of?(Hash)
|
|
58
|
+
opt = Opt.merge(opt)
|
|
59
|
+
file = nil
|
|
60
|
+
if !string || File.exist?(string) then
|
|
61
|
+
string ||= opt[:filename]
|
|
62
|
+
file = string
|
|
63
|
+
string = File.read(string)
|
|
64
|
+
end
|
|
65
|
+
@string = string.dup.freeze
|
|
66
|
+
@erb = ERB.new(@string, *opt.values_at(:safe_level, :trim_mode, :eoutvar))
|
|
67
|
+
@erb.filename = file if file
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def result(delegator=nil, variables={}, &on_error)
|
|
71
|
+
variables ||= {}
|
|
72
|
+
on_error ||= Raiser
|
|
73
|
+
variables = Variables.new(delegator, variables, &on_error) unless variables.kind_of?(Variables)
|
|
74
|
+
@erb.result(Binder.bind(variables).call)
|
|
75
|
+
rescue NameError => e
|
|
76
|
+
raise NameError, e.message+" for #{self.inspect} with #{variables.inspect}", e.backtrace
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def inspect # :nodoc:
|
|
80
|
+
"#<%s:0x%x string=%s>" % [
|
|
81
|
+
self.class,
|
|
82
|
+
object_id << 1,
|
|
83
|
+
@string.inspect
|
|
84
|
+
]
|
|
85
|
+
end
|
|
86
|
+
end
|
data/test/cloptions.rb
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
require 'pp'
|
|
2
|
+
require 'test/unit'
|
|
3
|
+
|
|
4
|
+
class TestCLOptions < Test::Unit::TestCase
|
|
5
|
+
def setup
|
|
6
|
+
# if you prefer methods over bullet lists, o is just an alias to option
|
|
7
|
+
@options = CLOptions.new("file1 file2 [file3]") {
|
|
8
|
+
application_name 'app' # optional, defaults to $0
|
|
9
|
+
#required_options %w(-p) # optional (obviously)
|
|
10
|
+
|
|
11
|
+
o nil, '--help', "Display this help."
|
|
12
|
+
o '-i', '--interactive', "Don't deamonize, lets process run interactively in Terminal."
|
|
13
|
+
o '-p', '--port', 'PORT', "On what port to run the server."
|
|
14
|
+
o '-v', '--version', "Prints the version and quits."
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_flags
|
|
19
|
+
flag1 = CLOptions::Switch.new('-p')
|
|
20
|
+
flag2 = CLOptions::Switch.new('-p', 'Help')
|
|
21
|
+
flag3 = CLOptions::Switch.new('-p', '--port', 'Help')
|
|
22
|
+
flag4 = CLOptions::Switch.new('-p', '--port', nil, 'Help')
|
|
23
|
+
flag5 = CLOptions::Switch.new('-p', '--port', 'INTEGER', 'Help')
|
|
24
|
+
flag6 = CLOptions::Switch.new('-p', '--port', 'INTEGER', true, 'Help')
|
|
25
|
+
flag7 = CLOptions::Switch.new('-p', '--port', 'INTEGER', :portmap, 'Help')
|
|
26
|
+
flag8 = CLOptions::Switch.new('-p', '--port', 'INTEGER', :portmap, 'Help', &CLOptions::Integer)
|
|
27
|
+
|
|
28
|
+
assert_equal('-p', flag1.short)
|
|
29
|
+
assert_equal(nil, flag1.long)
|
|
30
|
+
assert_equal([], flag1.params)
|
|
31
|
+
assert_equal(%w(-p), flag1.mappings)
|
|
32
|
+
assert_equal(nil, flag1.hash_key)
|
|
33
|
+
assert_equal('', flag1.help)
|
|
34
|
+
assert_equal('-p', flag1.to_s)
|
|
35
|
+
|
|
36
|
+
assert_equal('-p', flag2.short)
|
|
37
|
+
assert_equal(nil, flag2.long)
|
|
38
|
+
assert_equal([], flag2.params)
|
|
39
|
+
assert_equal(%w(-p), flag2.mappings)
|
|
40
|
+
assert_equal(nil, flag2.hash_key)
|
|
41
|
+
assert_equal('Help', flag2.help)
|
|
42
|
+
assert_equal('-p', flag2.to_s)
|
|
43
|
+
|
|
44
|
+
assert_equal('-p', flag3.short)
|
|
45
|
+
assert_equal('--port', flag3.long)
|
|
46
|
+
assert_equal([], flag3.params)
|
|
47
|
+
assert_equal(%w(-p --port), flag3.mappings)
|
|
48
|
+
assert_equal(nil, flag3.hash_key)
|
|
49
|
+
assert_equal('Help', flag3.help)
|
|
50
|
+
assert_equal('-p, --port', flag3.to_s)
|
|
51
|
+
|
|
52
|
+
assert_equal('-p', flag5.short)
|
|
53
|
+
assert_equal('--port', flag5.long)
|
|
54
|
+
assert_equal(['INTEGER'], flag5.params)
|
|
55
|
+
assert_equal(%w(-p --port), flag5.mappings)
|
|
56
|
+
assert_equal(nil, flag5.hash_key)
|
|
57
|
+
assert_equal('Help', flag5.help)
|
|
58
|
+
assert_equal('-p, --port INTEGER', flag5.to_s)
|
|
59
|
+
|
|
60
|
+
assert_equal('-p', flag6.short)
|
|
61
|
+
assert_equal('--port', flag6.long)
|
|
62
|
+
assert_equal(['INTEGER'], flag6.params)
|
|
63
|
+
assert_equal(['-p', '--port', :port], flag6.mappings)
|
|
64
|
+
assert_equal(:port, flag6.hash_key)
|
|
65
|
+
assert_equal('Help', flag6.help)
|
|
66
|
+
assert_equal('-p, --port INTEGER', flag6.to_s)
|
|
67
|
+
|
|
68
|
+
assert_equal('-p', flag7.short)
|
|
69
|
+
assert_equal('--port', flag7.long)
|
|
70
|
+
assert_equal(['INTEGER'], flag7.params)
|
|
71
|
+
assert_equal(['-p', '--port', :portmap], flag7.mappings)
|
|
72
|
+
assert_equal(:portmap, flag7.hash_key)
|
|
73
|
+
assert_equal('Help', flag7.help)
|
|
74
|
+
assert_equal('-p, --port INTEGER', flag7.to_s)
|
|
75
|
+
|
|
76
|
+
assert_equal('-p', flag8.short)
|
|
77
|
+
assert_equal('--port', flag8.long)
|
|
78
|
+
assert_equal(['INTEGER'], flag8.params)
|
|
79
|
+
assert_equal(['-p', '--port', :portmap], flag8.mappings)
|
|
80
|
+
assert_equal(:portmap, flag8.hash_key)
|
|
81
|
+
assert_equal('Help', flag8.help)
|
|
82
|
+
assert_equal('-p, --port INTEGER', flag8.to_s)
|
|
83
|
+
|
|
84
|
+
flag = flag8.process(nil, ["1234"])
|
|
85
|
+
assert_equal(1234, flag.parameters)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_parsing1
|
|
89
|
+
result = nil
|
|
90
|
+
assert_nothing_raised { result = @options.parse(%w(-p 1234 --interactive fubu fubar)) }
|
|
91
|
+
|
|
92
|
+
assert_equal("fubu", result.argv.file1)
|
|
93
|
+
assert_equal("fubu", result.argv[0])
|
|
94
|
+
assert_equal("fubar", result.argv.file2)
|
|
95
|
+
assert_equal("fubar", result.argv[1])
|
|
96
|
+
assert_equal(nil, result.argv.file3)
|
|
97
|
+
|
|
98
|
+
assert_equal("1234", result['-p'])
|
|
99
|
+
assert_equal("1234", result['--port'])
|
|
100
|
+
assert_equal(true, result['-i'])
|
|
101
|
+
assert_equal(true, result['--interactive'])
|
|
102
|
+
assert_equal(nil, result['-v'])
|
|
103
|
+
assert_equal(nil, result['--version'])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_usage
|
|
107
|
+
assert_equal(<<EOUSAGE, @options.usage(false))
|
|
108
|
+
Usage:
|
|
109
|
+
app -iv -p PORT --help file1 file2 [file3]
|
|
110
|
+
EOUSAGE
|
|
111
|
+
assert_equal(<<EOUSAGE, @options.usage(true))
|
|
112
|
+
Usage:
|
|
113
|
+
app -iv -p PORT --help --interactive --port PORT --version file1 file2 [file3]
|
|
114
|
+
EOUSAGE
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def test_help
|
|
118
|
+
assert_equal(<<EOUSAGE, @options.help)
|
|
119
|
+
app file1 file2 [file3]
|
|
120
|
+
|
|
121
|
+
--help
|
|
122
|
+
Display this help.
|
|
123
|
+
-i, --interactive
|
|
124
|
+
Don't deamonize, lets process run interactively in Terminal.
|
|
125
|
+
-p, --port PORT
|
|
126
|
+
On what port to run the server.
|
|
127
|
+
-v, --version
|
|
128
|
+
Prints the version and quits.
|
|
129
|
+
EOUSAGE
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_options
|
|
133
|
+
end
|
|
134
|
+
end
|