slurper 0.1.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/README.rdoc +58 -0
- data/bin/slurp +39 -0
- data/lib/slurper.rb +1 -0
- data/lib/story.rb +70 -0
- data/spec/story_spec.rb +95 -0
- metadata +70 -0
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
|
data/spec/story_spec.rb
ADDED
@@ -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
|