kbaum-pickle 0.2.1.1
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/.gitignore +3 -0
- data/History.txt +239 -0
- data/License.txt +20 -0
- data/README.rdoc +246 -0
- data/Rakefile +110 -0
- data/Todo.txt +4 -0
- data/VERSION +1 -0
- data/features/app/app.rb +121 -0
- data/features/app/blueprints.rb +11 -0
- data/features/app/factories.rb +23 -0
- data/features/app/views/notifier/email.erb +1 -0
- data/features/app/views/notifier/user_email.erb +6 -0
- data/features/email/email.feature +39 -0
- data/features/generator/generators.feature +59 -0
- data/features/path/models_page.feature +44 -0
- data/features/path/named_route_page.feature +10 -0
- data/features/pickle/create_from_active_record.feature +49 -0
- data/features/pickle/create_from_factory_girl.feature +55 -0
- data/features/pickle/create_from_machinist.feature +38 -0
- data/features/step_definitions/email_steps.rb +55 -0
- data/features/step_definitions/extra_email_steps.rb +7 -0
- data/features/step_definitions/fork_steps.rb +4 -0
- data/features/step_definitions/generator_steps.rb +46 -0
- data/features/step_definitions/path_steps.rb +14 -0
- data/features/step_definitions/pickle_steps.rb +73 -0
- data/features/support/email.rb +21 -0
- data/features/support/env.rb +55 -0
- data/features/support/paths.rb +46 -0
- data/features/support/pickle.rb +26 -0
- data/features/support/pickle_app.rb +4 -0
- data/garlic.rb +38 -0
- data/init.rb +0 -0
- data/lib/pickle/adapter.rb +88 -0
- data/lib/pickle/config.rb +48 -0
- data/lib/pickle/email/parser.rb +18 -0
- data/lib/pickle/email/world.rb +13 -0
- data/lib/pickle/email.rb +36 -0
- data/lib/pickle/parser/matchers.rb +87 -0
- data/lib/pickle/parser.rb +65 -0
- data/lib/pickle/path/world.rb +5 -0
- data/lib/pickle/path.rb +45 -0
- data/lib/pickle/session/parser.rb +34 -0
- data/lib/pickle/session.rb +157 -0
- data/lib/pickle/version.rb +6 -0
- data/lib/pickle/world.rb +9 -0
- data/lib/pickle.rb +26 -0
- data/pickle.gemspec +110 -0
- data/rails_generators/pickle/pickle_generator.rb +40 -0
- data/rails_generators/pickle/templates/email.rb +21 -0
- data/rails_generators/pickle/templates/email_steps.rb +55 -0
- data/rails_generators/pickle/templates/paths.rb +20 -0
- data/rails_generators/pickle/templates/pickle.rb +28 -0
- data/rails_generators/pickle/templates/pickle_steps.rb +73 -0
- data/spec/lib/pickle_adapter_spec.rb +164 -0
- data/spec/lib/pickle_config_spec.rb +97 -0
- data/spec/lib/pickle_email_parser_spec.rb +49 -0
- data/spec/lib/pickle_email_spec.rb +131 -0
- data/spec/lib/pickle_parser_matchers_spec.rb +70 -0
- data/spec/lib/pickle_parser_spec.rb +154 -0
- data/spec/lib/pickle_path_spec.rb +92 -0
- data/spec/lib/pickle_session_spec.rb +384 -0
- data/spec/lib/pickle_spec.rb +24 -0
- data/spec/spec_helper.rb +38 -0
- metadata +126 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module EmailHelpers
|
2
|
+
# Maps a name to an email address. Used by email_steps
|
3
|
+
|
4
|
+
def email_for(to)
|
5
|
+
case to
|
6
|
+
|
7
|
+
# add your own name => email address mappings here
|
8
|
+
|
9
|
+
when /^#{capture_model}$/
|
10
|
+
model($1).email
|
11
|
+
|
12
|
+
when /^"(.*)"$/
|
13
|
+
$1
|
14
|
+
|
15
|
+
else
|
16
|
+
to
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
World(EmailHelpers)
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
|
2
|
+
# It is recommended to regenerate this file in the future when you upgrade to a
|
3
|
+
# newer version of cucumber-rails. Consider adding your own code to a new file
|
4
|
+
# instead of editing this one. Cucumber will automatically load all features/**/*.rb
|
5
|
+
# files.
|
6
|
+
|
7
|
+
ENV["RAILS_ENV"] ||= "cucumber"
|
8
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../../../../config/environment')
|
9
|
+
|
10
|
+
require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support
|
11
|
+
require 'cucumber/rails/rspec'
|
12
|
+
require 'cucumber/rails/world'
|
13
|
+
require 'cucumber/rails/active_record'
|
14
|
+
require 'cucumber/web/tableish'
|
15
|
+
|
16
|
+
require 'webrat'
|
17
|
+
require 'webrat/core/matchers'
|
18
|
+
require 'cucumber/webrat/element_locator' # Deprecated in favor of #tableish - remove this line if you don't use #element_at or #table_at
|
19
|
+
|
20
|
+
Webrat.configure do |config|
|
21
|
+
config.mode = :rails
|
22
|
+
config.open_error_files = false # Set to true if you want error pages to pop up in the browser
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# If you set this to false, any error raised from within your app will bubble
|
27
|
+
# up to your step definition and out to cucumber unless you catch it somewhere
|
28
|
+
# on the way. You can make Rails rescue errors and render error pages on a
|
29
|
+
# per-scenario basis by tagging a scenario or feature with the @allow-rescue tag.
|
30
|
+
#
|
31
|
+
# If you set this to true, Rails will rescue all errors and render error
|
32
|
+
# pages, more or less in the same way your application would behave in the
|
33
|
+
# default production environment. It's not recommended to do this for all
|
34
|
+
# of your scenarios, as this makes it hard to discover errors in your application.
|
35
|
+
ActionController::Base.allow_rescue = false
|
36
|
+
|
37
|
+
# If you set this to true, each scenario will run in a database transaction.
|
38
|
+
# You can still turn off transactions on a per-scenario basis, simply tagging
|
39
|
+
# a feature or scenario with the @no-txn tag. If you are using Capybara,
|
40
|
+
# tagging with @culerity or @javascript will also turn transactions off.
|
41
|
+
#
|
42
|
+
# If you set this to false, transactions will be off for all scenarios,
|
43
|
+
# regardless of whether you use @no-txn or not.
|
44
|
+
#
|
45
|
+
# Beware that turning transactions off will leave data in your database
|
46
|
+
# after each scenario, which can lead to hard-to-debug failures in
|
47
|
+
# subsequent scenarios. If you do this, we recommend you create a Before
|
48
|
+
# block that will explicitly put your database in a known state.
|
49
|
+
Cucumber::Rails::World.use_transactional_fixtures = true
|
50
|
+
|
51
|
+
# How to clean your database when transactions are turned off. See
|
52
|
+
# http://github.com/bmabey/database_cleaner for more info.
|
53
|
+
require 'database_cleaner'
|
54
|
+
DatabaseCleaner.strategy = :truncation
|
55
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module NavigationHelpers
|
2
|
+
# Maps a name to a path. Used by the
|
3
|
+
#
|
4
|
+
# When /^I go to (.+)$/ do |page_name|
|
5
|
+
#
|
6
|
+
# step definition in web_steps.rb
|
7
|
+
#
|
8
|
+
def path_to(page_name)
|
9
|
+
case page_name
|
10
|
+
|
11
|
+
when /the home\s?page/
|
12
|
+
'/'
|
13
|
+
|
14
|
+
# Add more mappings here.
|
15
|
+
# Here is an example that pulls values out of the Regexp:
|
16
|
+
#
|
17
|
+
# when /^(.*)'s profile page$/i
|
18
|
+
# user_profile_path(User.find_by_login($1))
|
19
|
+
|
20
|
+
# added by script/generate pickle path
|
21
|
+
|
22
|
+
when /^#{capture_model}(?:'s)? page$/ # eg. the forum's page
|
23
|
+
path_to_pickle $1
|
24
|
+
|
25
|
+
when /^#{capture_model}(?:'s)? #{capture_model}(?:'s)? page$/ # eg. the forum's post's page
|
26
|
+
path_to_pickle $1, $2
|
27
|
+
|
28
|
+
when /^#{capture_model}(?:'s)? #{capture_model}'s (.+?) page$/ # eg. the forum's post's comments page
|
29
|
+
path_to_pickle $1, $2, :extra => $3 # or the forum's post's edit page
|
30
|
+
|
31
|
+
when /^#{capture_model}(?:'s)? (.+?) page$/ # eg. the forum's posts page
|
32
|
+
path_to_pickle $1, :extra => $2 # or the forum's edit page
|
33
|
+
|
34
|
+
when /^the (.+?) page$/ # translate to named route
|
35
|
+
send "#{$1.downcase.gsub(' ','_')}_path"
|
36
|
+
|
37
|
+
# end added by pickle path
|
38
|
+
|
39
|
+
else
|
40
|
+
raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
|
41
|
+
"Now, go and add a mapping in #{__FILE__}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
World(NavigationHelpers)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# this file generated by script/generate pickle [paths] [email]
|
2
|
+
#
|
3
|
+
# Make sure that you are loading your factory of choice in your cucumber environment
|
4
|
+
#
|
5
|
+
# For machinist add: features/support/machinist.rb
|
6
|
+
#
|
7
|
+
# require 'machinist/active_record' # or your chosen adaptor
|
8
|
+
# require File.dirname(__FILE__) + '/../../spec/blueprints' # or wherever your blueprints are
|
9
|
+
# Before { Sham.reset } # to reset Sham's seed between scenarios so each run has same random sequences
|
10
|
+
#
|
11
|
+
# For FactoryGirl add: features/support/factory_girl.rb
|
12
|
+
#
|
13
|
+
# require 'factory_girl'
|
14
|
+
# require File.dirname(__FILE__) + '/../../spec/factories' # or wherever your factories are
|
15
|
+
#
|
16
|
+
# You may also need to add gem dependencies on your factory of choice in <tt>config/environments/cucumber.rb</tt>
|
17
|
+
|
18
|
+
require 'pickle/world'
|
19
|
+
# Example of configuring pickle:
|
20
|
+
#
|
21
|
+
# Pickle.configure do |config|
|
22
|
+
# config.adapters = [:machinist]
|
23
|
+
# config.map 'I', 'myself', 'me', 'my', :to => 'user: "me"'
|
24
|
+
# end
|
25
|
+
require 'pickle/path/world'
|
26
|
+
require 'pickle/email/world'
|
data/garlic.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
garlic do
|
2
|
+
repo 'rails', :url => 'git://github.com/rails/rails'
|
3
|
+
repo 'rspec', :url => 'git://github.com/dchelimsky/rspec'
|
4
|
+
repo 'rspec-rails', :url => 'git://github.com/dchelimsky/rspec-rails'
|
5
|
+
repo 'factory_girl', :url => 'git://github.com/thoughtbot/factory_girl'
|
6
|
+
repo 'machinist', :url => 'git://github.com/notahat/machinist'
|
7
|
+
repo 'cucumber', :url => 'git://github.com/aslakhellesoy/cucumber'
|
8
|
+
repo 'cucumber-rails', :url => 'git://github.com/aslakhellesoy/cucumber-rails'
|
9
|
+
repo 'webrat', :url => 'git://github.com/brynary/webrat'
|
10
|
+
repo 'pickle', :path => '.'
|
11
|
+
|
12
|
+
['2-3-stable', '2-2-stable', '2-1-stable'].each do |rails|
|
13
|
+
|
14
|
+
target rails, :tree_ish => "origin/#{rails}" do
|
15
|
+
prepare do
|
16
|
+
plugin 'pickle', :clone => true
|
17
|
+
plugin 'rspec'
|
18
|
+
plugin 'rspec-rails' do
|
19
|
+
`script/generate rspec -f`
|
20
|
+
end
|
21
|
+
plugin 'factory_girl'
|
22
|
+
plugin 'cucumber'
|
23
|
+
plugin 'machinist'
|
24
|
+
plugin 'webrat'
|
25
|
+
plugin 'cucumber-rails' do
|
26
|
+
`script/generate cucumber --webrat -f`
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
run do
|
31
|
+
cd "vendor/plugins/pickle" do
|
32
|
+
sh "rake rcov:verify && rake features"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/init.rb
ADDED
File without changes
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Pickle
|
2
|
+
# Abstract Factory adapter class, if you have a factory type setup, you
|
3
|
+
# can easily create an adaptor to make it work with Pickle.
|
4
|
+
#
|
5
|
+
# The factory adaptor must have a #factories class method that returns
|
6
|
+
# its instances, and each instance must respond to:
|
7
|
+
#
|
8
|
+
# #name : identifies the factory by name (default is attr_reader)
|
9
|
+
# #klass : returns the associated model class for this factory (default is attr_reader)
|
10
|
+
# #create(attrs = {}) : returns a newly created object
|
11
|
+
class Adapter
|
12
|
+
attr_reader :name, :klass
|
13
|
+
|
14
|
+
def self.factories
|
15
|
+
raise NotImplementedError, "return an array of factory adapter objects"
|
16
|
+
end
|
17
|
+
|
18
|
+
def create(attrs = {})
|
19
|
+
raise NotImplementedError, "create and return an object with the given attributes"
|
20
|
+
end
|
21
|
+
|
22
|
+
cattr_writer :model_classes
|
23
|
+
self.model_classes = nil
|
24
|
+
|
25
|
+
def self.model_classes
|
26
|
+
# remove abstract, framework, and non-table classes
|
27
|
+
@@model_classes ||= ::ActiveRecord::Base.send(:subclasses).reject do |klass|
|
28
|
+
klass.abstract_class? || !klass.table_exists? ||
|
29
|
+
(defined?(CGI::Session::ActiveRecordStore::Session) && klass == CGI::Session::ActiveRecordStore::Session) ||
|
30
|
+
(defined?(::ActiveRecord::SessionStore::Session) && klass == ::ActiveRecord::SessionStore::Session)
|
31
|
+
end if defined?(::ActiveRecord)
|
32
|
+
@@model_classes ||= []
|
33
|
+
end
|
34
|
+
|
35
|
+
# machinist adapter
|
36
|
+
class Machinist < Adapter
|
37
|
+
def self.factories
|
38
|
+
factories = []
|
39
|
+
model_classes.each do |klass|
|
40
|
+
if blueprints = klass.instance_variable_get('@blueprints')
|
41
|
+
blueprints.keys.each {|blueprint| factories << new(klass, blueprint)}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
factories
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(klass, blueprint)
|
48
|
+
@klass, @blueprint = klass, blueprint
|
49
|
+
@name = @klass.name.underscore.gsub('/','_')
|
50
|
+
@name = "#{@blueprint}_#{@name}" unless @blueprint == :master
|
51
|
+
end
|
52
|
+
|
53
|
+
def create(attrs = {})
|
54
|
+
@klass.send(:make, @blueprint, attrs)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# factory-girl adapter
|
59
|
+
class FactoryGirl < Adapter
|
60
|
+
def self.factories
|
61
|
+
(::Factory.factories.values rescue []).map {|factory| new(factory)}
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize(factory)
|
65
|
+
@klass, @name = factory.build_class, factory.factory_name.to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
def create(attrs = {})
|
69
|
+
Factory.create(@name, attrs)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# fallback active record adapter
|
74
|
+
class ActiveRecord < Adapter
|
75
|
+
def self.factories
|
76
|
+
model_classes.map {|klass| new(klass) }
|
77
|
+
end
|
78
|
+
|
79
|
+
def initialize(klass)
|
80
|
+
@klass, @name = klass, klass.name.underscore.gsub('/','_')
|
81
|
+
end
|
82
|
+
|
83
|
+
def create(attrs = {})
|
84
|
+
@klass.send(:create!, attrs)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Pickle
|
4
|
+
class Config
|
5
|
+
attr_writer :adapters, :factories, :mappings, :predicates
|
6
|
+
|
7
|
+
def initialize(&block)
|
8
|
+
configure(&block) if block_given?
|
9
|
+
end
|
10
|
+
|
11
|
+
def configure(&block)
|
12
|
+
yield(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def adapters
|
16
|
+
@adapters ||= [:machinist, :factory_girl, :active_record]
|
17
|
+
end
|
18
|
+
|
19
|
+
def adapter_classes
|
20
|
+
adapters.map {|a| a.is_a?(Class) ? a : "pickle/adapter/#{a}".classify.constantize}
|
21
|
+
end
|
22
|
+
|
23
|
+
def factories
|
24
|
+
@factories ||= adapter_classes.reverse.inject({}) do |factories, adapter|
|
25
|
+
factories.merge(adapter.factories.inject({}){|h, f| h.merge(f.name => f)})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def predicates
|
30
|
+
@predicates ||= Pickle::Adapter.model_classes.map do |k|
|
31
|
+
k.public_instance_methods.select{|m| m =~ /\?$/} + k.column_names
|
32
|
+
end.flatten.uniq
|
33
|
+
end
|
34
|
+
|
35
|
+
def mappings
|
36
|
+
@mappings ||= []
|
37
|
+
end
|
38
|
+
|
39
|
+
# Usage: map 'me', 'myself', 'I', :to => 'user: "me"'
|
40
|
+
def map(*args)
|
41
|
+
options = args.extract_options!
|
42
|
+
raise ArgumentError, "Usage: map 'search' [, 'search2', ...] :to => 'replace'" unless args.any? && options[:to].is_a?(String)
|
43
|
+
args.each do |search|
|
44
|
+
self.mappings << OpenStruct.new(:search => search, :replacement => options[:to])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Pickle
|
2
|
+
module Email
|
3
|
+
# add ability to parse emails
|
4
|
+
module Parser
|
5
|
+
def match_email
|
6
|
+
"(?:#{match_prefix}?(?:#{match_index} )?email)"
|
7
|
+
end
|
8
|
+
|
9
|
+
def capture_email
|
10
|
+
"(#{match_email})"
|
11
|
+
end
|
12
|
+
|
13
|
+
def capture_index_in_email
|
14
|
+
"(?:#{match_prefix}?(?:#{capture_index} )?email)"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'pickle'
|
2
|
+
require 'pickle/email'
|
3
|
+
require 'pickle/email/parser'
|
4
|
+
|
5
|
+
# add email parser expressions
|
6
|
+
Pickle::Parser.send :include, Pickle::Email::Parser
|
7
|
+
|
8
|
+
World(Pickle::Email)
|
9
|
+
|
10
|
+
# shortcuts for use in step regexps
|
11
|
+
class << self
|
12
|
+
delegate :capture_email, :to => 'Pickle.parser'
|
13
|
+
end
|
data/lib/pickle/email.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Pickle
|
2
|
+
module Email
|
3
|
+
# return the deliveries array, optionally selected by the passed fields
|
4
|
+
def emails(fields = nil)
|
5
|
+
@emails = ActionMailer::Base.deliveries.select {|m| email_has_fields?(m, fields)}
|
6
|
+
end
|
7
|
+
|
8
|
+
def email(ref, fields = nil)
|
9
|
+
(match = ref.match(/^#{capture_index_in_email}$/)) or raise ArgumentError, "argument should match #{match_email}"
|
10
|
+
@emails or raise RuntimeError, "Call #emails before calling #email"
|
11
|
+
index = parse_index(match[1])
|
12
|
+
email_has_fields?(@emails[index], fields) ? @emails[index] : nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def email_has_fields?(email, fields)
|
16
|
+
parse_fields(fields).each do |key, val|
|
17
|
+
return false unless (Array(email.send(key)) & Array(val)).any?
|
18
|
+
end
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
# Saves the emails out to RAILS_ROOT/tmp/ and opens it in the default
|
24
|
+
# web browser if on OS X. (depends on webrat)
|
25
|
+
def save_and_open_emails
|
26
|
+
emails_to_open = @emails || emails
|
27
|
+
filename = "#{RAILS_ROOT}/tmp/webrat-email-#{Time.now.to_i}.html"
|
28
|
+
File.open(filename, "w") do |f|
|
29
|
+
emails_to_open.each_with_index do |e, i|
|
30
|
+
f.write "<h1>Email #{i+1}</h1><pre>#{e}</pre><hr />"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
open_in_browser(filename)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Pickle
|
2
|
+
class Parser
|
3
|
+
module Matchers
|
4
|
+
def match_ordinal
|
5
|
+
'(?:\d+(?:st|nd|rd|th))'
|
6
|
+
end
|
7
|
+
|
8
|
+
def match_index
|
9
|
+
"(?:first|last|#{match_ordinal})"
|
10
|
+
end
|
11
|
+
|
12
|
+
def match_prefix
|
13
|
+
'(?:(?:a|an|another|the|that) )'
|
14
|
+
end
|
15
|
+
|
16
|
+
def match_quoted
|
17
|
+
'(?:[^\\"]|\\.)*'
|
18
|
+
end
|
19
|
+
|
20
|
+
def match_label
|
21
|
+
"(?::? \"#{match_quoted}\")"
|
22
|
+
end
|
23
|
+
|
24
|
+
def match_value
|
25
|
+
"(?:\"#{match_quoted}\"|nil|true|false|[+-]?\\d+(?:\\.\\d+)?)"
|
26
|
+
end
|
27
|
+
|
28
|
+
def match_field
|
29
|
+
"(?:\\w+: #{match_value})"
|
30
|
+
end
|
31
|
+
|
32
|
+
def match_fields
|
33
|
+
"(?:#{match_field}, )*#{match_field}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def match_mapping
|
37
|
+
"(?:#{config.mappings.map(&:search).join('|')})"
|
38
|
+
end
|
39
|
+
|
40
|
+
def match_factory
|
41
|
+
"(?:#{config.factories.keys.map{|n| n.gsub('_','[_ ]')}.join('|')})"
|
42
|
+
end
|
43
|
+
|
44
|
+
def match_plural_factory
|
45
|
+
"(?:#{config.factories.keys.map{|n| n.pluralize.gsub('_','[_ ]')}.join('|')})"
|
46
|
+
end
|
47
|
+
|
48
|
+
def match_indexed_model
|
49
|
+
"(?:(?:#{match_index} )?#{match_factory})"
|
50
|
+
end
|
51
|
+
|
52
|
+
def match_labeled_model
|
53
|
+
"(?:#{match_factory}#{match_label})"
|
54
|
+
end
|
55
|
+
|
56
|
+
def match_model
|
57
|
+
"(?:#{match_mapping}|#{match_prefix}?(?:#{match_indexed_model}|#{match_labeled_model}))"
|
58
|
+
end
|
59
|
+
|
60
|
+
def match_predicate
|
61
|
+
"(?:#{config.predicates.map{|m| m.to_s.sub(/\?$/,'').gsub('_','[_ ]')}.join('|')})"
|
62
|
+
end
|
63
|
+
|
64
|
+
# create capture analogues of match methods
|
65
|
+
instance_methods.select{|m| m =~ /^match_/}.each do |method|
|
66
|
+
eval <<-end_eval
|
67
|
+
def #{method.to_s.sub('match_', 'capture_')} # def capture_field
|
68
|
+
"(" + #{method} + ")" # "(" + match_field + ")"
|
69
|
+
end # end
|
70
|
+
end_eval
|
71
|
+
end
|
72
|
+
|
73
|
+
# special capture methods
|
74
|
+
def capture_number_in_ordinal
|
75
|
+
'(?:(\d+)(?:st|nd|rd|th))'
|
76
|
+
end
|
77
|
+
|
78
|
+
def capture_name_in_label
|
79
|
+
"(?::? \"(#{match_quoted})\")"
|
80
|
+
end
|
81
|
+
|
82
|
+
def capture_key_and_value_in_field
|
83
|
+
"(?:(\\w+): #{capture_value})"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'pickle/parser/matchers'
|
2
|
+
|
3
|
+
module Pickle
|
4
|
+
class Parser
|
5
|
+
include Matchers
|
6
|
+
|
7
|
+
attr_reader :config
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@config = options[:config] || raise(ArgumentError, "Parser.new requires a :config")
|
11
|
+
end
|
12
|
+
|
13
|
+
# given a string like 'foo: "bar", bar: "baz"' returns {"foo" => "bar", "bar" => "baz"}
|
14
|
+
def parse_fields(fields)
|
15
|
+
if fields.blank?
|
16
|
+
{}
|
17
|
+
elsif fields =~ /^#{match_fields}$/
|
18
|
+
fields.scan(/(#{match_field})(?:,|$)/).inject({}) do |m, match|
|
19
|
+
m.merge(parse_field(match[0]))
|
20
|
+
end
|
21
|
+
else
|
22
|
+
raise ArgumentError, "The fields string is not in the correct format.\n\n'#{fields}' did not match: #{match_fields}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# given a string like 'foo: expr' returns {key => value}
|
27
|
+
def parse_field(field)
|
28
|
+
if field =~ /^#{capture_key_and_value_in_field}$/
|
29
|
+
{ $1 => eval($2) }
|
30
|
+
else
|
31
|
+
raise ArgumentError, "The field argument is not in the correct format.\n\n'#{field}' did not match: #{match_field}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# returns really underscored name
|
36
|
+
def canonical(str)
|
37
|
+
str.to_s.underscore.gsub(' ','_').gsub('/','_')
|
38
|
+
end
|
39
|
+
|
40
|
+
# return [factory_name, name or integer index]
|
41
|
+
def parse_model(model_name)
|
42
|
+
apply_mappings!(model_name)
|
43
|
+
if /#{capture_index} #{capture_factory}$/ =~ model_name
|
44
|
+
[canonical($2), parse_index($1)]
|
45
|
+
elsif /#{capture_factory}#{capture_name_in_label}?$/ =~ model_name
|
46
|
+
[canonical($1), canonical($2)]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_index(index)
|
51
|
+
case index
|
52
|
+
when nil, '', 'last' then -1
|
53
|
+
when /#{capture_number_in_ordinal}/ then $1.to_i - 1
|
54
|
+
when 'first' then 0
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def apply_mappings!(string)
|
60
|
+
config.mappings.each do |mapping|
|
61
|
+
string.sub! /^#{mapping.search}$/, mapping.replacement
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/pickle/path.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Pickle
|
2
|
+
module Path
|
3
|
+
# given args of pickle model name, and an optional extra action, or segment, will attempt to find
|
4
|
+
# a matching named route
|
5
|
+
#
|
6
|
+
# path_to_pickle 'the user', :action => 'edit' # => /users/3/edit
|
7
|
+
# path_to_pickle 'the user', 'the comment' # => /users/3/comments/1
|
8
|
+
# path_to_pickle 'the user', :segment => 'comments' # => /users/3/comments
|
9
|
+
#
|
10
|
+
# If you don;t know if the 'extra' part of the path is an action or a segment, then just
|
11
|
+
# pass it as 'extra' and this method will run through the possibilities
|
12
|
+
#
|
13
|
+
# path_to_pickle 'the user', :extra => 'new comment' # => /users/3/comments/new
|
14
|
+
def path_to_pickle(*pickle_names)
|
15
|
+
options = pickle_names.extract_options!
|
16
|
+
resources = pickle_names.map{|n| model(n) || n.to_sym}
|
17
|
+
if options[:extra]
|
18
|
+
parts = options[:extra].underscore.gsub(' ','_').split("_")
|
19
|
+
find_pickle_path_using_action_segment_combinations(resources, parts)
|
20
|
+
else
|
21
|
+
pickle_path_for_resources_action_segment(resources, options[:action], options[:segment])
|
22
|
+
end or raise "Could not figure out a path for #{pickle_names.inspect} #{options.inspect}"
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
def find_pickle_path_using_action_segment_combinations(resources, parts)
|
27
|
+
path = nil
|
28
|
+
(0..parts.length).each do |idx|
|
29
|
+
action = parts.slice(0, idx).join('_')
|
30
|
+
segment = parts.slice(idx, parts.length).join('_')
|
31
|
+
path = pickle_path_for_resources_action_segment(resources, action, segment) and break
|
32
|
+
end
|
33
|
+
path
|
34
|
+
end
|
35
|
+
|
36
|
+
def pickle_path_for_resources_action_segment(resources, action, segment)
|
37
|
+
action.blank? or action = action.downcase.gsub(' ','_')
|
38
|
+
segment.blank? or segment = segment.downcase.gsub(' ','_')
|
39
|
+
resource_names = resources.map{|s| s.is_a?(Symbol) ? s.to_s : s.class.name.underscore}.join("_")
|
40
|
+
models = resources.reject{|s| s.is_a?(Symbol)}
|
41
|
+
parts = [action, resource_names, segment].reject(&:blank?)
|
42
|
+
send("#{parts.join('_')}_path", *models) rescue nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Pickle
|
2
|
+
module Session
|
3
|
+
# add ability to parse model names as fields, using a session
|
4
|
+
module Parser
|
5
|
+
def self.included(parser_class)
|
6
|
+
parser_class.alias_method_chain :parse_field, :model
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :session
|
10
|
+
|
11
|
+
def match_field
|
12
|
+
"(?:\\w+: (?:#{match_model}|#{match_value}))"
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse_field_with_model(field)
|
16
|
+
if session && field =~ /^(\w+): #{capture_model}$/
|
17
|
+
{$1 => session.model($2)}
|
18
|
+
else
|
19
|
+
parse_field_without_model(field)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_hash(hash)
|
24
|
+
hash.inject({}) do |parsed, (key, val)|
|
25
|
+
if session && val =~ /^#{capture_model}$/
|
26
|
+
parsed.merge(key => session.model($1))
|
27
|
+
else
|
28
|
+
parsed.merge(key => val)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|