slurper 0.2.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +58 -17
- data/bin/slurp +6 -16
- data/lib/slurper.rb +57 -0
- data/lib/story.rb +2 -56
- data/spec/{story_spec.rb → slurper_spec.rb} +25 -21
- metadata +3 -3
data/README.rdoc
CHANGED
@@ -12,7 +12,7 @@ Works great with slurper.vim! (http://github.com/alowe/vim-slurper)
|
|
12
12
|
future version unintentionally.
|
13
13
|
* Commit, do not mess with rakefile, version, or history.
|
14
14
|
(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)
|
15
|
-
* Send me a pull request.
|
15
|
+
* Send me a pull request.
|
16
16
|
|
17
17
|
== Setup
|
18
18
|
|
@@ -20,7 +20,7 @@ Works great with slurper.vim! (http://github.com/alowe/vim-slurper)
|
|
20
20
|
|
21
21
|
== Config
|
22
22
|
|
23
|
-
|
23
|
+
Slurper requires a slurper_config.yml file in your working directory. This file contains your Tracker API and story requestor information.
|
24
24
|
|
25
25
|
=== Example
|
26
26
|
|
@@ -32,31 +32,72 @@ The project_id tells tracker which project to add your stories to. It can be fou
|
|
32
32
|
|
33
33
|
The token can be found on your personal profile page in Pivotal Tracker.
|
34
34
|
|
35
|
-
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.slurper file.
|
36
|
-
|
37
35
|
The requested_by field should be the name of your project stakeholder exactly as it appears in tracker.
|
38
36
|
|
39
37
|
== Usage
|
40
38
|
|
41
|
-
Create a stories.slurper file and compose your stories in the slurper story format. In your working directory use the slurp command to import your stories from the stories.slurper file into Pivotal Tracker.
|
39
|
+
Create a stories.slurper file and compose your stories in the slurper story format. In your working directory use the slurp command to import your stories from the stories.slurper file into Pivotal Tracker. Slurper looks for a stories.slurper file in your current directory by default, however, you can provide an alternate story source file if necessary.
|
42
40
|
|
43
|
-
|
41
|
+
Default
|
44
42
|
|
45
|
-
|
43
|
+
$slurp ~/stories.slurper
|
44
|
+
|
45
|
+
Also valid
|
46
|
+
|
47
|
+
$slurp ~/special_stories.slurper
|
48
|
+
|
49
|
+
Or even
|
50
|
+
|
51
|
+
$slurp ~/mystories.txt
|
46
52
|
|
53
|
+
=== Example stories.slurper
|
54
|
+
|
55
|
+
==
|
56
|
+
story_type:
|
57
|
+
chore
|
58
|
+
name:
|
59
|
+
Set Up Staging Environment
|
60
|
+
description:
|
61
|
+
Set up and configure staging environment for approval of stories
|
62
|
+
|
63
|
+
labels:
|
64
|
+
|
47
65
|
==
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
66
|
+
story_type:
|
67
|
+
feature
|
68
|
+
name:
|
69
|
+
Campaign Manager Does Something
|
70
|
+
description:
|
71
|
+
In order to get some value
|
72
|
+
As a campaign manager
|
73
|
+
I want to do something
|
74
|
+
|
75
|
+
- can do something
|
76
|
+
|
77
|
+
labels:
|
78
|
+
campaign managers
|
57
79
|
==
|
80
|
+
story_type:
|
81
|
+
release
|
82
|
+
name:
|
83
|
+
Big Release
|
84
|
+
description:
|
85
|
+
This release marks a lot of awesome functionality
|
86
|
+
|
87
|
+
labels:
|
88
|
+
|
89
|
+
==
|
90
|
+
story_type:
|
91
|
+
bug
|
92
|
+
name:
|
93
|
+
I did something and nothing happened
|
94
|
+
description:
|
95
|
+
When I do something, another thing is supposed to happen but I see an error screen instead.
|
96
|
+
|
97
|
+
labels:
|
98
|
+
|
58
99
|
|
59
|
-
Note:
|
100
|
+
Note: the story source file is whitespace-sensitive. Be sure the value for each key phrase is indented with two spaces beneath each key phrase. Also, start each story with a double-equals on its own line.
|
60
101
|
|
61
102
|
|
62
103
|
Credit - Wes Gibbs (http://wgibbs.github.com) thought of and wrote slurper as a ruby script. It was later packaged and released as a gem by his fellow Rocketeers after using it and finding it extremely handy.
|
data/bin/slurp
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
# Slurps stories from the given file (stories.
|
3
|
+
# Slurps stories from the given file (stories.slurper by default) and creates
|
4
4
|
# Pivotal Tracker stories from them. Useful during story carding sessions
|
5
5
|
# when you want to capture a number of stories quickly without clicking
|
6
6
|
# your way through the Tracker UI.
|
7
7
|
|
8
|
-
#
|
8
|
+
# Default story values and API token information should be provided in a
|
9
|
+
# ~/.slurper.yml file.
|
10
|
+
|
11
|
+
# Note that if you include labels in stories.slurper, they don't appear
|
9
12
|
# immediately in Tracker. You'll have to refresh Tracker after a few seconds
|
10
13
|
# to see them.
|
11
14
|
|
@@ -23,17 +26,4 @@ end.parse!
|
|
23
26
|
|
24
27
|
story_file = ARGV.empty? ? "stories.slurper" : ARGV[0]
|
25
28
|
|
26
|
-
|
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 }
|
29
|
+
Slurper.slurp(story_file, options[:reverse])
|
data/lib/slurper.rb
CHANGED
@@ -1 +1,58 @@
|
|
1
1
|
require 'story'
|
2
|
+
|
3
|
+
class Slurper
|
4
|
+
attr_accessor :story_file, :stories
|
5
|
+
|
6
|
+
def self.slurp(story_file, reverse = true)
|
7
|
+
slurper = new(story_file)
|
8
|
+
slurper.yamlize_story_file
|
9
|
+
slurper.stories.reverse! if reverse
|
10
|
+
slurper.create_stories
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(story_file)
|
14
|
+
self.story_file = story_file
|
15
|
+
end
|
16
|
+
|
17
|
+
def yamlize_story_file
|
18
|
+
self.stories = YAML.load(
|
19
|
+
IO.read(story_file).
|
20
|
+
gsub(/^/, " ").
|
21
|
+
gsub(/ ==.*/, "- !ruby/object:Story\n attributes:").
|
22
|
+
gsub(/ description:$/, " description: |")
|
23
|
+
)
|
24
|
+
scrub_descriptions
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_stories
|
28
|
+
puts "Preparing to slurp #{stories.size} stories into Tracker..."
|
29
|
+
stories.each_with_index do |story, index|
|
30
|
+
begin
|
31
|
+
story.save
|
32
|
+
puts "#{index+1}. #{story.name}"
|
33
|
+
rescue ActiveResource::ServerError, ActiveResource::ResourceNotFound => e
|
34
|
+
msg = "Slurp failed on story "
|
35
|
+
if story.attributes["name"]
|
36
|
+
msg << story.attributes["name"]
|
37
|
+
else
|
38
|
+
msg << "##{options[:reverse] ? index + 1 : stories.size - index }"
|
39
|
+
end
|
40
|
+
puts msg + ". Error: #{e}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def scrub_descriptions
|
48
|
+
stories.each do |story|
|
49
|
+
if story.respond_to? :description
|
50
|
+
story.description = story.description.gsub(" ", "").gsub(" \n", "\n")
|
51
|
+
end
|
52
|
+
if story.respond_to?(:description) && story.description == ""
|
53
|
+
story.attributes["description"] = nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/lib/story.rb
CHANGED
@@ -2,8 +2,8 @@ require 'active_resource'
|
|
2
2
|
|
3
3
|
class Story < ActiveResource::Base
|
4
4
|
|
5
|
-
@@defaults = YAML.load_file('
|
6
|
-
self.site = "http://www.pivotaltracker.com/services/
|
5
|
+
@@defaults = YAML.load_file('slurper_config.yml')
|
6
|
+
self.site = "http://www.pivotaltracker.com/services/v3/projects/#{@@defaults['project_id']}"
|
7
7
|
headers['X-TrackerToken'] = @@defaults.delete("token")
|
8
8
|
attr_accessor :story_lines
|
9
9
|
|
@@ -13,58 +13,4 @@ class Story < ActiveResource::Base
|
|
13
13
|
load(@@defaults.merge(attributes))
|
14
14
|
end
|
15
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
16
|
end
|
@@ -1,18 +1,14 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'spec'
|
3
|
-
require '
|
3
|
+
require 'slurper'
|
4
4
|
|
5
|
-
describe
|
6
|
-
|
7
|
-
it ".parse should return a reference to the story" do
|
8
|
-
story = Story.new
|
9
|
-
story.parse("").should == story
|
10
|
-
end
|
5
|
+
describe Slurper do
|
11
6
|
|
12
7
|
context "deals with leading/trailing whitespace" do
|
13
8
|
before do
|
14
|
-
|
15
|
-
|
9
|
+
slurper = Slurper.new(File.join(File.dirname(__FILE__), "fixtures", "whitespacey_story.slurper"))
|
10
|
+
slurper.yamlize_story_file
|
11
|
+
@story = slurper.stories.first
|
16
12
|
end
|
17
13
|
|
18
14
|
it "strips whitespace from the name" do
|
@@ -26,8 +22,9 @@ describe Story do
|
|
26
22
|
|
27
23
|
context "given values for all attributes" do
|
28
24
|
before do
|
29
|
-
|
30
|
-
|
25
|
+
slurper = Slurper.new(File.join(File.dirname(__FILE__), "fixtures", "full_story.slurper"))
|
26
|
+
slurper.yamlize_story_file
|
27
|
+
@story = slurper.stories.first
|
31
28
|
end
|
32
29
|
|
33
30
|
it "parses the name correctly" do
|
@@ -35,18 +32,23 @@ describe Story do
|
|
35
32
|
end
|
36
33
|
|
37
34
|
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
|
35
|
+
@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
36
|
end
|
40
37
|
|
41
38
|
it "parses the label correctly" do
|
42
39
|
@story.labels.should == "money,power,fame"
|
43
40
|
end
|
41
|
+
|
42
|
+
it "parses the story type correctly" do
|
43
|
+
@story.story_type.should == "feature"
|
44
|
+
end
|
44
45
|
end
|
45
46
|
|
46
47
|
context "given only a name" do
|
47
48
|
before do
|
48
|
-
|
49
|
-
|
49
|
+
slurper = Slurper.new(File.join(File.dirname(__FILE__), "fixtures", "name_only.slurper"))
|
50
|
+
slurper.yamlize_story_file
|
51
|
+
@story = slurper.stories.first
|
50
52
|
end
|
51
53
|
|
52
54
|
it "should parse the name correctly" do
|
@@ -57,27 +59,29 @@ describe Story do
|
|
57
59
|
|
58
60
|
context "given empty attributes" do
|
59
61
|
before do
|
60
|
-
|
61
|
-
|
62
|
+
slurper = Slurper.new(File.join(File.dirname(__FILE__), "fixtures", "empty_attributes.slurper"))
|
63
|
+
slurper.yamlize_story_file
|
64
|
+
@story = slurper.stories.first
|
62
65
|
end
|
63
66
|
|
64
67
|
it "should not set any name" do
|
65
|
-
@story.
|
68
|
+
@story.name.should be_nil
|
66
69
|
end
|
67
70
|
|
68
71
|
it "should not set any description" do
|
69
|
-
@story.
|
72
|
+
@story.description.should be_nil
|
70
73
|
end
|
71
74
|
|
72
75
|
it "should not set any labels" do
|
73
|
-
@story.
|
76
|
+
@story.labels.should be_nil
|
74
77
|
end
|
75
78
|
end
|
76
79
|
|
77
80
|
context "given attributes with spaces" do
|
78
81
|
before do
|
79
|
-
|
80
|
-
|
82
|
+
slurper = Slurper.new(File.join(File.dirname(__FILE__), "fixtures", "quoted_attributes.slurper"))
|
83
|
+
slurper.yamlize_story_file
|
84
|
+
@story = slurper.stories.first
|
81
85
|
end
|
82
86
|
|
83
87
|
it "should set the description correctly" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slurper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wes Gibbs
|
@@ -12,7 +12,7 @@ autorequire:
|
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
14
|
|
15
|
-
date: 2010-
|
15
|
+
date: 2010-02-05 00:00:00 -05:00
|
16
16
|
default_executable: slurp
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
@@ -67,4 +67,4 @@ signing_key:
|
|
67
67
|
specification_version: 3
|
68
68
|
summary: takes a formatted story file and puts it on Pivotal Tracker
|
69
69
|
test_files:
|
70
|
-
- spec/
|
70
|
+
- spec/slurper_spec.rb
|