eslurper 1.0.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/lib/slurper.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'yaml'
2
+ require 'story'
3
+
4
+ YAML::ENGINE.yamler='syck' if RUBY_VERSION > '1.9'
5
+
6
+
7
+ class Slurper
8
+
9
+ attr_accessor :story_file, :stories
10
+
11
+ def self.slurp(story_file, reverse)
12
+ slurper = new(story_file)
13
+ slurper.load_stories
14
+ slurper.prepare_stories
15
+ slurper.sort_stories(reverse)
16
+ slurper.create_stories
17
+ end
18
+
19
+ def initialize(story_file)
20
+ self.story_file = story_file
21
+ end
22
+
23
+ def load_stories
24
+ self.stories = YAML.load(yamlize_story_file)
25
+ end
26
+
27
+ def prepare_stories
28
+ stories.each { |story| story.prepare }
29
+ end
30
+
31
+ def sort_stories(reverse)
32
+ stories.sort!
33
+ stories.reverse! unless reverse
34
+ end
35
+
36
+ def create_stories
37
+ puts "Preparing to slurp #{stories.size} stories into Tracker..."
38
+ stories.each_with_index do |story, index|
39
+ begin
40
+ if story.save
41
+ puts "#{index+1}. #{story.name}"
42
+ else
43
+ puts "Slurp failed. #{story.errors.full_messages}"
44
+ end
45
+ rescue ActiveResource::ConnectionError => e
46
+ msg = "Slurp failed on story "
47
+ if story.attributes["name"]
48
+ msg << story.attributes["name"]
49
+ else
50
+ msg << "##{options[:reverse] ? index + 1 : stories.size - index }"
51
+ end
52
+ puts msg + ". Error: #{e}"
53
+ end
54
+ end
55
+ end
56
+
57
+ protected
58
+
59
+ def yamlize_story_file
60
+ IO.read(story_file).
61
+ gsub(/^/, " ").
62
+ gsub(/ ==.*/, "- !ruby/object:Story\n attributes:").
63
+ gsub(/ description:$/, " description: |")
64
+ end
65
+
66
+ end
data/lib/story.rb ADDED
@@ -0,0 +1,98 @@
1
+ require 'active_resource'
2
+
3
+ class Story < ActiveResource::Base
4
+ include Comparable
5
+
6
+ def self.yaml
7
+ YAML.load_file('slurper_config.yml')
8
+ end
9
+
10
+ def self.config(project_id='project_id')
11
+ @@config = yaml
12
+ scheme = if !!@@config['ssl']
13
+ self.ssl_options = { :verify_mode => OpenSSL::SSL::VERIFY_PEER,
14
+ :ca_file => File.join(File.dirname(__FILE__), "cacert.pem") }
15
+ "https"
16
+ else
17
+ "http"
18
+ end
19
+ self.site = "#{scheme}://www.pivotaltracker.com/services/v3/projects/:project_id"
20
+ # self.site = "#{scheme}://www.pivotaltracker.com/services/v3/projects/#{@@config[project_id]}"
21
+ @@config
22
+ end
23
+
24
+ headers['X-TrackerToken'] = config.delete("token")
25
+ headers['Accept-Encoding'] = '*/*'
26
+ self.format = ActiveResource::Formats::XmlFormat
27
+
28
+ def prepare
29
+ scrub_description
30
+ scrub_labels
31
+ default_requested_by
32
+ self.description += "\n" + "Label Search\n" + label_search_url + "\n\n" + story_links.join("\n") if epic_story?
33
+ end
34
+
35
+ def epic_story?
36
+ respond_to?(:labels) ? self.labels.split(',').include?("epic") : false
37
+ end
38
+
39
+ def feature_labels
40
+ self.labels.split(',').find { |label| label != "epic" }
41
+ end
42
+
43
+ def save
44
+ @config ||= Story.yaml
45
+ self.prefix_options = {:project_id => @config['project_id']}
46
+ super
47
+ end
48
+
49
+ def <=>(another_story)
50
+ if self.epic_story? && another_story.epic_story?
51
+ 0
52
+ elsif self.epic_story? && !another_story.epic_story?
53
+ -1
54
+ elsif !self.epic_story? && !another_story.epic_story?
55
+ 0
56
+ else
57
+ 1
58
+ end
59
+ end
60
+
61
+ protected
62
+
63
+ def label_search_url
64
+ @config ||= Story.yaml
65
+ "https://www.pivotaltracker.com/projects/#{@config['epic_features_project_id']}?label=" + feature_labels
66
+ end
67
+
68
+ def story_links
69
+ retrieve_user_stories.map { |story| story.url }
70
+ end
71
+
72
+ def retrieve_user_stories
73
+ @config ||= Story.yaml
74
+ stories = Story.find(:all, :params => {:filter => 'label:' + feature_labels, :project_id => @config['epic_features_project_id']})
75
+ end
76
+
77
+ def scrub_description
78
+ if respond_to?(:description)
79
+ self.description = description.gsub(" ", "").gsub(" \n", "\n")
80
+ end
81
+ if respond_to?(:description) && description == ""
82
+ self.attributes["description"] = nil
83
+ end
84
+ end
85
+
86
+ def scrub_labels
87
+ if respond_to?(:labels)
88
+ self.labels = labels.gsub(" ", "").gsub(" \n", "\n")
89
+ end
90
+ end
91
+
92
+ def default_requested_by
93
+ if (!respond_to?(:requested_by) || requested_by == "") && Story.config["requested_by"]
94
+ self.attributes["requested_by"] = Story.config["requested_by"]
95
+ end
96
+ end
97
+
98
+ end
@@ -0,0 +1,79 @@
1
+ require 'rubygems'
2
+ require 'slurper'
3
+
4
+ describe Slurper do
5
+
6
+ context "deals with leading/trailing whitespace" do
7
+ before do
8
+ slurper = Slurper.new(File.join(File.dirname(__FILE__), "fixtures", "whitespacey_story.slurper"))
9
+ slurper.load_stories
10
+ @story = slurper.stories.first
11
+ end
12
+
13
+ it "strips whitespace from the name" do
14
+ @story.name.should == "Profit"
15
+ end
16
+ end
17
+
18
+ context "given an epic feature" do
19
+ before do
20
+ slurper = Slurper.new(File.join(File.dirname(__FILE__), "fixtures", "epic_story.slurper"))
21
+ slurper.load_stories
22
+ @story = slurper.stories.first
23
+ end
24
+
25
+ it "knows it's an epic story" do
26
+ @story.epic_story? == true
27
+ end
28
+ end
29
+
30
+ context "given values for all attributes" do
31
+ before do
32
+ slurper = Slurper.new(File.join(File.dirname(__FILE__), "fixtures", "full_story.slurper"))
33
+ slurper.load_stories
34
+ @story = slurper.stories.first
35
+ end
36
+
37
+ it "parses the name correctly" do
38
+ @story.name.should == "Profit"
39
+ end
40
+
41
+ it "parses the label correctly" do
42
+ @story.labels.should == "money,power,fame"
43
+ end
44
+
45
+ it "parses the story type correctly" do
46
+ @story.story_type.should == "feature"
47
+ end
48
+ end
49
+
50
+ context "given only a name" do
51
+ before do
52
+ slurper = Slurper.new(File.join(File.dirname(__FILE__), "fixtures", "name_only.slurper"))
53
+ slurper.load_stories
54
+ @story = slurper.stories.first
55
+ end
56
+
57
+ it "should parse the name correctly" do
58
+ @story.name.should == "Profit"
59
+ end
60
+
61
+ end
62
+
63
+ context "given empty attributes" do
64
+ before do
65
+ slurper = Slurper.new(File.join(File.dirname(__FILE__), "fixtures", "empty_attributes.slurper"))
66
+ slurper.load_stories
67
+ @story = slurper.stories.first
68
+ end
69
+
70
+ it "should not set any name" do
71
+ @story.name.should be_nil
72
+ end
73
+
74
+ it "should not set any labels" do
75
+ @story.labels.should be_nil
76
+ end
77
+ end
78
+
79
+ end
@@ -0,0 +1,110 @@
1
+ require 'rubygems'
2
+ require 'slurper'
3
+
4
+ describe Story do
5
+
6
+ context "#prepare" do
7
+ it "scrubs the description" do
8
+ story = Story.new
9
+ story.stub!(:default_requested_by)
10
+ story.should_receive(:scrub_description)
11
+ story.prepare
12
+ end
13
+
14
+ it "scrubs the defaults the requested_by attribute" do
15
+ story = Story.new
16
+ story.stub!(:scrub_description)
17
+ story.should_receive(:default_requested_by)
18
+ story.prepare
19
+ end
20
+
21
+ it "uses http by default" do
22
+ Story.site.scheme.should == "http"
23
+ Story.yaml['ssl'].should be_nil
24
+ end
25
+
26
+ it "uses https if set in the config" do
27
+ Story.stub(:yaml => {"ssl" => true})
28
+ Story.config['ssl'].should be_true
29
+ Story.site.scheme.should == "https"
30
+ Story.ssl_options[:verify_mode].should == 1
31
+
32
+ # Not sure what this next line is testing
33
+ File.open(File.expand_path('lib/cacert.pem')).readlines.find_all{ |l| l.starts_with?("Equifax") }.count.should == 4
34
+ end
35
+ end
36
+
37
+ context "requested_by attribute" do
38
+ it "uses the default if not given one" do
39
+ Story.stub!(:config).and_return({"requested_by" => "Mr. Client"})
40
+ story = Story.new
41
+ story.send(:default_requested_by)
42
+ story.requested_by.should == "Mr. Client"
43
+ end
44
+
45
+ it "uses the default if given a blank requested_by" do
46
+ Story.stub!(:config).and_return({"requested_by" => "Mr. Client"})
47
+ story = Story.new(:requested_by => "")
48
+ story.send(:default_requested_by)
49
+ story.requested_by.should == "Mr. Client"
50
+ end
51
+
52
+ it "uses the name given in the story file if there is one" do
53
+ Story.stub!(:config).and_return({"requested_by" => "Mr. Client"})
54
+ story = Story.new(:requested_by => "Mr. Stakeholder")
55
+ story.send(:default_requested_by)
56
+ story.requested_by.should == "Mr. Stakeholder"
57
+ end
58
+ end
59
+
60
+ context "scrubs the descriptions correctly" do
61
+ it "when the description is blank" do
62
+ story = Story.new(:description => "")
63
+ story.send(:scrub_description)
64
+ story.description.should be_nil
65
+ end
66
+
67
+ it "when there is no description given" do
68
+ story = Story.new
69
+ lambda {
70
+ story.send(:scrub_description)
71
+ }.should_not raise_error
72
+ end
73
+
74
+ it "when it contains quotes" do
75
+ desc = <<-STRING
76
+ I have a "quote"
77
+ STRING
78
+ story = Story.new(:description => desc)
79
+ story.send(:scrub_description)
80
+ story.description.should == "I have a \"quote\"\n"
81
+ end
82
+
83
+ it "when it is full of whitespace" do
84
+ desc = <<-STRING
85
+ In order to do something
86
+ As a role
87
+ I want to click a thingy
88
+ STRING
89
+ story = Story.new(:description => desc)
90
+ story.send(:scrub_description)
91
+ story.description.should == "In order to do something\nAs a role\nI want to click a thingy\n"
92
+ end
93
+
94
+ it "when it contains acceptance criteria" do
95
+ desc = <<-STRING
96
+ In order to do something
97
+ As a role
98
+ I want to click a thingy
99
+
100
+ Acceptance:
101
+ - do the thing
102
+ - don't forget the other thing
103
+ STRING
104
+ story = Story.new(:description => desc)
105
+ story.send(:scrub_description)
106
+ story.description.should == "In order to do something\nAs a role\nI want to click a thingy\n\nAcceptance:\n- do the thing\n- don't forget the other thing\n"
107
+ end
108
+ end
109
+
110
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eslurper
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Wes Gibbs
9
+ - Adam Lowe
10
+ - Stephen Caudill
11
+ - Tim Pope
12
+ - Jeffrey Walter
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+ date: 2011-11-21 00:00:00.000000000Z
17
+ dependencies:
18
+ - !ruby/object:Gem::Dependency
19
+ name: activeresource
20
+ requirement: &70340789669980 !ruby/object:Gem::Requirement
21
+ none: false
22
+ requirements:
23
+ - - ~>
24
+ - !ruby/object:Gem::Version
25
+ version: 3.1.2
26
+ type: :runtime
27
+ prerelease: false
28
+ version_requirements: *70340789669980
29
+ - !ruby/object:Gem::Dependency
30
+ name: rspec
31
+ requirement: &70340789669360 !ruby/object:Gem::Requirement
32
+ none: false
33
+ requirements:
34
+ - - ~>
35
+ - !ruby/object:Gem::Version
36
+ version: 1.3.0
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: *70340789669360
40
+ - !ruby/object:Gem::Dependency
41
+ name: ruby-debug19
42
+ requirement: &70340789668760 !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 0.11.6
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: *70340789668760
51
+ - !ruby/object:Gem::Dependency
52
+ name: configuration
53
+ requirement: &70340789668160 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ~>
57
+ - !ruby/object:Gem::Version
58
+ version: 1.2.0
59
+ type: :development
60
+ prerelease: false
61
+ version_requirements: *70340789668160
62
+ description: ! "\n Slurps stories from the given file (stories.slurper by default)
63
+ and creates Pivotal Tracker stories from them. Useful during story carding sessions
64
+ when you want to capture a number of stories quickly without clicking your way through
65
+ the Tracker UI.\n "
66
+ email: jeffrey.walter@ericsson.com
67
+ executables:
68
+ - eslurp
69
+ extensions: []
70
+ extra_rdoc_files:
71
+ - README.rdoc
72
+ files:
73
+ - bin/eslurp
74
+ - lib/slurper.rb
75
+ - lib/story.rb
76
+ - lib/cacert.pem
77
+ - README.rdoc
78
+ - spec/slurper_spec.rb
79
+ - spec/story_spec.rb
80
+ homepage: http://github.com/jeffreywalter/slurper
81
+ licenses: []
82
+ post_install_message:
83
+ rdoc_options:
84
+ - --charset=UTF-8
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: 1.3.6
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 1.8.10
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: takes a formatted story file and puts it on Pivotal Tracker
105
+ test_files:
106
+ - spec/slurper_spec.rb
107
+ - spec/story_spec.rb