pickle 0.1.16

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.
Files changed (60) hide show
  1. data/.gitignore +2 -0
  2. data/History.txt +165 -0
  3. data/License.txt +20 -0
  4. data/README.rdoc +205 -0
  5. data/Rakefile +116 -0
  6. data/Todo.txt +4 -0
  7. data/VERSION +1 -0
  8. data/features/app/app.rb +112 -0
  9. data/features/app/blueprints.rb +11 -0
  10. data/features/app/factories.rb +23 -0
  11. data/features/app/views/notifier/email.erb +1 -0
  12. data/features/app/views/notifier/user_email.erb +6 -0
  13. data/features/email/email.feature +38 -0
  14. data/features/generator/generators.feature +54 -0
  15. data/features/path/models_page.feature +44 -0
  16. data/features/path/named_route_page.feature +10 -0
  17. data/features/pickle/create_from_active_record.feature +20 -0
  18. data/features/pickle/create_from_factory_girl.feature +43 -0
  19. data/features/pickle/create_from_machinist.feature +38 -0
  20. data/features/step_definitions/email_steps.rb +50 -0
  21. data/features/step_definitions/extra_email_steps.rb +7 -0
  22. data/features/step_definitions/fork_steps.rb +4 -0
  23. data/features/step_definitions/generator_steps.rb +42 -0
  24. data/features/step_definitions/path_steps.rb +14 -0
  25. data/features/step_definitions/pickle_steps.rb +41 -0
  26. data/features/support/env.rb +34 -0
  27. data/features/support/paths.rb +46 -0
  28. data/garlic.rb +36 -0
  29. data/init.rb +0 -0
  30. data/lib/pickle.rb +26 -0
  31. data/lib/pickle/adapter.rb +87 -0
  32. data/lib/pickle/config.rb +48 -0
  33. data/lib/pickle/email.rb +36 -0
  34. data/lib/pickle/email/parser.rb +18 -0
  35. data/lib/pickle/email/world.rb +13 -0
  36. data/lib/pickle/parser.rb +65 -0
  37. data/lib/pickle/parser/matchers.rb +87 -0
  38. data/lib/pickle/path.rb +44 -0
  39. data/lib/pickle/path/world.rb +5 -0
  40. data/lib/pickle/session.rb +151 -0
  41. data/lib/pickle/session/parser.rb +24 -0
  42. data/lib/pickle/version.rb +6 -0
  43. data/lib/pickle/world.rb +9 -0
  44. data/pickle.gemspec +107 -0
  45. data/rails_generators/pickle/pickle_generator.rb +41 -0
  46. data/rails_generators/pickle/templates/email_steps.rb +50 -0
  47. data/rails_generators/pickle/templates/env.rb +14 -0
  48. data/rails_generators/pickle/templates/paths.rb +20 -0
  49. data/rails_generators/pickle/templates/pickle_steps.rb +41 -0
  50. data/spec/lib/pickle_adapter_spec.rb +164 -0
  51. data/spec/lib/pickle_config_spec.rb +97 -0
  52. data/spec/lib/pickle_email_parser_spec.rb +49 -0
  53. data/spec/lib/pickle_email_spec.rb +131 -0
  54. data/spec/lib/pickle_parser_matchers_spec.rb +70 -0
  55. data/spec/lib/pickle_parser_spec.rb +154 -0
  56. data/spec/lib/pickle_path_spec.rb +77 -0
  57. data/spec/lib/pickle_session_spec.rb +337 -0
  58. data/spec/lib/pickle_spec.rb +24 -0
  59. data/spec/spec_helper.rb +38 -0
  60. metadata +122 -0
