pivo_flow 0.2.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/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pivo_flow.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Adam Nowak
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # PivoFlow
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'pivo_flow'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install pivo_flow
18
+
19
+ ## Usage
20
+
21
+ All the required information is gathered on demand, but it's a good idea to prepare:
22
+
23
+ * project's Pivotal Tracker ID
24
+ * your Pivotal Tracker API token
25
+
26
+ Get list of current stories
27
+
28
+ pf stories
29
+
30
+ Start story with given ID
31
+
32
+ pf start STORY_ID
33
+
34
+ Finish current story [or given story ID]
35
+
36
+ pf finish [STORY_ID]
37
+
38
+ Clear current story without notifying Pivotal
39
+
40
+ pf clear
41
+
42
+ ## Contributing
43
+
44
+ 1. Fork it
45
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
46
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
47
+ 4. Push to the branch (`git push origin my-new-feature`)
48
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/pf ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+
5
+ require 'pivo_flow'
6
+
7
+ exit PivoFlow::Cli.new(*ARGV)
8
+
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ story_path = 'tmp/.pivotal_story_id'
3
+ if File.exists?(story_path)
4
+ story_id = File.read(story_path).strip
5
+ if story_id =~ /(\d{7,})/
6
+ puts IO.read(ARGV[0])
7
+ commit_msg = IO.read(ARGV[0])
8
+ unless commit_msg.include?($1)
9
+ File.open(ARGV[0], 'w') do |file|
10
+ file.print commit_msg
11
+ file.print "[##{$1}]"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,87 @@
1
+ module PivoFlow
2
+ class Base
3
+ GIT_DIR = '.git'
4
+ KEYS_TO_CHECK = ["test-pivotal.project-id", "test-pivotal.api-token"]
5
+
6
+ def initialize(*args)
7
+ @options = {}
8
+ @current_dir = Dir.pwd
9
+ return "no GIT (#{GIT_DIR}) directory found" unless File.directory?(File.join(@current_dir, GIT_DIR))
10
+
11
+ # paths
12
+ @git_dir = File.join(@current_dir, GIT_DIR)
13
+ @git_hook_path = File.join(@git_dir, 'hooks', 'prepare-commit-msg')
14
+ @pf_git_hook_name = 'pf-prepare-commit-msg'
15
+ @pf_git_hook_path = File.join(@git_dir, 'hooks', @pf_git_hook_name)
16
+
17
+ @options[:repository] = Grit::Repo.new(@git_dir)
18
+ install_git_hook if git_hook_needed?
19
+ git_config_ok? ? parse_git_config : add_git_config
20
+ run
21
+ end
22
+
23
+ def git_hook_needed?
24
+ !File.executable?(@git_hook_path) || !File.read(@git_hook_path).match(/\.\/#{@pf_git_hook_name}/)
25
+ end
26
+
27
+ def install_git_hook
28
+ puts "Installing prepare-commit-msg hook..."
29
+ hook_path = File.join(File.dirname(__FILE__), '..', '..', 'bin', @pf_git_hook_name)
30
+ FileUtils.cp(hook_path, @pf_git_hook_path, preserve: true)
31
+ puts "File copied..."
32
+ unless File.read(@git_hook_path).match(/\.\/#{@pf_git_hook_name}/)
33
+ File.open(@git_hook_path, "a") { |f| f.puts("./#{@pf_git_hook_name}") }
34
+ puts "Reference to pf-prepare-commit-msg added to prepare-commit-msg..."
35
+ end
36
+ unless File.executable?(@git_hook_path)
37
+ FileUtils.chmod 0755, @git_hook_path unless
38
+ puts "Chmod on #{@git_hook_path} set to 755"
39
+ end
40
+
41
+ puts "Success!\n"
42
+ end
43
+
44
+ def run
45
+ raise "you should define run!"
46
+ end
47
+
48
+ def user_name
49
+ @options[:user_name] ||= @options[:repository].config['pivotal.full-name'] || @options[:repository].config['user.name']
50
+ end
51
+
52
+ private
53
+
54
+ def git_config_ok?
55
+ !KEYS_TO_CHECK.any? { |key| @options[:repository].config[key].nil? }
56
+ end
57
+
58
+ def add_git_config
59
+ ask_question_and_update_config "Pivotal: what is your project's ID?", "test-pivotal.project-id"
60
+ ask_question_and_update_config "Pivotal: what is your pivotal tracker api-token?", "test-pivotal.api-token"
61
+ parse_git_config
62
+ end
63
+
64
+ def parse_git_config
65
+ KEYS_TO_CHECK.each do |key|
66
+ new_key = key.split(".").last
67
+ @options[new_key] = @options[:repository].config[key]
68
+ end
69
+ end
70
+
71
+ def ask_question_and_update_config question, variable
72
+ @options[:repository].config[variable] ||= ask_question(question)
73
+ end
74
+
75
+ def ask_question question, first_answer = nil
76
+ h = HighLine.new
77
+ h.ask("#{question}\t") do |q|
78
+ q.responses[:ask_on_error] = :question
79
+ q.responses[:not_valid] = "It can't be empty, sorry"
80
+ q.validate = ->(id) { !id.empty? }
81
+ q.first_answer = first_answer
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+ end
@@ -0,0 +1,55 @@
1
+ module PivoFlow
2
+ class Cli
3
+
4
+ def initialize *args
5
+ if args.length.zero?
6
+ puts "You forgot method name"
7
+ exit 1
8
+ end
9
+ parse_argv(*args)
10
+ end
11
+
12
+ def stories
13
+ PivoFlow::Pivotal.new.show_stories
14
+ end
15
+
16
+ def start story_id
17
+ PivoFlow::Pivotal.new.pick_up_story(story_id)
18
+ end
19
+
20
+ def finish story_id=nil
21
+ file_story_path = File.join(Dir.pwd, "/tmp/.pivotal_story_id")
22
+ if File.exists? file_story_path
23
+ story_id = File.open(file_story_path).read.strip
24
+ end
25
+ PivoFlow::Pivotal.new.finish_story(story_id)
26
+ end
27
+
28
+ def clear
29
+ file_story_path = File.join(Dir.pwd, "/tmp/.pivotal_story_id")
30
+ if File.exists? file_story_path
31
+ FileUtils.remove_file(file_story_path)
32
+ end
33
+ puts "Current pivotal story id cleared."
34
+ end
35
+
36
+ private
37
+
38
+ def valid_method? method_name
39
+ self.methods.include? method_name.to_sym
40
+ end
41
+
42
+ def parse_argv(*args)
43
+ command = args.first.split.first
44
+ args = args.slice(1..-1)
45
+
46
+ unless valid_method?(command)
47
+ puts "Ups, no such method..."
48
+ exit 1
49
+ end
50
+ send(command, *args)
51
+ exit 0
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,129 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module PivoFlow
3
+ class Pivotal < Base
4
+
5
+ def run
6
+ @story_id_file_name = ".pivotal_story_id"
7
+ @story_id_tmp_path = File.join(@current_dir, "/tmp")
8
+ @story_id_file_path = File.join(@story_id_tmp_path, @story_id_file_name)
9
+
10
+ return 1 unless @options["api-token"] && @options["project-id"]
11
+ PivotalTracker::Client.token = @options["api-token"]
12
+ PivotalTracker::Client.use_ssl = true
13
+ @options[:project] ||= PivotalTracker::Project.find(@options["project-id"])
14
+ end
15
+
16
+ def user_stories
17
+ project_stories.select{ |story| story.owned_by == user_name }
18
+ end
19
+
20
+ def project_stories
21
+ @options[:stories] ||= fetch_stories
22
+ end
23
+
24
+ def unasigned_stories
25
+ project_stories.select{ |story| story.owned_by == nil }
26
+ end
27
+
28
+ def current_story force = false
29
+ if (@options[:current_story] && !force)
30
+ @options[:current_story]
31
+ else
32
+ @options[:current_story] = user_stories.count.zero? ? nil : user_stories.first
33
+ end
34
+ end
35
+
36
+ def list_stories_to_output stories
37
+ if current_story
38
+ puts "You've got some started stories, it may be a good idea to finish them in the first place"
39
+ puts "[##{current_story.id}] #{current_story.name} - #{current_story.description}"
40
+ end
41
+
42
+ HighLine.new.choose do |menu|
43
+ menu.header = "--- STORIES FROM PIVOTAL TRACKER ---\nWhich one would you like to start? "
44
+ menu.prompt = "story no.? "
45
+ menu.select_by = :index
46
+ stories.each do |story|
47
+ vars = {
48
+ story_id: story.id,
49
+ requested_by: story.requested_by,
50
+ name: story.name,
51
+ story_type: story_type_icon(story),
52
+ estimate: estimate_points(story)
53
+ }
54
+ story_text = "[#%{story_id}] %{story_type} [%{estimate} pts.] (requested by: %{requested_by}) %{name}" % vars
55
+ story_text += "\n Description: #{story.description}" unless story.description
56
+ menu.choice(story_text) { |answer| pick_up_story(answer.match(/\[#(?<id>\d+)\]/)[:id])}
57
+ end
58
+ end
59
+ end
60
+
61
+ def story_type_icon story
62
+ case story.story_type
63
+ when "feature" then "☆"
64
+ when "bug" then "☠"
65
+ when "chore" then "✙"
66
+ else "☺"
67
+ end
68
+ end
69
+
70
+ def estimate_points story
71
+ unless story.estimate.nil?
72
+ story.estimate < 0 ? "?" : story.estimate
73
+ else
74
+ "no"
75
+ end
76
+ end
77
+
78
+ def pick_up_story story_id
79
+ save_story_id_to_file(story_id) if start_story(story_id)
80
+ end
81
+
82
+ def update_story story_id, state
83
+ story = @options[:project].stories.find(story_id)
84
+ if story.nil?
85
+ puts "Story not found, sorry."
86
+ end
87
+ if story.update(owned_by: user_name, current_state: state).errors.count.zero?
88
+ puts "Story updated in Pivotal Tracker"
89
+ true
90
+ else
91
+ error_message = "ERROR"
92
+ error_message += ": #{story.errors.first}"
93
+ puts error_message
94
+ end
95
+ end
96
+
97
+ def start_story story_id
98
+ update_story(story_id, :started)
99
+ end
100
+
101
+ def finish_story story_id
102
+ remove_story_id_file if story_id.nil? or update_story(story_id, :finished)
103
+ end
104
+
105
+ def remove_story_id_file
106
+ FileUtils.remove_file(@story_id_file_path)
107
+ end
108
+
109
+ def save_story_id_to_file story_id
110
+ FileUtils.mkdir_p(@story_id_tmp_path)
111
+ File.open(@story_id_file_path, 'w') { |f| f.write(story_id) }
112
+ end
113
+
114
+ def show_stories
115
+ stories = user_stories + unasigned_stories
116
+ if stories.count.zero?
117
+ puts "hmm... there is no story assigned to you! I'll better check for unasigned stories!"
118
+ stories = unasigned_stories
119
+ end
120
+ list_stories_to_output stories.first(10)
121
+ end
122
+
123
+ def fetch_stories(count = 100, state = "unstarted,unscheduled")
124
+ conditions = { current_state: state, limit: count }
125
+ @options[:stories] = @options[:project].stories.all(conditions)
126
+ end
127
+
128
+ end
129
+ end
@@ -0,0 +1,3 @@
1
+ module PivoFlow
2
+ VERSION = "0.2.0"
3
+ end
data/lib/pivo_flow.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'grit'
2
+ require 'optparse'
3
+ require 'highline'
4
+ require 'fileutils'
5
+ require 'pivotal-tracker'
6
+
7
+ require 'pivo_flow/version'
8
+ require 'pivo_flow/base'
9
+ require 'pivo_flow/cli'
10
+ require 'pivo_flow/pivotal'
data/pivo_flow.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/pivo_flow/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Adam Nowak"]
6
+ gem.email = ["lubieniebieski@gmail.com"]
7
+ gem.description = %q{Automated querying for pivotal stories, adding story id to commit message, etc.}
8
+ gem.summary = %q{Simple pivotal tracker integration for day to day work with git}
9
+ gem.homepage = "https://github.com/lubieniebieski/pivo_flow"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "pivo_flow"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = PivoFlow::VERSION
17
+
18
+ gem.add_runtime_dependency "pivotal-tracker"
19
+ gem.add_runtime_dependency "grit"
20
+ gem.add_runtime_dependency "highline"
21
+ gem.add_development_dependency "rspec"
22
+
23
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pivo_flow
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Adam Nowak
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: pivotal-tracker
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: grit
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: highline
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Automated querying for pivotal stories, adding story id to commit message,
79
+ etc.
80
+ email:
81
+ - lubieniebieski@gmail.com
82
+ executables:
83
+ - pf
84
+ - pf-prepare-commit-msg
85
+ extensions: []
86
+ extra_rdoc_files: []
87
+ files:
88
+ - .gitignore
89
+ - Gemfile
90
+ - LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - bin/pf
94
+ - bin/pf-prepare-commit-msg
95
+ - lib/pivo_flow.rb
96
+ - lib/pivo_flow/base.rb
97
+ - lib/pivo_flow/cli.rb
98
+ - lib/pivo_flow/pivotal.rb
99
+ - lib/pivo_flow/version.rb
100
+ - pivo_flow.gemspec
101
+ homepage: https://github.com/lubieniebieski/pivo_flow
102
+ licenses: []
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ! '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 1.8.24
122
+ signing_key:
123
+ specification_version: 3
124
+ summary: Simple pivotal tracker integration for day to day work with git
125
+ test_files: []