stories_sync 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,37 @@
1
+ Stories Sync
2
+ ============
3
+
4
+ The idea behind this gem is to have a tool to help you manage your user stories.
5
+
6
+ It'a a great combo to use [stories](github.com/citrusbyte/storires) + [Pivotal Tracker](http://www.pivotaltracker.com) for your integration testing, and it works pretty fine out of the box. However, one can't avoid thinking that there's something missing. For example there's no obvious way to run your user stories or generate reports easily; one would have to create a rake task or something like that.
7
+
8
+ And what about the ability to have pending stories? seems like a cool idea, but for that to make any sense, one would have to copy/past every story (one by one) on your backlog at the beginning of each sprint to your local stories file, mark them as pending stories and start them one at a time. Seems like a lot of work. It is.
9
+
10
+ Finally, if you discover that a story actually needs to be splitted in two, or you just want to add a new story and start implementing it, it's kind of a drag to have to log in to pivotal, create the story, move it to the backlog, mark it as "started", then go back to your local file, copy it there and finally start coding.
11
+
12
+ What we wanted is the following workflow:
13
+
14
+ $ stories sync
15
+
16
+ That's it. That simple command will fetch the pivotal stories and add them as pending stories in your file and also upload any new local story to pivotal.
17
+
18
+ If you want to run your stories with the standard stories output:
19
+
20
+ $ stories run
21
+
22
+ To generate a pdf report:
23
+
24
+ $ stories report
25
+
26
+
27
+ Setup and usage
28
+ ---------------
29
+
30
+ This gem requires you to have a pivotal.yml in your config directory, but this can be generated automatically. Just execute the following:
31
+
32
+ $ stories setup
33
+
34
+ This will ask you for your pivotal username and password, then it will fetch your api token and finally it will create a config file for you.
35
+ After that you are all set. You just need to have a stories file in /test/stories/stories.rb.
36
+
37
+ _Right now this gem supports a single stories file only. This is the first version though, we are working for multiple file support._
data/bin/stories ADDED
@@ -0,0 +1,9 @@
1
+ #! /usr/bin/env ruby -rubygems
2
+
3
+ require File.join(File.dirname(__FILE__), "..", "lib", "stories_sync.rb")
4
+
5
+ if File.exists?("Thorfile")
6
+ load("Thorfile")
7
+ end
8
+
9
+ Stories.start
data/lib/pivotal.rb ADDED
@@ -0,0 +1,17 @@
1
+ CONFIG_FILE = File.join("#{Dir.pwd}", "config", "pivotal.yml")
2
+
3
+ # Failsafe in case there's no config file
4
+ raise "You don't have a configuration file yet, try running 'stories setup'" unless File.exists?(CONFIG_FILE)
5
+
6
+ PIVOTAL_CONFIG = YAML.load_file(CONFIG_FILE)
7
+
8
+ # Interaction with Pivotal
9
+ class Iteration < ActiveResource::Base
10
+ self.site = "http://www.pivotaltracker.com/services/v2/projects/#{PIVOTAL_CONFIG[:project][:id]}"
11
+ headers['X-TrackerToken'] = PIVOTAL_CONFIG[:user][:token]
12
+ end
13
+
14
+ class Story < ActiveResource::Base
15
+ self.site = "http://www.pivotaltracker.com/services/v2/projects/#{PIVOTAL_CONFIG[:project][:id]}"
16
+ headers['X-TrackerToken'] = PIVOTAL_CONFIG[:user][:token]
17
+ end
@@ -0,0 +1,177 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require 'restclient'
5
+ require "activeresource"
6
+ require "thor"
7
+ require "yaml"
8
+ require File.join("#{File.dirname(__FILE__)}", "pivotal.rb")
9
+
10
+ # FIXME We are using both restclient and ARes at the moment.
11
+ # We should migrate everything to restclient, since it's way smaller.
12
+
13
+ class Stories < Thor
14
+ include Thor::Actions
15
+
16
+ desc "add_pending_stories", "Fetch stories and add them to the stories file"
17
+ def add_pending_stories
18
+ say_status(:fetch, "Fetching stories from Pivotal.")
19
+ new_external_stories = difference(fetch_stories, local_stories)
20
+
21
+ if new_external_stories.empty?
22
+ say_status(:complete, "Pivotal stories up to date.")
23
+ else
24
+ new_external_stories.each_pair do |label, stories|
25
+ say_status(:compare, "There are #{stories.size} new stories in Pivotal with the label '#{label}'.")
26
+ end
27
+ add_new_stories(new_external_stories)
28
+ end
29
+ end
30
+
31
+ desc "upload_new_stories", "Upload new stories to pivotal"
32
+ def upload_new_stories
33
+ new_local_stories = difference(local_stories, fetch_stories)
34
+
35
+ new_local_stories.each_pair do |label, stories|
36
+ say_status(:compare, "You have #{stories.size} new local stories in #{label}_test.rb.")
37
+ end
38
+
39
+ if new_local_stories.empty?
40
+ say_status(:completed, "Your stories up to date.")
41
+ else
42
+ new_local_stories.each_pair do |label, stories|
43
+ stories.each do |story|
44
+ upload_new_story(story, label)
45
+ say_status(:append, "Added story: '#{story}'")
46
+ end
47
+ end
48
+ say_status(:completed, "Stories added to Pivotal successfully.")
49
+ end
50
+ end
51
+
52
+ desc "sync", "Synchronize your local stories with your Pivotal stories"
53
+ def sync
54
+ add_pending_stories
55
+ upload_new_stories
56
+ end
57
+
58
+ desc "setup", "Fetch your Pivotal token and creates or updates your config file"
59
+ def setup
60
+ unless user_config
61
+ say "You need to create a configuration file"
62
+ create_config_file
63
+ else
64
+ say "This is your pivotal configuration"
65
+ say user_config
66
+ create_config_file if yes? "Do you want to change your configuration? (Y/N)"
67
+ end
68
+ end
69
+
70
+ private
71
+ def difference(hash_a ,hash_b)
72
+ new_stories = {}
73
+ hash_a.each_pair do |label, stories|
74
+ new_stories[label] = stories - hash_b[label].to_a
75
+ end
76
+ new_stories
77
+ end
78
+
79
+ # Returns {"label_1" => [story_1, story_2, ..], "label_2" => [story_1, story_2, ..]}
80
+ def fetch_stories
81
+ Iteration.find(:last).stories.inject({}) do |result, story|
82
+ result[story.labels] ||= []
83
+ result[story.labels] << story.name
84
+ result
85
+ end
86
+ rescue NoMethodError; []
87
+ end
88
+
89
+ def fetch_token(user = PIVOTAL_CONFIG[:user][:name], pass = PIVOTAL_CONFIG[:user][:pass])
90
+ token = RestClient::Resource.new("https://www.pivotaltracker.com/services/tokens/active/guid",
91
+ :user => user,
92
+ :password => pass).get
93
+ token.match(/<guid>(.*)<.guid>/)[1]
94
+ rescue RestClient::Unauthorized
95
+ say_status :unauthorized, "Your credentials are invalid, You'll have to try again", :red
96
+ end
97
+
98
+ def create_config_file
99
+ say_status :setup, "Gathering your Pivotal information"
100
+ user = ask "What is your user name?"
101
+ pass = ask "What is your password?"
102
+ say_status :fetch, "Fetching your Pivotal token"
103
+ token = fetch_token(user, pass)
104
+ return if token.nil?
105
+ project_id = ask "What is your pivotal project id?"
106
+ create_yaml(user, pass, token, project_id)
107
+ say_status :create, "./config/pivotal.yml"
108
+ end
109
+
110
+ def user_config
111
+ File.read(CONFIG_FILE) if File.exists?(CONFIG_FILE)
112
+ end
113
+
114
+ # TODO we should consider making this a public method
115
+ # and merging the extra options if given.
116
+ def upload_new_story(name, key)
117
+ # Other options are: requested_by, description, owned_by, labels
118
+ story = Story.create(:name => name, :estimate => 1, :labels => key)
119
+ story.current_state = "unstarted"
120
+ story.save
121
+ end
122
+
123
+ def story_file(label)
124
+ File.join("#{Dir.pwd}", "test", "stories", "#{label}_test.rb")
125
+ end
126
+
127
+ def files
128
+ Dir.entries(File.join("#{Dir.pwd}", "test", "stories")).delete_if do |name|
129
+ name.match(/_test.rb/).nil?
130
+ end
131
+ end
132
+
133
+ def labels
134
+ files.inject([]) do |labels, name|
135
+ labels << name.scan(/(.*)_test.rb/).to_s
136
+ labels
137
+ end
138
+ end
139
+
140
+ def local_stories
141
+ labels.inject({}) do |result, label|
142
+ result[label] ||= []
143
+ result[label] = File.read(story_file(label)).scan(/\n\s*story[\s\n]*(?:"([^"]*)"|'([^']*)')/m).map(&:compact).flatten
144
+ result
145
+ end
146
+ end
147
+
148
+ def add_new_stories(new_stories)
149
+ new_stories.each_pair do |label, stories|
150
+ file = story_file(label)
151
+ if File.exists?(file)
152
+ original_file = File.read(file).scan(/(.*)end/m)
153
+ original_file << "\n # Pending stories\n"
154
+
155
+ File.open(story_file(label), "w") do |f|
156
+ f.puts original_file
157
+ f.puts stories.map {|story| " story \"#{story}\""}.join("\n")
158
+ f.puts "end"
159
+ end
160
+ else
161
+ say_status(:warning, "The file '#{file}' doesn't exist", :yellow)
162
+ end
163
+ end
164
+ say_status(:append, "Pending stories added successfully.")
165
+ end
166
+
167
+ def create_yaml(user, pass, token, project_id)
168
+ config = { :user => { :name => user,
169
+ :pass => pass,
170
+ :token => token.to_s },
171
+ :project => { :id => project_id } }
172
+
173
+ File.open(CONFIG_FILE, "w+") do |f|
174
+ f.puts config.to_yaml
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,13 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "stories_sync"
3
+ s.version = "0.0.1"
4
+ s.summary = "Stories Sync"
5
+ s.description = "Manage and syncrhonize your UATs with Pivotal Tracker."
6
+ s.authors = ["Evelin Garcia", "Lucas Nasif"]
7
+ s.email = ["ehqhvm@gmail.com"]
8
+
9
+ s.rubyforge_project = "stories_sync"
10
+
11
+ s.files = ["README.markdown", "lib/pivotal.rb", "lib/stories_sync.rb", "bin/stories", "stories_sync.gemspec"]
12
+ end
13
+
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stories_sync
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Evelin Garcia
8
+ - Lucas Nasif
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-10-12 00:00:00 -03:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Manage and syncrhonize your UATs with Pivotal Tracker.
18
+ email:
19
+ - ehqhvm@gmail.com
20
+ executables: []
21
+
22
+ extensions: []
23
+
24
+ extra_rdoc_files: []
25
+
26
+ files:
27
+ - README.markdown
28
+ - lib/pivotal.rb
29
+ - lib/stories_sync.rb
30
+ - bin/stories
31
+ - stories_sync.gemspec
32
+ has_rdoc: true
33
+ homepage:
34
+ licenses: []
35
+
36
+ post_install_message:
37
+ rdoc_options: []
38
+
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project: stories_sync
56
+ rubygems_version: 1.3.5
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: Stories Sync
60
+ test_files: []
61
+