slurper 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,58 @@
1
+ = slurper
2
+
3
+ Slurper allows you to quickly compose your stories in a text file and import them into Pivotal Tracker.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Setup
16
+
17
+ gem install hashrocket-slurper
18
+
19
+ == Config
20
+
21
+ Create a story_defaults.yml file in your working directory for project stories.
22
+
23
+ === Example
24
+
25
+ project_id: 1234
26
+ token: 123abc123abc123abc
27
+ name: Untitled
28
+ description: "In order to \nAs a \nI want \n\nAcceptance:\n* "
29
+ requested_by: Jane Stakeholder
30
+ labels: slurper
31
+
32
+ The project_id tells tracker which project to add your stories to. It can be found on the project settings or the url for the project.
33
+
34
+ The token can be found on your personal profile page in Pivotal Tracker.
35
+
36
+ The name, labels and description fields provide slurper with default values for story titles and descriptoins if you don't provide them in your stories.txt file.
37
+
38
+ The requested_by field should be the name of your project stakeholder exactly as it appears in tracker.
39
+
40
+ == Usage
41
+
42
+ Create a stories.txt file and compose your stories in the slurper story format.
43
+
44
+ === Example
45
+
46
+ ==
47
+ name
48
+ User Create Something
49
+ description
50
+ In order to accomplish some business value
51
+ As a User
52
+ I want to create something
53
+
54
+ labels
55
+ things
56
+ ==
57
+
58
+ Note: stories must have == before and after each story.
data/bin/slurp ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Slurps stories from the given file (stories.txt by default) and creates
4
+ # Pivotal Tracker stories from them. Useful during story carding sessions
5
+ # when you want to capture a number of stories quickly without clicking
6
+ # your way through the Tracker UI.
7
+
8
+ # Note that if you include labels in stories.txt, they don't appear
9
+ # immediately in Tracker. You'll have to refresh Tracker after a few seconds
10
+ # to see them.
11
+
12
+ $:.unshift(File.join(File.dirname(File.dirname(__FILE__)),'lib'))
13
+ require 'rubygems'
14
+ require 'slurper'
15
+ require 'optparse'
16
+
17
+ options = {}
18
+ OptionParser.new do |opts|
19
+ opts.on("-r", "--reverse", "Reverse story creation order") do |v|
20
+ options[:reverse] = v
21
+ end
22
+ end.parse!
23
+
24
+ story_file = ARGV.empty? ? "stories.txt" : ARGV[0]
25
+
26
+ story_lines = Array.new
27
+ stories = Array.new
28
+ IO.foreach(story_file) do |line|
29
+ if line[0,2] != "=="
30
+ story_lines << line
31
+ else
32
+ stories << Story.new.parse(story_lines)
33
+ story_lines.clear
34
+ end
35
+ end
36
+
37
+ stories.reverse! unless options[:reverse]
38
+
39
+ stories.each { |story| story.save }
data/lib/slurper.rb ADDED
@@ -0,0 +1 @@
1
+ require 'story'
data/lib/story.rb ADDED
@@ -0,0 +1,70 @@
1
+ require 'activeresource'
2
+
3
+ class Story < ActiveResource::Base
4
+
5
+ @@defaults = YAML.load_file('story_defaults.yml')
6
+ self.site = "http://www.pivotaltracker.com/services/v2/projects/#{@@defaults['project_id']}"
7
+ headers['X-TrackerToken'] = @@defaults.delete("token")
8
+ attr_accessor :story_lines
9
+
10
+ def initialize(attributes = {})
11
+ @attributes = {}
12
+ @prefix_options = {}
13
+ load(@@defaults.merge(attributes))
14
+ end
15
+
16
+ def parse(story_lines)
17
+ @story_lines = story_lines
18
+ parse_name
19
+ parse_description
20
+ parse_labels
21
+ self
22
+ end
23
+
24
+ private
25
+
26
+ def parse_name
27
+ @story_lines.each_with_index do |line, i|
28
+ if start_of_value?(line, 'name')
29
+ if starts_with_whitespace?(@story_lines[i+1])
30
+ @attributes["name"] = @story_lines[i+1].strip
31
+ else
32
+ @attributes.delete("name")
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def parse_description
39
+ @story_lines.each_with_index do |line, i|
40
+ if start_of_value?(line, 'description')
41
+ desc = Array.new
42
+ while((next_line = @story_lines[i+=1]) && starts_with_whitespace?(next_line)) do
43
+ desc << next_line
44
+ end
45
+ desc.empty? ? @attributes.delete("description") : @attributes["description"] = desc.join.gsub(/^ +/, "").gsub(/^\t+/, "")
46
+ end
47
+ end
48
+ end
49
+
50
+ def parse_labels
51
+ @story_lines.each_with_index do |line, i|
52
+ if start_of_value?(line, 'labels')
53
+ if starts_with_whitespace?(@story_lines[i+1])
54
+ @attributes["labels"] = @story_lines[i+1].strip
55
+ else
56
+ @attributes.delete("labels")
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def starts_with_whitespace?(line)
63
+ line && line[0,1] =~ /\s/
64
+ end
65
+
66
+ def start_of_value?(line, attribute)
67
+ line[0,attribute.size] == attribute
68
+ end
69
+
70
+ end
@@ -0,0 +1,95 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'story'
4
+
5
+ describe Story do
6
+
7
+ it ".parse should return a reference to the story" do
8
+ story = Story.new
9
+ story.parse("").should == story
10
+ end
11
+
12
+ context "deals with leading/trailing whitespace" do
13
+ before do
14
+ story_lines = IO.readlines(File.dirname(__FILE__) + "/whitespacey_story.txt")
15
+ @story = Story.new.parse(story_lines)
16
+ end
17
+
18
+ it "strips whitespace from the name" do
19
+ @story.name.should == "Profit"
20
+ end
21
+
22
+ it "strips whitespace from the description" do
23
+ @story.description.should == "In order to do something\nAs a role\nI want to click a thingy\n"
24
+ end
25
+ end
26
+
27
+ context "given values for all attributes" do
28
+ before do
29
+ story_lines = IO.readlines(File.dirname(__FILE__) + "/full_story.txt")
30
+ @story = Story.new.parse(story_lines)
31
+ end
32
+
33
+ it "parses the name correctly" do
34
+ @story.name.should == "Profit"
35
+ end
36
+
37
+ it "parses the description correctly" do
38
+ @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"
39
+ end
40
+
41
+ it "parses the label correctly" do
42
+ @story.labels.should == "money,power,fame"
43
+ end
44
+ end
45
+
46
+ context "given only a name" do
47
+ before do
48
+ story_lines = IO.readlines(File.dirname(__FILE__) + "/name_only.txt")
49
+ @story = Story.new.parse(story_lines)
50
+ end
51
+
52
+ it "should parse the name correctly" do
53
+ @story.name.should == "Profit"
54
+ end
55
+
56
+ it "should use the default value for the description" do
57
+ @story.description.should == "In order to \nAs a \nI want \n\nAcceptance:\n* "
58
+ end
59
+
60
+ it "should use the default value for the labels" do
61
+ @story.labels.should == "slurper"
62
+ end
63
+ end
64
+
65
+ context "given empty attributes" do
66
+ before do
67
+ story_lines = IO.readlines(File.dirname(__FILE__) + "/empty_attributes.txt")
68
+ @story = Story.new.parse(story_lines)
69
+ end
70
+
71
+ it "should not set any name" do
72
+ @story.attributes.keys.should_not include("name")
73
+ end
74
+
75
+ it "should not set any description" do
76
+ @story.attributes.keys.should_not include("description")
77
+ end
78
+
79
+ it "should not set any labels" do
80
+ @story.attributes.keys.should_not include("labels")
81
+ end
82
+ end
83
+
84
+ context "given attributes with spaces" do
85
+ before do
86
+ story_lines = IO.readlines(File.dirname(__FILE__) + "/quoted_attributes.txt")
87
+ @story = Story.new.parse(story_lines)
88
+ end
89
+
90
+ it "should set the description correctly" do
91
+ @story.description.should == "I have a \"quote\"\n"
92
+ end
93
+ end
94
+
95
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slurper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Wes Gibbs
8
+ - Adam Lowe
9
+ - Stephen Caudill
10
+ - Tim Pope
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+
15
+ date: 2009-12-16 00:00:00 -05:00
16
+ default_executable: slurp
17
+ dependencies:
18
+ - !ruby/object:Gem::Dependency
19
+ name: rspec
20
+ type: :development
21
+ version_requirement:
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.2.9
27
+ version:
28
+ description: "\n Slurps stories from the given file (stories.txt by default) and creates\n Pivotal Tracker stories from them. Useful during story carding sessions\n when you want to capture a number of stories quickly without clicking\n your way through the Tracker UI.\n "
29
+ email: info@hashrocket.com
30
+ executables:
31
+ - slurp
32
+ extensions: []
33
+
34
+ extra_rdoc_files:
35
+ - README.rdoc
36
+ files:
37
+ - bin/slurp
38
+ - lib/slurper.rb
39
+ - lib/story.rb
40
+ - README.rdoc
41
+ has_rdoc: true
42
+ homepage: http://github.com/hashrocket/slurper
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --charset=UTF-8
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project:
65
+ rubygems_version: 1.3.5
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: takes a formatted story file and puts it on Pivotal Tracker
69
+ test_files:
70
+ - spec/story_spec.rb