flog 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +15 -1
- data/Manifest.txt +17 -0
- data/bin/flog +52 -23
- data/lib/flog.rb +230 -179
- data/lib/gauntlet_flog.rb +193 -0
- data/spec/flog_command_spec.rb +352 -0
- data/spec/flog_integration_spec.rb +944 -0
- data/spec/flog_spec.rb +1123 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +36 -0
- data/spec_fixtures/collection/bigger_example/acts/date_range.rb +199 -0
- data/spec_fixtures/collection/bigger_example/acts/range.rb +391 -0
- data/spec_fixtures/collection/bigger_example/association_extensions/date_ranged.rb +11 -0
- data/spec_fixtures/collection/bigger_example/association_extensions/ranged.rb +13 -0
- data/spec_fixtures/collection/bigger_example/reflection_extensions/ranged.rb +50 -0
- data/spec_fixtures/directory/bot_filter.rb +70 -0
- data/spec_fixtures/directory/bot_parser.rb +79 -0
- data/spec_fixtures/directory/bot_parser_format.rb +23 -0
- data/spec_fixtures/directory/bot_sender.rb +46 -0
- data/spec_fixtures/empty/empty.rb +0 -0
- data/spec_fixtures/simple/simple.rb +191 -0
- metadata +21 -4
@@ -0,0 +1,13 @@
|
|
1
|
+
module Centerstone #:nodoc:
|
2
|
+
module AssociationExtensions
|
3
|
+
module Ranged
|
4
|
+
|
5
|
+
%w{before after containing contained_by overlapping}.each do |comparison|
|
6
|
+
define_method comparison do |target|
|
7
|
+
self.select { |x| x.send((comparison + '?').to_sym, target) }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Centerstone #:nodoc:
|
2
|
+
module ReflectionExtensions
|
3
|
+
module Ranged
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
puts 'hello'
|
7
|
+
base.send(:include, InstanceMethods)
|
8
|
+
|
9
|
+
base.instance_eval do
|
10
|
+
alias_method_chain :initialize, :has_many_range_extension
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module InstanceMethods
|
15
|
+
def initialize_with_has_many_range_extension(*args)
|
16
|
+
puts 'yo'
|
17
|
+
returning initialize_without_has_many_range_extension(*args) do
|
18
|
+
puts 'returning stuff'
|
19
|
+
if macro.to_s == 'has_many'
|
20
|
+
puts 'adding the extension'
|
21
|
+
add_has_many_range_extension
|
22
|
+
end
|
23
|
+
puts 'blah'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def add_has_many_range_extension
|
30
|
+
puts 'extension adding, hey'
|
31
|
+
puts 'bbbbb'
|
32
|
+
puts "target class [#{klass}]"
|
33
|
+
if klass.acts_as_range?
|
34
|
+
puts 'target acts as range'
|
35
|
+
extension = Centerstone::AssociationExtensions::Ranged
|
36
|
+
opts = options
|
37
|
+
|
38
|
+
opts[:extend] ||= []
|
39
|
+
opts[:extend] = [opts[:extend]].flatten
|
40
|
+
|
41
|
+
opts[:extend].push(extension) unless opts[:extend].include?(extension)
|
42
|
+
|
43
|
+
@options = opts
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class BotFilter
|
2
|
+
attr_reader :options
|
3
|
+
|
4
|
+
def initialize(options = {})
|
5
|
+
@options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
@@kinds = []
|
9
|
+
@@filters_registered = false
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def new(options = {})
|
13
|
+
locate_filters(options) unless @@filters_registered
|
14
|
+
obj = allocate
|
15
|
+
obj.send :initialize, options
|
16
|
+
obj
|
17
|
+
end
|
18
|
+
|
19
|
+
def kinds
|
20
|
+
@@kinds
|
21
|
+
end
|
22
|
+
|
23
|
+
def register(name)
|
24
|
+
@@kinds << name
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear_kinds
|
28
|
+
@@kinds = []
|
29
|
+
@@filters_registered = false
|
30
|
+
end
|
31
|
+
|
32
|
+
def get(ident)
|
33
|
+
name = ident.to_s.gsub(/(?:^|_)([a-z])/) { $1.upcase }.to_sym
|
34
|
+
const_get(name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def locate_filters(options)
|
38
|
+
if options and options[:active_filters]
|
39
|
+
options[:active_filters].each do |filter|
|
40
|
+
register_filter(filter)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
@@filters_registered = true
|
44
|
+
end
|
45
|
+
|
46
|
+
def filter_path(name)
|
47
|
+
File.expand_path(File.dirname(__FILE__)+"/../lib/filters/#{name}.rb")
|
48
|
+
end
|
49
|
+
|
50
|
+
def register_filter(name)
|
51
|
+
path = filter_path(name)
|
52
|
+
raise ArgumentError, "Could not find source code for filter [#{name}] in [#{path}]" unless File.exists? path
|
53
|
+
load(path)
|
54
|
+
register(name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def process(data)
|
59
|
+
result = data
|
60
|
+
self.class.kinds.each do |k|
|
61
|
+
if result
|
62
|
+
result = BotFilter.get(k).new(options).process(result)
|
63
|
+
else
|
64
|
+
result = nil
|
65
|
+
break
|
66
|
+
end
|
67
|
+
end
|
68
|
+
result
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'bot_parser_format'
|
2
|
+
|
3
|
+
class BotParser
|
4
|
+
@formats = []
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_reader :formats
|
8
|
+
|
9
|
+
def register_format(*args, &block)
|
10
|
+
formats << BotParserFormat.new(*args, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def clear_formats
|
14
|
+
@formats = []
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def formats() self.class.formats; end
|
19
|
+
|
20
|
+
register_format :image, /^\s*(?:(.*?)\s+)?(http:\S+\.(?:jpe?g|png|gif))(?:\s+(\S.*))?$/i,
|
21
|
+
%q['http://www.citizenx.cx/img/best_picture_ever.jpg'],
|
22
|
+
%q['http://www.citizenx.cx/img/best_picture_never.jpg this poster hangs over my bed'],
|
23
|
+
%q['Best. Picture. Ever. http://www.citizenx.cx/img/best_picture_ever.jpg'] do |md, _|
|
24
|
+
{ :title => md[1], :source => md[2], :caption => md[3] }
|
25
|
+
end
|
26
|
+
|
27
|
+
register_format :video, %r{^\s*(?:(.*?)\s+)?(http://(?:www\.)?youtube\.com/\S+\?\S+)(?:\s+(.*))?$}i,
|
28
|
+
%q['http://www.youtube.com/watch?v=E2Fjilze0eI'],
|
29
|
+
%q['http://www.youtube.com/watch?v=E2Fjilze0eI the bunny gets it'],
|
30
|
+
%q['A waste of chocolate http://www.youtube.com/watch?v=E2Fjilze0eI'] do |md, _|
|
31
|
+
{ :title => md[1], :embed => md[2], :caption => md[3] }
|
32
|
+
end
|
33
|
+
|
34
|
+
register_format :quote, /^\s*"([^"]+)"\s+--\s*(.*?)(?:\s+\((https?:.*)\))?$/i,
|
35
|
+
%q['"adios, turd nuggets" --J.P.'],
|
36
|
+
%q['"adios, turd nuggets" --J.P. (http://imdb.com/title/tt0456554/)'] do |md, _|
|
37
|
+
{ :quote => md[1], :source => md[2], :url => md[3] }
|
38
|
+
end
|
39
|
+
|
40
|
+
register_format :link, %r{^\s*(?:(.*?)\s+)?(https?://\S+)\s*(?:\s+(\S.*))?$}i,
|
41
|
+
%q['http://news.yahoo.com/s/ap/20071203/ap_on_sc/dinosaur_mummy'],
|
42
|
+
%q['http://news.yahoo.com/s/ap/20071203/ap_on_sc/dinosaur_mummy shows just how fast a mummified dinosaur can be'],
|
43
|
+
%q['Fossilized Hadrosaur http://news.yahoo.com/s/ap/20071203/ap_on_sc/dinosaur_mummy'] do |md, _|
|
44
|
+
{ :name => md[1], :url => md[2], :description => md[3] }
|
45
|
+
end
|
46
|
+
|
47
|
+
register_format :fact, %r{^\s*fact:\s+(.*)}i,
|
48
|
+
%q['FACT: Zed Shaw doesn't do pushups, he pushes the earth down'] do |md, _|
|
49
|
+
{ :title => "FACT: #{md[1]}" }
|
50
|
+
end
|
51
|
+
|
52
|
+
register_format :true_or_false, %r{^\s*(?:(?:true\s+or\s+false)|(?:t\s+or\s+f))\s*[:\?]\s+(.*)}i,
|
53
|
+
%q['T or F: the human body has more than one sphincter'],
|
54
|
+
%q['true or false: the human body has more than one sphincter'],
|
55
|
+
%q['true or false? the human body has more than one sphincter'] do |md, _|
|
56
|
+
{ :title => "True or False? #{md[1]}" }
|
57
|
+
end
|
58
|
+
|
59
|
+
register_format :definition, %r{^\s*defin(?:e|ition):?\s+(.*?)\s*(?:[:=]|as)\s*(.*)}i,
|
60
|
+
%q['Definition: tardulism: the ideology of the tard culture'],
|
61
|
+
%q['Definition: tardulism = the ideology of the tard culture'],
|
62
|
+
%q["define tardulism as the ideology of the tard culture"] do |md, _|
|
63
|
+
{ :title => "DEFINITION: #{md[1]}: #{md[2]}" }
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse(sender, channel, mesg)
|
67
|
+
return nil if mesg.empty?
|
68
|
+
|
69
|
+
common = { :poster => sender, :channel => channel }
|
70
|
+
|
71
|
+
result = nil
|
72
|
+
formats.detect { |f| result = f.process(mesg) }
|
73
|
+
|
74
|
+
return nil unless result
|
75
|
+
|
76
|
+
result = common.merge(result)
|
77
|
+
result
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class BotParserFormat
|
2
|
+
attr_reader :name, :format, :block
|
3
|
+
|
4
|
+
def initialize(name, format, *description, &block)
|
5
|
+
raise ArgumentError, 'Block needed' if block.nil?
|
6
|
+
|
7
|
+
@name = name
|
8
|
+
@format = format
|
9
|
+
@block = block
|
10
|
+
@description = description
|
11
|
+
end
|
12
|
+
|
13
|
+
def description
|
14
|
+
@description.empty? ? nil : @description.join("\n")
|
15
|
+
end
|
16
|
+
|
17
|
+
def process(text)
|
18
|
+
md = format.match(text)
|
19
|
+
return nil unless md
|
20
|
+
|
21
|
+
block.call(md, text).merge(:type => name)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class BotSender
|
2
|
+
attr_reader :kind
|
3
|
+
|
4
|
+
@@kinds = { }
|
5
|
+
|
6
|
+
def self.kinds
|
7
|
+
@@kinds.keys.sort_by {|k| k.to_s }
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.register(args = {})
|
11
|
+
args.each_pair {|k,v| @@kinds[k] = v }
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.new(args = {})
|
15
|
+
raise ArgumentError unless self.kinds.include?(args[:destination])
|
16
|
+
obj = @@kinds[args[:destination]].allocate
|
17
|
+
obj.send :initialize, args
|
18
|
+
obj
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(args = {})
|
22
|
+
validate(args)
|
23
|
+
@kind = args[:destination]
|
24
|
+
end
|
25
|
+
|
26
|
+
def deliver(message)
|
27
|
+
return nil unless message and message[:type]
|
28
|
+
meth = "do_#{message[:type]}".to_sym
|
29
|
+
raise ArgumentError, "unknown message type [#{message[:type]}]" unless self.respond_to?(meth)
|
30
|
+
begin
|
31
|
+
self.send(meth, message)
|
32
|
+
rescue Exception => e
|
33
|
+
return e.to_s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# validate arguments when creating a specific BotSender type instance
|
38
|
+
def validate(args = {})
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
require 'senders/tumblr'
|
43
|
+
|
44
|
+
class BotSender
|
45
|
+
@@kinds[:tumblr] = BotSender::Tumblr
|
46
|
+
end
|
File without changes
|
@@ -0,0 +1,191 @@
|
|
1
|
+
module ObjectDaddy
|
2
|
+
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend ClassMethods
|
5
|
+
if defined? ActiveRecord and klass < ActiveRecord::Base
|
6
|
+
klass.extend RailsClassMethods
|
7
|
+
|
8
|
+
class << klass
|
9
|
+
alias_method :validates_presence_of_without_object_daddy, :validates_presence_of
|
10
|
+
alias_method :validates_presence_of, :validates_presence_of_with_object_daddy
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
attr_accessor :exemplars_generated, :exemplar_path, :generators
|
17
|
+
attr_reader :presence_validated_attributes
|
18
|
+
protected :exemplars_generated=
|
19
|
+
|
20
|
+
# create a valid instance of this class, using any known generators
|
21
|
+
def spawn(args = {})
|
22
|
+
gather_exemplars
|
23
|
+
(generators || {}).each_pair do |handle, gen_data|
|
24
|
+
next if args[handle]
|
25
|
+
generator = gen_data[:generator]
|
26
|
+
if generator[:block]
|
27
|
+
if generator[:start]
|
28
|
+
generator[:prev] = args[handle] = generator[:start]
|
29
|
+
generator.delete(:start)
|
30
|
+
else
|
31
|
+
generator[:prev] = args[handle] = generator[:block].call(generator[:prev])
|
32
|
+
end
|
33
|
+
elsif generator[:method]
|
34
|
+
args[handle] = send(generator[:method])
|
35
|
+
elsif generator[:class]
|
36
|
+
args[handle] = generator[:class].next
|
37
|
+
end
|
38
|
+
end
|
39
|
+
if presence_validated_attributes and !presence_validated_attributes.empty?
|
40
|
+
req = {}
|
41
|
+
(presence_validated_attributes.keys - args.keys).each {|a| req[a.to_s] = true } # find attributes required by validates_presence_of not already set
|
42
|
+
|
43
|
+
belongs_to_associations = reflect_on_all_associations(:belongs_to).to_a
|
44
|
+
missing = belongs_to_associations.select { |a| req[a.name.to_s] or req[a.primary_key_name.to_s] }
|
45
|
+
if create_scope = scope(:create)
|
46
|
+
missing.reject! { |a| create_scope.include?(a.primary_key_name) }
|
47
|
+
end
|
48
|
+
missing.each {|a| args[a.name] = a.class_name.constantize.generate }
|
49
|
+
end
|
50
|
+
new(args)
|
51
|
+
end
|
52
|
+
|
53
|
+
# register a generator for an attribute of this class
|
54
|
+
# generator_for :foo do |prev| ... end
|
55
|
+
# generator_for :foo do ... end
|
56
|
+
# generator_for :foo, value
|
57
|
+
# generator_for :foo => value
|
58
|
+
# generator_for :foo, :class => GeneratorClass
|
59
|
+
# generator_for :foo, :method => :method_name
|
60
|
+
def generator_for(handle, args = {}, &block)
|
61
|
+
if handle.is_a?(Hash)
|
62
|
+
raise ArgumentError, "only specify one attr => value pair at a time" unless handle.keys.length == 1
|
63
|
+
gen_data = handle
|
64
|
+
handle = gen_data.keys.first
|
65
|
+
args = gen_data[handle]
|
66
|
+
end
|
67
|
+
|
68
|
+
raise ArgumentError, "an attribute name must be specified" unless handle = handle.to_sym
|
69
|
+
|
70
|
+
unless args.is_a?(Hash)
|
71
|
+
unless block
|
72
|
+
retval = args
|
73
|
+
block = lambda { retval } # lambda { args } results in returning the empty hash that args gets changed to
|
74
|
+
end
|
75
|
+
args = {} # args is assumed to be a hash for the rest of the method
|
76
|
+
end
|
77
|
+
|
78
|
+
if args[:method]
|
79
|
+
record_generator_for(handle, :method => args[:method].to_sym)
|
80
|
+
elsif args[:class]
|
81
|
+
raise ArgumentError, "generator class [#{args[:class].name}] does not have a :next method" unless args[:class].respond_to?(:next)
|
82
|
+
record_generator_for(handle, :class => args[:class])
|
83
|
+
elsif block
|
84
|
+
raise ArgumentError, "generator block must take an optional single argument" unless (-1..1).include?(block.arity) # NOTE: lambda {} has an arity of -1, while lambda {||} has an arity of 0
|
85
|
+
h = { :block => block }
|
86
|
+
h[:start] = args[:start] if args[:start]
|
87
|
+
record_generator_for(handle, h)
|
88
|
+
else
|
89
|
+
raise ArgumentError, "a block, :class generator, :method generator, or value must be specified to generator_for"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def gather_exemplars
|
94
|
+
return if exemplars_generated
|
95
|
+
if superclass.respond_to?(:gather_exemplars)
|
96
|
+
superclass.gather_exemplars
|
97
|
+
self.generators = (superclass.generators || {}).dup
|
98
|
+
end
|
99
|
+
|
100
|
+
path = File.join(exemplar_path, "#{underscore(name)}_exemplar.rb")
|
101
|
+
load(path) if File.exists?(path)
|
102
|
+
self.exemplars_generated = true
|
103
|
+
end
|
104
|
+
|
105
|
+
def presence_validated_attributes
|
106
|
+
@presence_validated_attributes ||= {}
|
107
|
+
attrs = @presence_validated_attributes
|
108
|
+
if superclass.respond_to?(:presence_validated_attributes)
|
109
|
+
attrs = superclass.presence_validated_attributes.merge(attrs)
|
110
|
+
end
|
111
|
+
attrs
|
112
|
+
end
|
113
|
+
|
114
|
+
protected
|
115
|
+
|
116
|
+
# we define an underscore helper ourselves since the ActiveSupport isn't available if we're not using Rails
|
117
|
+
def underscore(string)
|
118
|
+
string.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
119
|
+
end
|
120
|
+
|
121
|
+
def record_generator_for(handle, generator)
|
122
|
+
self.generators ||= {}
|
123
|
+
raise ArgumentError, "a generator for attribute [:#{handle}] has already been specified" if (generators[handle] || {})[:source] == self
|
124
|
+
generators[handle] = { :generator => generator, :source => self }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
module RailsClassMethods
|
129
|
+
def exemplar_path
|
130
|
+
File.join(RAILS_ROOT, 'test', 'exemplars')
|
131
|
+
end
|
132
|
+
|
133
|
+
def validates_presence_of_with_object_daddy(*attr_names)
|
134
|
+
@presence_validated_attributes ||= {}
|
135
|
+
new_attr = attr_names.dup
|
136
|
+
new_attr.pop if new_attr.last.is_a?(Hash)
|
137
|
+
new_attr.each {|a| @presence_validated_attributes[a] = true }
|
138
|
+
validates_presence_of_without_object_daddy(*attr_names)
|
139
|
+
end
|
140
|
+
|
141
|
+
def generate(args = {})
|
142
|
+
obj = spawn(args)
|
143
|
+
obj.save
|
144
|
+
obj
|
145
|
+
end
|
146
|
+
|
147
|
+
def generate!(args = {})
|
148
|
+
obj = spawn(args)
|
149
|
+
obj.save!
|
150
|
+
obj
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
# these additional routines are just to give us coverage for flog opcodes that we hadn't yet covered in an integration test
|
157
|
+
alias puts print
|
158
|
+
attr_writer :foo
|
159
|
+
|
160
|
+
foo = 2
|
161
|
+
|
162
|
+
case 'foo'
|
163
|
+
when :foo
|
164
|
+
true
|
165
|
+
else
|
166
|
+
false
|
167
|
+
end
|
168
|
+
|
169
|
+
class Foo
|
170
|
+
def initialize
|
171
|
+
super(:foo)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
until true
|
176
|
+
true
|
177
|
+
end
|
178
|
+
|
179
|
+
while false
|
180
|
+
true
|
181
|
+
end
|
182
|
+
|
183
|
+
begin
|
184
|
+
true
|
185
|
+
rescue Exception
|
186
|
+
false
|
187
|
+
else
|
188
|
+
true
|
189
|
+
end
|
190
|
+
|
191
|
+
puts(/foo/)
|