todone 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm ruby-1.9.2-head@rails3
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+
4
+ gem "commander","4.0.3"
5
+ gem "crack", "0.1.8"
6
+ gem "httparty", "0.7.4"
7
+
8
+ # Add dependencies to develop your gem here.
9
+ group :development do
10
+ gem "rspec"
11
+ gem "mocha","0.9.12"
12
+ gem "bundler", "~> 1.0.0"
13
+ gem "jeweler", "~> 1.5.2"
14
+ gem "rcov", ">= 0"
15
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,39 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ commander (4.0.3)
5
+ highline (>= 1.5.0)
6
+ crack (0.1.8)
7
+ diff-lcs (1.1.2)
8
+ git (1.2.5)
9
+ highline (1.6.1)
10
+ httparty (0.7.4)
11
+ crack (= 0.1.8)
12
+ jeweler (1.5.2)
13
+ bundler (~> 1.0.0)
14
+ git (>= 1.2.5)
15
+ rake
16
+ mocha (0.9.12)
17
+ rake (0.8.7)
18
+ rcov (0.9.9)
19
+ rspec (2.5.0)
20
+ rspec-core (~> 2.5.0)
21
+ rspec-expectations (~> 2.5.0)
22
+ rspec-mocks (~> 2.5.0)
23
+ rspec-core (2.5.1)
24
+ rspec-expectations (2.5.0)
25
+ diff-lcs (~> 1.1.2)
26
+ rspec-mocks (2.5.0)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ bundler (~> 1.0.0)
33
+ commander (= 4.0.3)
34
+ crack (= 0.1.8)
35
+ httparty (= 0.7.4)
36
+ jeweler (~> 1.5.2)
37
+ mocha (= 0.9.12)
38
+ rcov
39
+ rspec
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 A Flores
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ todone
2
+ ====
3
+
4
+ ToDone is a command-line tool that gives you the ability to better integrate git with your project manager(which is currently only allowed to be pivotal tracker). Also, Todone is, somewhat ironically, a work in progress.
5
+
6
+ Requirements
7
+ ------------
8
+
9
+ * Ruby = 1.9.2
10
+ * Rubygems = 1.3.7
11
+
12
+ Installation & Setup
13
+ --------------------
14
+
15
+ $ gem install todone
16
+ $ cd git_pj_directory
17
+ $ todone add_project
18
+
19
+ todone add_project
20
+ ----------------
21
+
22
+ adds a hook for pre-commit that pulls in open stories from pivotal tracker in order to standardize the user's commit messages
23
+
24
+ todone users
25
+ --------
26
+
27
+ displays who todone will filter by when grabbing information via pivotal
28
+
29
+ todone tickets
30
+ --------------------------
31
+
32
+ grabs all of the tickets/chores/bugs from pivotal tracker that are open or have been modified for the current day.
33
+
34
+ todone daily_update
35
+ -----------------
36
+
37
+ prints out a template for the daily update a user will have
data/Rakefile ADDED
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "todone"
16
+ gem.homepage = "http://github.com/unflores/todone"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Todone is a command-line tool that gives you the ability to better integrate git with your pivotal project.}
19
+ gem.description = %Q{Toone is a command-line tool that gives you the ability to better integrate git with your project manager(which is currently only allowed to be pivotal tracker). Todone will keep track of the tickets your working on so you don't have to.}
20
+ gem.email = "ctrl4ltdeleteme@gmail.com"
21
+ gem.authors = ["A Flores"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core/rake_task'
29
+ RSpec::Core::RakeTask.new do |t|
30
+ t.rspec_opts = ["--color", "--format documentation", "--fail-fast"]
31
+ t.pattern = ['spec/libs/*.rb']
32
+ end
33
+
34
+ require 'rcov/rcovtask'
35
+ Rcov::RcovTask.new do |test|
36
+ test.libs << 'spec'
37
+ test.pattern = 'spec/libs/*_spec.rb'
38
+ test.verbose = true
39
+ end
40
+
41
+ task :default => :test
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "todone #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.2
data/bin/todone ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ %w(rubygems commander/import todone).each{|requirement| require requirement}
5
+
6
+ version_file = File.join(File.dirname(__FILE__), '..', 'VERSION')
7
+
8
+ program :name, 'Todone'
9
+ program :description, 'a tool for better communication'
10
+ program :version, File.read(version_file)
11
+ program :help_formatter, Commander::HelpFormatter::TerminalCompact
12
+ default_command :help
13
+
14
+ def ensure_config_exists!
15
+ if Todone::MessageProcessor.needs_init?
16
+ setup = {}
17
+ setup[:api_key] = ask("API Key: ") {|q| q.echo = true}
18
+ setup[:owner] = ask("Username: "){|q| q.echo = true}
19
+ Todone::MessageProcessor.init setup
20
+ end
21
+ end
22
+
23
+ command :add_project do |cmd|
24
+ cmd.syntax = "todone #{cmd.name}"
25
+ cmd.summary = "Adds the current project to the config file"
26
+ cmd.description = "#{cmd.summary}"
27
+ cmd.when_called do |args, options|
28
+
29
+ ensure_config_exists!
30
+
31
+ options = {}
32
+ options[:id] = ask("Project id: ") {|q| q.echo = true}
33
+ options[:users] = ask("Other users: "){|q| q.echo = true}
34
+ mp = Todone::MessageProcessor.new()
35
+ puts mp.add_project(options)
36
+ end
37
+ end
38
+
39
+ command :users do |cmd|
40
+ cmd.syntax = "todone #{cmd.name}"
41
+ cmd.summary = "display the users who the tickets will be filtered by"
42
+ cmd.description = "#{cmd.summary}"
43
+ cmd.when_called do |args, options|
44
+ #
45
+ end
46
+ end
47
+
48
+ command :daily_update do |cmd|
49
+ cmd.syntax = "todone #{cmd.name}"
50
+ cmd.summary = "print out an email template with"
51
+ cmd.description = "#{cmd.summary}"
52
+ cmd.when_called do |args, options|
53
+ ensure_config_exists!
54
+ #
55
+ end
56
+ end
57
+
58
+ command :tickets do |cmd|
59
+ cmd.syntax = "todone #{cmd.name} [options]"
60
+ cmd.summary = "print out a list of open tickets"
61
+ cmd.description = "#{cmd.summary}"
62
+ cmd.option "-s","--started", "All tickets currently started"
63
+ cmd.option "-d","--delivered", "All tickets delivered today"
64
+ cmd.option "-a","--accepted", "All tickets accepted today"
65
+ cmd.option "-w", "--write-file", "Write to file"
66
+ cmd.option "--file [FILE]", String ,"File to write to. Default is .git/COMMIT_EDITMSG"
67
+ cmd.when_called do |args, options|
68
+ ensure_config_exists!
69
+ project_id = args.shift
70
+ mp = Todone::MessageProcessor.new(:project_id => project_id)
71
+ if options.write_file.nil?
72
+ puts mp.view_tickets
73
+ else
74
+ mp.write_tickets :file => options.file
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,12 @@
1
+ project_string = ''
2
+ found = nil
3
+ File.open('.git/config') do |file|
4
+ file.each_line do |line|
5
+ found ||= line.slice('[pivotal]')
6
+ project_string = line.tr(" \t\n",'') unless found.nil? or (line.slice('[pivotal]') and found)
7
+ end
8
+ end
9
+
10
+ return nil if project_string.blank?
11
+
12
+ project_string.split('=').last
@@ -0,0 +1,73 @@
1
+ # @see http://mjijackson.com/2010/02/flexible-ruby-config-objects
2
+ # @author Michael Jackson
3
+ #
4
+ # config = Config.new
5
+ # config.database = 'database_name'
6
+ # config.username = 'user'
7
+ # config.db_hosts = {
8
+ # 'sj' => 'sanjose.example.com',
9
+ # 'ny' => 'newyork.example.com'
10
+ # }
11
+ #
12
+ # config.username # "user"
13
+ # config.db_hosts.ny # "newyork.example.com"
14
+ module Todone
15
+ class Config
16
+
17
+ class << self
18
+ # @see Todone::Consts:CONFIG_FILE
19
+ # @param [String] dir The directory to look in for the file specified by {Todone::Consts::CONFIG_FILE}
20
+ # @return [Todone::Config] The populated {Config} instance
21
+ def load_config dir
22
+ require 'yaml'
23
+ data = YAML.load(File.open(File.join(dir, Todone::Consts::CONFIG_FILE)).read)
24
+ Todone::Config.new data
25
+ end
26
+ end
27
+
28
+ def save dir
29
+ File.open(File.join(dir, Todone::Consts::CONFIG_FILE), 'w') do |f|
30
+ f.write( @data.to_hash.to_yaml )
31
+ end
32
+ end
33
+
34
+ def initialize(data={})
35
+ @data = {}
36
+ update!(data)
37
+ end
38
+
39
+ def update!(data)
40
+ data.each do |key, value|
41
+ self[key] = value
42
+ end
43
+ end
44
+
45
+ def [](key)
46
+ @data[key.to_sym]
47
+ end
48
+
49
+ def []=(key, value)
50
+ if value.class == Hash
51
+ @data[key.to_sym] = Config.new(value)
52
+ else
53
+ @data[key.to_sym] = value
54
+ end
55
+ end
56
+
57
+ def to_hash
58
+ hash = {}
59
+ @data.each do |key,value|
60
+ hash[key] = value.class == Config ? value.to_hash : value
61
+ end
62
+ hash
63
+ end
64
+
65
+ def method_missing(sym, *args)
66
+ if sym.to_s =~ /(.+)=$/
67
+ self[$1] = args.first
68
+ else
69
+ self[sym]
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,16 @@
1
+ module Todone
2
+ ##
3
+ # Simple container module for constants.
4
+ #
5
+ # @author A Flores
6
+ module Consts
7
+ ## Name of the general (defaults) config file
8
+ CONFIG_FILE = 'config.yml'
9
+ ## Default configuaration storage directory
10
+ CONFIG_DIR = File.join(ENV['HOME'], '.todone')
11
+ ## Default general (defaults) config file location
12
+ CONFIG = File.join(CONFIG_DIR, CONFIG_FILE)
13
+ ## where the hook file is
14
+ HOOK_FILE = File.join('.git','hooks','pre-commit')
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ require 'httparty'
2
+ require 'cgi'
3
+ module Todone
4
+ class PivotalPuller
5
+ include HTTParty
6
+ base_uri 'www.pivotaltracker.com'
7
+
8
+
9
+ def initialize opts
10
+ @project_id = opts[:project_id]
11
+ Todone::PivotalPuller.headers 'X-TrackerToken' => opts[:tracker_token]
12
+ end
13
+
14
+ def pull_stories state
15
+ return { "error" => "invalid_state" } unless %w(started unstarted accepted delivered unscheduled).include? state
16
+ filter = CGI.escape("state:#{state}")
17
+ PivotalPuller.get("/services/v3/projects/#{@project_id}/stories?filter=#{filter}")['stories'] || []
18
+ rescue
19
+ return { "error" => "api_problem" }
20
+ end
21
+ end
22
+
23
+ end
24
+
@@ -0,0 +1,61 @@
1
+ module Todone
2
+ module Views
3
+
4
+ def missing_project
5
+ "Error: Missing project id or users."
6
+ end
7
+
8
+ def missing_hooks_dir data
9
+ "Couldn't find the .git dir.\n" +
10
+ "Go to your project directory and type the following:\n" +
11
+ "echo 'todone tickets #{data[:project][:id]} -m' > .git/hooks/pre-commit\n" +
12
+ "chmod 751 #{Todone::Consts::HOOK_FILE}\n"
13
+ end
14
+
15
+ def missing_project_id
16
+ "Error: No project id"
17
+ end
18
+
19
+ def exists_pre_commit_hook data
20
+ "It looks like you're already using your pre-commit hook.\n" +
21
+ "I was planning on putting something like the following in there:\n" +
22
+ "todone tickets #{data[:project_id]} -m"
23
+
24
+ end
25
+
26
+ def updated_pre_commit_hook
27
+ "Your pre-commit hook has been updated."
28
+ end
29
+
30
+ def show_pivotal_stories data
31
+ #TODO: merge post-commit into precommit
32
+ #TODO: resque from getaddrinfo: nodename nor servname provided, or not known (SocketError)
33
+ =begin def last_msg
34
+ last_commit = `git log -n1`.split("\n")
35
+ 3.times{ last_commit.shift }
36
+ last_commit_msg = "#\n#==================Last Commit===============\n"
37
+ last_commit_msg << last_commit.collect{|line| "##{line}"}.join("\n")+"\n"
38
+ end
39
+ =end
40
+ pre_commit_msg = "#==================Open Tickets================\n"
41
+ unless data[:stories].nil?
42
+ data[:stories].each do |story|
43
+ pre_commit_msg << "#[#{story['id']}] #{story['name']}\n"
44
+ end
45
+ pre_commit_msg
46
+ end
47
+ end
48
+
49
+ def missing_write_file data
50
+ "The file you are trying to write to '#{data[:file]}' does not exist"
51
+ end
52
+
53
+ def missing_view data
54
+ "The method: #{data[:method]} does not currently have a view associated with it."
55
+ end
56
+
57
+ def api_problem data
58
+ "There was a problem connecting to the PivotalTracker API."
59
+ end
60
+ end
61
+ end
data/lib/todone.rb ADDED
@@ -0,0 +1,131 @@
1
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'todone')
2
+
3
+ %w(cgi pivotal_puller config consts views yaml fileutils).each{|requirement| require requirement}
4
+
5
+ module Todone
6
+ class MessageProcessor
7
+ attr :config
8
+ attr_writer :config_dir
9
+ include Todone::Consts
10
+ include Todone::Views
11
+
12
+ class << self
13
+ def needs_init? dir = Todone::Consts::CONFIG_DIR
14
+ not File.exists? File.join(dir, Todone::Consts::CONFIG_FILE)
15
+ end
16
+
17
+ def init options
18
+ return if options[:owner].nil? or options[:api_key].nil?
19
+ dir = options.delete(:dir) || Todone::Consts::CONFIG_DIR
20
+ conf_file = File.join(dir, Todone::Consts::CONFIG_FILE)
21
+
22
+ FileUtils.mkdir_p dir unless File.exists? dir
23
+ unless File.exists? conf_file
24
+ File.open(conf_file, 'w') do |f|
25
+ YAML.dump({
26
+ :owner => options[:owner],
27
+ :api_key => options[:api_key]
28
+ }, f)
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ def load_project_id
35
+ File.open(Todone::MessageProcessor.git_config) do |file|
36
+ file.each_line do |line|
37
+ return file.gets.tr("\s\t\n",'').split('=').last if line.slice('[pivotal]')
38
+ end
39
+ end
40
+ return nil
41
+ end
42
+
43
+ def commit_msg_file
44
+ File.join(git_dir,'COMMIT_EDITMSG')
45
+ end
46
+
47
+ def git_dir
48
+ if Dir.getwd.split('/').last == 'hooks' then '..'
49
+ elsif Dir.exists? '.git' then '.git'
50
+ end
51
+ end
52
+
53
+ def git_config; File.join(git_dir,'config') end
54
+
55
+ end
56
+
57
+ def initialize opts = {}
58
+ self.config_dir = opts[:config_dir]
59
+ project_id = opts[:project_id] || Todone::MessageProcessor.load_project_id
60
+ @pp = Todone::PivotalPuller.new(:project_id => project_id, :tracker_token => config.api_key) if project_id
61
+ end
62
+
63
+ def add_project project
64
+ return 'missing_project' if project.nil? or project[:id].nil? or project[:users].nil?
65
+ save_project_id project[:id]
66
+ config[project[:id]] = project[:users].split(',')
67
+ config.save config_dir
68
+ return ['missing_hooks_dir', {:project => project}] unless File.exists? File.join('.git','hooks')
69
+
70
+ return self.add_hook project[:id]
71
+ end
72
+
73
+ def add_hook project_id
74
+ if File.exists? Todone::Consts::HOOK_FILE
75
+ return ['exists_pre_commit_hook',{:project_id => project_id}]
76
+ else
77
+ File.open(Todone::Consts::HOOK_FILE,'w') do |f|
78
+ f.write("todone tickets #{project_id} -m")
79
+ end
80
+ FileUtils.chmod 0751, Todone::Consts::HOOK_FILE
81
+ return "pre_commit_hook_updated"
82
+ end
83
+ end
84
+
85
+ def save_project_id project_id
86
+ `git config -f .git/config pivotal.project-id #{project_id}`
87
+ end
88
+
89
+
90
+ def config
91
+ @config ||= Todone::Config.load_config self.config_dir
92
+ end
93
+
94
+ def config_dir
95
+ (@config_dir.nil? && Todone::Consts::CONFIG_DIR) || @config_dir
96
+ end
97
+
98
+ def tickets
99
+ return ["missing_project_id"] if @pp.nil?
100
+ api_data = @pp.pull_stories("started")
101
+ if api_data.class == Hash
102
+ api_data.delete("error")
103
+ else
104
+ ["show_pivotal_stories", :stories => api_data]
105
+ end
106
+ end
107
+
108
+ def write_tickets opts = {}
109
+ file = opts[:file] || Todone::MessageProcessor.commit_msg_file
110
+ if File.exists? file
111
+ File.open(file, 'r+') do |f|
112
+ original_message = f.read
113
+ f.pos = 0
114
+ f.write( view_tickets + original_message )
115
+ end
116
+ else
117
+ puts missing_write_file( :file => file )
118
+ end
119
+ end
120
+
121
+
122
+ def method_missing(method_id, *args)
123
+ super unless match = /view_(.*)/.match(method_id.to_s) and Todone::MessageProcessor.method_defined?(match[1])
124
+ method = match[1]
125
+ view, data = self.send(method, *args)
126
+ return self.send('missing_view',:method=> method) unless Todone::Views.method_defined? view
127
+
128
+ self.send(view, data)
129
+ end
130
+ end
131
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,113 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'yaml'
4
+ require 'mocha'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+
13
+ RSpec.configure do |config|
14
+ config.mock_with :mocha
15
+ end
16
+
17
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '../..', 'lib'))
18
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
19
+
20
+ require 'todone'
21
+ module FSHelp
22
+ TMP_DIR = '/tmp'
23
+ CONFIG_DIR = File.join(TMP_DIR, '.todone_config')
24
+ ABS_CONFIG_FILE = File.join(CONFIG_DIR, Todone::Consts::CONFIG_FILE)
25
+
26
+ def clean_test_config!
27
+ %x(rm -rf #{CONFIG_DIR}) if File.exists? CONFIG_DIR
28
+ end
29
+
30
+ def dummy_config
31
+ {:owner => 'name',:api_key => 'alkjlkjlkjfd512452354'}
32
+ end
33
+
34
+ def ensure_fresh_config!
35
+ clean_test_config!
36
+ %x(mkdir -p #{CONFIG_DIR}) unless File.exists? CONFIG_DIR
37
+
38
+ File.open(ABS_CONFIG_FILE, 'w') {|f| f.write(dummy_config.to_yaml) }
39
+ end
40
+
41
+ end
42
+ include FSHelp
43
+
44
+ module PivotStories
45
+
46
+ accepted = {
47
+ "id"=>10622339,
48
+ "project_id"=>232039,
49
+ "story_type"=>"feature",
50
+ "url"=>"http://www.pivotaltracker.com/story/show/10622339",
51
+ "estimate"=>1,
52
+ "current_state"=>"accepted",
53
+ "description"=>"specifically the .git hooks. I'm leaving off the config settings for now",
54
+ "name"=>"remove the file manipulation in the tests",
55
+ "requested_by"=>"Austin Flores",
56
+ "owned_by"=>"Austin Flores",
57
+ "created_at"=>"2011-03-02 06:38:25 UTC",
58
+ "updated_at"=>"2011-03-14 01:43:01 UTC",
59
+ "accepted_at"=>"2011-03-14 01:43:01 UTC"
60
+ }
61
+
62
+ unscheduled = {
63
+ "id"=>9958691,
64
+ "project_id"=>232039,
65
+ "story_type"=>"feature",
66
+ "url"=>"http://www.pivotaltracker.com/story/show/9958691",
67
+ "estimate"=>1,
68
+ "current_state"=>"unscheduled",
69
+ "description"=>"returns the tickets/chores/bugs from pivotal tracker that have been completed/started for the current day.\n",
70
+ "name"=>"PivotalPuller grabs the day_stats from pivotal",
71
+ "requested_by"=>"Austin Flores",
72
+ "created_at"=>"2011-02-14 06:49:52 UTC",
73
+ "updated_at"=>"2011-02-14 06:50:17 UTC"
74
+ }
75
+
76
+ unstarted = {
77
+ "id"=>11211881,
78
+ "project_id"=>232039,
79
+ "story_type"=>"feature",
80
+ "url"=>"http://www.pivotaltracker.com/story/show/11211881",
81
+ "estimate"=>2, "current_state"=>"unstarted",
82
+ "description"=>"* Add a method that finds the other users on the project\n* gets all of the tickets that have been started/finished by them today\n*display that to the screen",
83
+ "name"=>"Add a daily_update method",
84
+ "requested_by"=>"Austin Flores",
85
+ "created_at"=>"2011-03-17 05:02:46 UTC",
86
+ "updated_at"=>"2011-03-17 05:02:51 UTC"
87
+ }
88
+ delivered = {
89
+ "id"=>11255061,
90
+ "project_id"=>232039,
91
+ "story_type"=>"feature",
92
+ "url"=>"http://www.pivotaltracker.com/story/show/11255061",
93
+ "estimate"=>1, "current_state"=>"delivered",
94
+ "description"=>"if no project-id is given check git config",
95
+ "name"=>"Read project id from git config",
96
+ "requested_by"=>"Austin Flores",
97
+ "owned_by"=>"Austin Flores",
98
+ "created_at"=>"2011-03-18 01:08:32 UTC",
99
+ "updated_at"=>"2011-03-30 04:46:41 UTC"
100
+ }
101
+
102
+ end
103
+
104
+ include PivotStories
105
+
106
+ class Class
107
+ def publicize_methods
108
+ saved_private_instance_methods = self.private_instance_methods
109
+ self.class_eval { public *saved_private_instance_methods }
110
+ yield
111
+ self.class_eval { private *saved_private_instance_methods }
112
+ end
113
+ end
@@ -0,0 +1,74 @@
1
+ require 'helper'
2
+ require 'yaml'
3
+
4
+ describe Config do
5
+ context "class" do
6
+ describe "self#load_config" do
7
+ before(:each) { FSHelp::ensure_fresh_config! }
8
+ after(:each) { clean_test_config! }
9
+
10
+ it "should properly load the config file" do
11
+ Todone::Config.load_config(FSHelp::CONFIG_DIR).to_hash.should == YAML.load(File.open(FSHelp::ABS_CONFIG_FILE).read)
12
+ end
13
+ end
14
+ end
15
+ context "instance" do
16
+ before(:each) do
17
+ @config = Todone::Config.new
18
+ @hash = { :foo => "bar", :baz => "widget" }
19
+ end
20
+
21
+ describe "#update!" do
22
+ it "should properly update and return its data variable" do
23
+ @config.update! @hash
24
+ @config.to_hash.should == @hash
25
+ end
26
+ end
27
+
28
+ describe "self#new" do
29
+ it "should properly instantiate with data" do
30
+ config = Todone::Config.new @hash
31
+ @hash.should == config.to_hash
32
+ end
33
+ end
34
+
35
+ describe "hash setter" do
36
+ it "should work with object getter" do
37
+ @config[:foo] = "bar"
38
+ "bar".should == @config[:foo]
39
+ end
40
+ end
41
+
42
+ describe "object setter" do
43
+ it "should work with object getter" do
44
+ @config.foo = "bar"
45
+ "bar".should == @config.foo
46
+ end
47
+ end
48
+
49
+ describe "#save" do
50
+ it "should write out a yaml version of the @data attribute" do
51
+ FSHelp::ensure_fresh_config!
52
+ hash = { :user => :dr_rumack, :nickname => 'Shirley' }
53
+ config = Todone::Config.new hash
54
+ config.save FSHelp::CONFIG_DIR
55
+ YAML.load(File.open(FSHelp::ABS_CONFIG_FILE).read).to_hash.should == config.to_hash
56
+ end
57
+ end
58
+
59
+ describe "#to_hash" do
60
+ it "should return a recursively hashified version of the @data attribute" do
61
+ #this hash should cover my bases as far as being deep
62
+ hash = {
63
+ :frank_drebbin => {
64
+ :guns => ['gun1','gun2','crome_dome'],
65
+ :friends=> {
66
+ :nordberg => { :football_skills => 'teh_best' }
67
+ }
68
+ }
69
+ }
70
+ Todone::Config.new(hash).to_hash.should == hash
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,169 @@
1
+ require 'helper'
2
+
3
+ describe Todone::MessageProcessor do
4
+ before(:each) { FSHelp::clean_test_config! }
5
+ describe "self#needs_init?" do
6
+ it "should return true there is no config.yml" do
7
+ Todone::MessageProcessor.needs_init?(FSHelp::CONFIG_DIR).should == true
8
+ end
9
+
10
+ it "should return false when there is a config.yml" do
11
+ ensure_fresh_config!
12
+ Todone::MessageProcessor.needs_init?(FSHelp::CONFIG_DIR).should == false
13
+ end
14
+ end
15
+
16
+ describe "self#init" do
17
+ it "should set owner and api_key when given those options" do
18
+ options = { :dir => FSHelp::CONFIG_DIR }.merge(FSHelp::dummy_config)
19
+ Todone::MessageProcessor.init options
20
+ config = File.open( FSHelp::ABS_CONFIG_FILE ) { |yf| YAML::load( yf ) }
21
+ config.should == FSHelp::dummy_config
22
+ end
23
+
24
+ it "should return nil if :api_key or :owner arent passed in" do
25
+ options = { :dir => FSHelp::CONFIG_DIR }
26
+ Todone::MessageProcessor.init(options).should == nil
27
+ end
28
+ end
29
+
30
+ #TODO remove file work and replace with some stubs
31
+ describe "#add_project" do
32
+ before(:each) do
33
+ FSHelp::ensure_fresh_config!
34
+ @mp = Todone::MessageProcessor.new(:config_dir => FSHelp::CONFIG_DIR)
35
+ @mp.stubs(:save_project_id).returns(nil);
36
+ @project = { :users => 'frank_drebbin', :id => '555555' }
37
+ end
38
+
39
+ it "should edit a project in config if project is present" do
40
+ @mp.stubs(:add_hook).returns("called")
41
+ @mp.add_project @project
42
+ config = File.open( FSHelp::ABS_CONFIG_FILE ) { |yf| YAML::load( yf ) }
43
+ config.should == FSHelp::dummy_config.merge({:'555555'=> ["frank_drebbin"]})
44
+ @mp.add_project @project
45
+ config = File.open( FSHelp::ABS_CONFIG_FILE ) { |yf| YAML::load( yf ) }
46
+ config.should == FSHelp::dummy_config.merge({:'555555'=> ["frank_drebbin"]})
47
+ end
48
+
49
+ it "should add a hook to .git/hooks/pre-commit if not present" do
50
+ File.stubs(:exists?).with(File.join('.git','hooks')).returns(true)
51
+ @mp.stubs(:add_hook).returns("called")
52
+ @mp.add_project(@project).should == "called"
53
+ end
54
+
55
+ #TODO remove text from return statement
56
+ it "should not add a hook if .git/hooks/pre-commit is present" do
57
+ File.stubs(:exists?).returns(true)
58
+ data = @mp.add_project(@project)
59
+ data.shift.should == "exists_pre_commit_hook"
60
+ end
61
+
62
+ it "should not add a hook if .git/hooks/ cannot be found" do
63
+ File.stubs(:exists?).returns(false)
64
+ data = @mp.add_project(@project)
65
+ data.shift.should == 'missing_hooks_dir'
66
+ end
67
+ end
68
+
69
+ describe "#load_project_id" do
70
+ it "should return the project_id if one exists in .git/config file" do
71
+ end
72
+ it "should return nil when a project_id does not exist in the .git/config file" do
73
+
74
+ end
75
+ end
76
+
77
+ describe "#tickets" do
78
+
79
+ it "should return show_pivotal_stories when project_id not passed to construct but exists in git config" do
80
+ Todone::MessageProcessor.stubs(:load_project_id).returns(nil)
81
+ @mp = Todone::MessageProcessor.new
82
+ data = @mp.tickets
83
+ data.shift.should == 'missing_project_id'
84
+ end
85
+
86
+ it "should return show_pivotal_stories when stories are returned" do
87
+ @mp = Todone::MessageProcessor.new({:project_id => 5})
88
+ Todone::PivotalPuller.stubs(:get).returns({'stories'=> []})
89
+ data = @mp.tickets
90
+ data.shift.should == "show_pivotal_stories"
91
+ end
92
+
93
+ it "should return api_problem when an api error" do
94
+ @mp = Todone::MessageProcessor.new({:project_id => 5})
95
+ Todone::PivotalPuller.stubs(:get).raises(SocketError)
96
+ @mp.tickets.should == "api_problem"
97
+ end
98
+
99
+ it "should return bad_state when passed a non-existant ticket state"
100
+
101
+ end
102
+ describe "#write_tickets" do
103
+ before(:each) do
104
+ @mp = Todone::MessageProcessor.new({:project_id => 5})
105
+ end
106
+
107
+ #TODO: figure out a better way to test this
108
+ it "should write to a file if there are stories and no file is specified" do
109
+ Todone::PivotalPuller.stubs(:get).returns({'stories'=> ['thing']})
110
+ File.expects(:open)
111
+ @mp.write_tickets
112
+ end
113
+
114
+ it "should not write to a file if there are no stories" do
115
+ Todone::PivotalPuller.stubs(:get).returns({'stories'=> []})
116
+ File.expects(:write).never
117
+ @mp.write_tickets
118
+ end
119
+
120
+ it "should call a missing_write_file view if a bad write file is specified" do
121
+ @mp.expects(:missing_write_file).returns("")
122
+ @mp.write_tickets :file => 'nonexistant_file_is_nonexistant'
123
+ end
124
+
125
+ end
126
+
127
+ describe "#commit_msg_file" do
128
+
129
+ before(:each) do
130
+ @mp = Todone::MessageProcessor.new({:project_id => 5})
131
+ end
132
+
133
+ it "should return ../COMMIT_EDITMSG if current dir is hooks" do
134
+ Dir.stubs(:getwd).returns('hooks')
135
+ Todone::MessageProcessor.commit_msg_file.should == '../COMMIT_EDITMSG'
136
+ end
137
+
138
+ it "should return .git/COMMIT_EDITMSG if current dir is base pj dir" do
139
+ Dir.stubs(:exists?).returns(true)
140
+ Todone::MessageProcessor.commit_msg_file.should == '.git/COMMIT_EDITMSG'
141
+ end
142
+
143
+ it "Not sure what to do if in a different directory than preious 2"
144
+ end
145
+
146
+ describe "dynamic view methods" do
147
+
148
+ it "should exist if a method exists" do
149
+ Todone::MessageProcessor.method_defined?('tickets').should == true
150
+ @mp = Todone::MessageProcessor.new({:project_id => 5})
151
+ @mp.view_tickets.class.should == String
152
+ end
153
+
154
+ it "should raise a method_missing error if the method does not exist" do
155
+ Todone::MessageProcessor.method_defined?('get_me_a_coke').should_not == true
156
+ @mp = Todone::MessageProcessor.new({:project_id => 5})
157
+ lambda {@mp.write_get_me_a_coke}.should raise_error NoMethodError
158
+ end
159
+
160
+ it "should call the missing template view method when view does not exist" do
161
+ @mp = Todone::MessageProcessor.new({:project_id => 5})
162
+ Todone::MessageProcessor.method_defined?('tickets').should == true
163
+ @mp.expects(:missing_view)
164
+ Todone::Views.stubs(:method_defined?).returns(false)
165
+ @mp.view_tickets
166
+ end
167
+
168
+ end
169
+ end
@@ -0,0 +1,47 @@
1
+ require 'helper'
2
+ require 'httparty'
3
+
4
+
5
+ describe Todone::PivotalPuller do
6
+ describe "self#pull_stories" do
7
+ it "should return stories when they are available" do
8
+ Todone::PivotalPuller.stubs(:get).returns({'stories'=> [{"id"=> 'valid_id'}]})
9
+ pp = Todone::PivotalPuller.new( :project_id => 100000, :api_key => 'blabbityboo' )
10
+ pp.pull_stories('started').class.to_s.should == 'Array'
11
+ end
12
+
13
+ # TODO: figure out how to test this, api_key stored in config, don't want to stub the call, is this important?
14
+ it "should return an empty array when a bad project_id is given" do
15
+ #pp = Todone::PivotalPuller.new( :project_id => 1, :api_key => 'blabbityboo' )
16
+ #pp.pull_stories('started').class.to_s.should == 'Array'
17
+ end
18
+
19
+ it "should return an empty array when no stories are available" do
20
+ Todone::PivotalPuller.stubs(:get).returns({'stories'=> []})
21
+ pp = Todone::PivotalPuller.new( :project_id => 100000, :api_key => 'blabbityboo' )
22
+ pp.pull_stories('started').class.to_s.should == 'Array'
23
+ end
24
+
25
+ it "should return an error if a bad state is requested" do
26
+ pp = Todone::PivotalPuller.new( :project_id => 100000, :api_key => 'blabbityboo' )
27
+ pp.pull_stories('unnavailable_state')['error'].should == 'invalid_state'
28
+ end
29
+
30
+ it "should return error when site unreachable" do
31
+ Todone::PivotalPuller.stubs(:get).raises(SocketError)
32
+ pp = Todone::PivotalPuller.new( :project_id => 100000, :api_key => 'blabbityboo' )
33
+ pp.pull_stories('started')['error'].should == 'api_problem'
34
+ end
35
+
36
+ it "should return an array of stories if given the state: unscheduled, started, unstarted, accepted or delivered" do
37
+ Todone::PivotalPuller.stubs(:get).returns({'stories'=> [{"id"=> 'valid_id'}]})
38
+ pp = Todone::PivotalPuller.new( :project_id => 100000, :api_key => 'blabbityboo' )
39
+ %w(unscheduled started unstarted accepted delivered).each do |state|
40
+ return_statement = pp.pull_stories(state)
41
+ return_statement.class.should == Array
42
+ return_statement[0]['id'].should_not == nil
43
+ end
44
+
45
+ end
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: todone
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.2.2
6
+ platform: ruby
7
+ authors:
8
+ - A Flores
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-05-23 00:00:00 +02:00
14
+ default_executable: todone
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: commander
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - "="
22
+ - !ruby/object:Gem::Version
23
+ version: 4.0.3
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: crack
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - "="
33
+ - !ruby/object:Gem::Version
34
+ version: 0.1.8
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: httparty
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - "="
44
+ - !ruby/object:Gem::Version
45
+ version: 0.7.4
46
+ type: :runtime
47
+ prerelease: false
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: rspec
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: mocha
62
+ requirement: &id005 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - "="
66
+ - !ruby/object:Gem::Version
67
+ version: 0.9.12
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: bundler
73
+ requirement: &id006 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ~>
77
+ - !ruby/object:Gem::Version
78
+ version: 1.0.0
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: *id006
82
+ - !ruby/object:Gem::Dependency
83
+ name: jeweler
84
+ requirement: &id007 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: 1.5.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: *id007
93
+ - !ruby/object:Gem::Dependency
94
+ name: rcov
95
+ requirement: &id008 !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: "0"
101
+ type: :development
102
+ prerelease: false
103
+ version_requirements: *id008
104
+ description: Toone is a command-line tool that gives you the ability to better integrate git with your project manager(which is currently only allowed to be pivotal tracker). Todone will keep track of the tickets your working on so you don't have to.
105
+ email: ctrl4ltdeleteme@gmail.com
106
+ executables:
107
+ - todone
108
+ extensions: []
109
+
110
+ extra_rdoc_files:
111
+ - LICENSE.txt
112
+ - README.md
113
+ files:
114
+ - .document
115
+ - .rvmrc
116
+ - Gemfile
117
+ - Gemfile.lock
118
+ - LICENSE.txt
119
+ - README.md
120
+ - Rakefile
121
+ - VERSION
122
+ - bin/todone
123
+ - git_config_read_test
124
+ - lib/todone.rb
125
+ - lib/todone/config.rb
126
+ - lib/todone/consts.rb
127
+ - lib/todone/pivotal_puller.rb
128
+ - lib/todone/views.rb
129
+ - spec/helper.rb
130
+ - spec/libs/config_spec.rb
131
+ - spec/libs/message_processor_spec.rb
132
+ - spec/libs/pivotal_puller_spec.rb
133
+ has_rdoc: true
134
+ homepage: http://github.com/unflores/todone
135
+ licenses:
136
+ - MIT
137
+ post_install_message:
138
+ rdoc_options: []
139
+
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ hash: 1320610435493697253
148
+ segments:
149
+ - 0
150
+ version: "0"
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ none: false
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: "0"
157
+ requirements: []
158
+
159
+ rubyforge_project:
160
+ rubygems_version: 1.6.2
161
+ signing_key:
162
+ specification_version: 3
163
+ summary: Todone is a command-line tool that gives you the ability to better integrate git with your pivotal project.
164
+ test_files:
165
+ - spec/helper.rb
166
+ - spec/libs/config_spec.rb
167
+ - spec/libs/message_processor_spec.rb
168
+ - spec/libs/pivotal_puller_spec.rb