eslurper 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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