@@ -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
@@ -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
@@ -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}\"|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.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.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,44 @@
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
+ models = pickle_names.map{|m| model!(m)}
17
+ if options[:extra]
18
+ parts = options[:extra].underscore.gsub(' ','_').split("_")
19
+ find_pickle_path_using_action_segment_combinations(models, parts)
20
+ else
21
+ pickle_path_for_models_action_segment(models, 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(models, 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_models_action_segment(models, action, segment) and break
32
+ end
33
+ path
34
+ end
35
+
36
+ def pickle_path_for_models_action_segment(models, action, segment)
37
+ action.blank? or action = action.downcase.gsub(' ','_')
38
+ segment.blank? or segment = segment.downcase.gsub(' ','_')
39
+ model_names = models.map{|m| m.class.name.underscore}.join("_")
40
+ parts = [action, model_names, segment].reject(&:blank?)
41
+ send("#{parts.join('_')}_path", *models) rescue nil
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ require 'pickle'
2
+ require 'pickle/path'
3
+
4
+ # make world pickle/path aware
5
+ World(Pickle::Path)
@@ -0,0 +1,151 @@
1
+ module Pickle
2
+ module Session
3
+ class << self
4
+ def included(world_class)
5
+ proxy_to_pickle_parser(world_class)
6
+ end
7
+
8
+ def extended(world_object)
9
+ proxy_to_pickle_parser(class << world_object; self; end) # metaclass is not 2.1 compatible
10
+ end
11
+
12
+ protected
13
+ def proxy_to_pickle_parser(world_class)
14
+ world_class.class_eval do
15
+ unless methods.include?('method_missing_with_pickle_parser')
16
+ alias_method_chain :method_missing, :pickle_parser
17
+ alias_method_chain :respond_to?, :pickle_parser
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def create_model(a_model_name, fields = nil)
24
+ factory, label = *parse_model(a_model_name)
25
+ raise ArgumentError, "Can't create with an ordinal (e.g. 1st user)" if label.is_a?(Integer)
26
+ record = pickle_config.factories[factory].create(parse_fields(fields))
27
+ store_model(factory, label, record)
28
+ end
29
+
30
+ def find_model(a_model_name, fields = nil)
31
+ factory, name = *parse_model(a_model_name)
32
+ raise ArgumentError, "Can't find a model with an ordinal (e.g. 1st user)" if name.is_a?(Integer)
33
+ model_class = pickle_config.factories[factory].klass
34
+ if record = model_class.find(:first, :conditions => convert_models_to_attributes(model_class, parse_fields(fields)))
35
+ store_model(factory, name, record)
36
+ end
37
+ end
38
+
39
+ def find_models(factory, fields = nil)
40
+ models_by_index(factory).clear
41
+ model_class = pickle_config.factories[factory].klass
42
+ records = model_class.find(:all, :conditions => convert_models_to_attributes(model_class, parse_fields(fields)))
43
+ records.each {|record| store_model(factory, nil, record)}
44
+ end
45
+
46
+ # return the original model stored by create_model or find_model
47
+ def created_model(name)
48
+ factory, name_or_index = *parse_model(name)
49
+
50
+ if name_or_index.blank?
51
+ models_by_index(factory).last
52
+ elsif name_or_index.is_a?(Integer)
53
+ models_by_index(factory)[name_or_index]
54
+ else
55
+ models_by_name(factory)[name_or_index] or raise "model: #{name} does not refer to known model in this scenario"
56
+ end
57
+ end
58
+
59
+ # predicate version which raises no errors
60
+ def created_model?(name)
61
+ (created_model(name) rescue nil) ? true : false
62
+ end
63
+
64
+ # return a newly selected model
65
+ def model(name)
66
+ (model = created_model(name)) && model.class.find(model.id)
67
+ end
68
+
69
+ # predicate version which raises no errors
70
+ def model?(name)
71
+ (model(name) rescue nil) ? true : false
72
+ end
73
+
74
+ # like model, but raise an error if it can't be found
75
+ def model!(name)
76
+ model(name) or raise "Can't find pickle model: '#{name}' in this scenario"
77
+ end
78
+
79
+ # like created_model, but raise an error if it can't be found
80
+ def created_model!(name)
81
+ created_model(name) or raise "Can't find pickle model: '#{name}' in this scenario"
82
+ end
83
+
84
+ # return all original models of specified type
85
+ def created_models(factory)
86
+ models_by_index(factory)
87
+ end
88
+
89
+ # return all models of specified type (freshly selected from the database)
90
+ def models(factory)
91
+ created_models(factory).map{|model| model.class.find(model.id) }
92
+ end
93
+
94
+ def respond_to_with_pickle_parser?(method, include_private = false)
95
+ respond_to_without_pickle_parser?(method, include_private) || pickle_parser.respond_to?(method, include_private)
96
+ end
97
+
98
+ protected
99
+ def method_missing_with_pickle_parser(method, *args, &block)
100
+ if pickle_parser.respond_to?(method)
101
+ pickle_parser.send(method, *args, &block)
102
+ else
103
+ method_missing_without_pickle_parser(method, *args, &block)
104
+ end
105
+ end
106
+
107
+ def pickle_parser=(parser)
108
+ parser.session = self
109
+ @pickle_parser = parser
110
+ end
111
+
112
+ def pickle_parser
113
+ @pickle_parser or self.pickle_parser = Pickle.parser
114
+ end
115
+
116
+ def pickle_config
117
+ pickle_parser.config
118
+ end
119
+
120
+ def convert_models_to_attributes(ar_class, attrs)
121
+ attrs.each do |key, val|
122
+ if val.is_a?(ActiveRecord::Base) && ar_class.column_names.include?("#{key}_id")
123
+ attrs["#{key}_id"] = val.id
124
+ attrs["#{key}_type"] = val.class.name if ar_class.column_names.include?("#{key}_type")
125
+ attrs.delete(key)
126
+ end
127
+ end
128
+ end
129
+
130
+ def models_by_name(factory)
131
+ @models_by_name ||= {}
132
+ @models_by_name[pickle_parser.canonical(factory)] ||= {}
133
+ end
134
+
135
+ def models_by_index(factory)
136
+ @models_by_index ||= {}
137
+ @models_by_index[pickle_parser.canonical(factory)] ||= []
138
+ end
139
+
140
+ # if the factory name != the model name, store under both names
141
+ def store_model(factory, name, record)
142
+ store_record(record.class.name, name, record) unless pickle_parser.canonical(factory) == pickle_parser.canonical(record.class.name)
143
+ store_record(factory, name, record)
144
+ end
145
+
146
+ def store_record(factory, name, record)
147
+ models_by_name(factory)[name] = record
148
+ models_by_index(factory) << record
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,24 @@
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
+ end
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ module Pickle
2
+ module Version
3
+ String = File.read(File.dirname(File.dirname(__FILE__)) + '/../VERSION').strip
4
+ Major, Minor, Patch = String.split('.').map{|i| i.to_i}
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ require 'pickle'
2
+
3
+ # make cucumber world pickle aware
4
+ World(Pickle::Session)
5
+
6
+ # shortcuts to regexps for use in step definition regexps
7
+ class << self
8
+ delegate :capture_model, :capture_fields, :capture_factory, :capture_plural_factory, :capture_predicate, :to => 'Pickle.parser'
9
+ end
@@ -0,0 +1,107 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{pickle}
8
+ s.version = "0.1.16"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ian White"]
12
+ s.date = %q{2009-10-15}
13
+ s.description = %q{Easy model creation and reference in your cucumber features}
14
+ s.email = %q{ian.w.white@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "History.txt",
21
+ "License.txt",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "Todo.txt",
25
+ "VERSION",
26
+ "features/app/app.rb",
27
+ "features/app/blueprints.rb",
28
+ "features/app/factories.rb",
29
+ "features/app/views/notifier/email.erb",
30
+ "features/app/views/notifier/user_email.erb",
31
+ "features/email/email.feature",
32
+ "features/generator/generators.feature",
33
+ "features/path/models_page.feature",
34
+ "features/path/named_route_page.feature",
35
+ "features/pickle/create_from_active_record.feature",
36
+ "features/pickle/create_from_factory_girl.feature",
37
+ "features/pickle/create_from_machinist.feature",
38
+ "features/step_definitions/email_steps.rb",
39
+ "features/step_definitions/extra_email_steps.rb",
40
+ "features/step_definitions/fork_steps.rb",
41
+ "features/step_definitions/generator_steps.rb",
42
+ "features/step_definitions/path_steps.rb",
43
+ "features/step_definitions/pickle_steps.rb",
44
+ "features/support/env.rb",
45
+ "features/support/paths.rb",
46
+ "garlic.rb",
47
+ "init.rb",
48
+ "lib/pickle.rb",
49
+ "lib/pickle/adapter.rb",
50
+ "lib/pickle/config.rb",
51
+ "lib/pickle/email.rb",
52
+ "lib/pickle/email/parser.rb",
53
+ "lib/pickle/email/world.rb",
54
+ "lib/pickle/parser.rb",
55
+ "lib/pickle/parser/matchers.rb",
56
+ "lib/pickle/path.rb",
57
+ "lib/pickle/path/world.rb",
58
+ "lib/pickle/session.rb",
59
+ "lib/pickle/session/parser.rb",
60
+ "lib/pickle/version.rb",
61
+ "lib/pickle/world.rb",
62
+ "pickle.gemspec",
63
+ "rails_generators/pickle/pickle_generator.rb",
64
+ "rails_generators/pickle/templates/email_steps.rb",
65
+ "rails_generators/pickle/templates/env.rb",
66
+ "rails_generators/pickle/templates/paths.rb",
67
+ "rails_generators/pickle/templates/pickle_steps.rb",
68
+ "spec/lib/pickle_adapter_spec.rb",
69
+ "spec/lib/pickle_config_spec.rb",
70
+ "spec/lib/pickle_email_parser_spec.rb",
71
+ "spec/lib/pickle_email_spec.rb",
72
+ "spec/lib/pickle_parser_matchers_spec.rb",
73
+ "spec/lib/pickle_parser_spec.rb",
74
+ "spec/lib/pickle_path_spec.rb",
75
+ "spec/lib/pickle_session_spec.rb",
76
+ "spec/lib/pickle_spec.rb",
77
+ "spec/spec_helper.rb"
78
+ ]
79
+ s.homepage = %q{http://github.com/ianwhite/pickle/tree}
80
+ s.rdoc_options = ["--charset=UTF-8"]
81
+ s.require_paths = ["lib"]
82
+ s.rubyforge_project = %q{pickle}
83
+ s.rubygems_version = %q{1.3.4}
84
+ s.summary = %q{Easy model creation and reference in your cucumber features}
85
+ s.test_files = [
86
+ "spec/lib/pickle_adapter_spec.rb",
87
+ "spec/lib/pickle_config_spec.rb",
88
+ "spec/lib/pickle_email_parser_spec.rb",
89
+ "spec/lib/pickle_email_spec.rb",
90
+ "spec/lib/pickle_parser_matchers_spec.rb",
91
+ "spec/lib/pickle_parser_spec.rb",
92
+ "spec/lib/pickle_path_spec.rb",
93
+ "spec/lib/pickle_session_spec.rb",
94
+ "spec/lib/pickle_spec.rb",
95
+ "spec/spec_helper.rb"
96
+ ]
97
+
98
+ if s.respond_to? :specification_version then
99
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
100
+ s.specification_version = 3
101
+
102
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
103
+ else
104
+ end
105
+ else
106
+ end
107
+ end