gitcamp 0.0.3 → 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.md CHANGED
@@ -22,8 +22,55 @@ to check. If not, you'll need to [install it](http://rubygems.org/pages/download
22
22
  $ gitcamp
23
23
  Commands:
24
24
  gitcamp help [COMMAND] # Describe available commands or one specific command
25
- gitcamp todolist # creates BaseCamp todo list from a milestone of GitHub issues
25
+ gitcamp milestone # creates GitHub milestone & issues from a Basecamp todo list
26
+ gitcamp todolist # creates Basecamp todo list from a milestone of GitHub issues
26
27
 
27
- Creating a todo list in Basecamp
28
+ #### Creating a GitHub milestone & issues from a Basecamp todo list
29
+
30
+ $ gitcamp milestone
31
+
32
+ Follow the instructions provided. This command will perform the following:
33
+
34
+ 1. Ask you to login to Basecamp
35
+ 2. Ask you to select a Basecamp project
36
+ 3. Ask you to select a Basecamp todo list from that project
37
+ 4. Ask you to login to GitHub
38
+ 5. Ask you to enter a GitHub repository name (eg. owner/repo)
39
+ 6. Ask you to confirm what you're about to do
40
+ 7. Find or create a milestone in GitHub with the name of the todo list
41
+ 8. Create or update an issue for each todo
42
+ 9. Tag each issue with the label `basecamp`
43
+ 10. Update the Basecamp todo with the issue number of the newly created GitHub issue
44
+
45
+ Creating a Basecamp todo list from a milestone of GitHub issues
28
46
 
29
47
  $ gitcamp todolist
48
+
49
+ Follow the instructions provided. This command will perform the following:
50
+
51
+ 1. Ask you to login to GitHub
52
+ 2. Ask you to enter a GitHub repository name (eg. owner/repo)
53
+ 3. Ask you to select a GitHub milestone from that repository
54
+ 4. Ask you to login to Basecamp
55
+ 5. Ask you to select a Basecamp project
56
+ 6. Ask you to confirm what you're about to do
57
+ 7. Find or create a todolist in Basecamp with the name of the milestone
58
+ 8. Create an todo for each issue
59
+ 9. Add the issue description as a comment
60
+ 10. Add the issue number of the GitHub issue to the front of the todo
61
+
62
+ #### Login credentials
63
+
64
+ You will only be asked for your GitHub and Basecamp credentials the first time you authenticate. After that they
65
+ will be stored on your computer so you don't have to enter them each time.
66
+
67
+ > TODO: Add a `--force-login` or `--clear-credentials` flag to allow users to login again
68
+
69
+ #### Idempotence
70
+
71
+ Gitcamp is designed to be idempotent, that is to say you can run it multiple times (selecting the same milestone and todolist)
72
+ and it will not duplicate todos or issues.
73
+
74
+ For example, if you sync a Basecamp todolist to a GitHub milestone once, then add another todo it will add that todo as an issue and leave
75
+ all the other issues in place.
76
+
@@ -4,17 +4,24 @@ require 'thor'
4
4
  require 'octokit'
5
5
  require 'highline'
6
6
  require 'basecamp'
7
- require 'hashie/mash'
7
+
8
+ BasecampClassic = ::Basecamp
8
9
 
9
10
  require 'gitcamp/version'
10
11
  require 'gitcamp/config'
11
- require 'gitcamp/github/auth'
12
- require 'gitcamp/basecamp/auth'
13
- require 'gitcamp/basecamp/todo'
14
- require 'gitcamp/cli'
15
12
 
16
- module Basecamp
17
- module API
18
- Classic = ::Basecamp
13
+ module Gitcamp
14
+ autoload :Cli, 'gitcamp/cli'
15
+
16
+ module Basecamp
17
+ autoload :Auth, 'gitcamp/basecamp/auth'
18
+ autoload :Todo, 'gitcamp/basecamp/todo'
19
+ end
20
+
21
+ module Github
22
+ autoload :Auth, 'gitcamp/github/auth'
23
+ autoload :Milestone, 'gitcamp/github/milestone'
24
+ autoload :Issue, 'gitcamp/github/issue'
19
25
  end
20
26
  end
27
+
@@ -15,30 +15,30 @@ module Gitcamp
15
15
  if netrc[domain]
16
16
  @username, token = netrc[domain]
17
17
 
18
- Basecamp::API::Classic.establish_connection!(domain, token, 'X', true)
18
+ BasecampClassic.establish_connection!(domain, token, 'X', true)
19
19
 
20
20
  begin
21
- Basecamp::API::Classic::Person.me
21
+ BasecampClassic::Person.me
22
22
  say "Authenticated as #{@username}", Thor::Shell::Color::GREEN
23
23
  rescue Octokit::Unauthorized => ex
24
- puts "Failed to authenticate you with those Basecamp credentials"
24
+ say "Failed to authenticate you with those Basecamp credentials", Thor::Shell::Color::RED
25
25
  exit false
26
26
  end
27
27
  else
28
28
  @username = ask 'Username:'
29
29
  password = ask_password 'Password:'
30
30
 
31
- Basecamp::API::Classic.establish_connection!(domain, @username, password, true)
31
+ BasecampClassic.establish_connection!(domain, @username, password, true)
32
32
 
33
33
  begin
34
- Basecamp::API::Classic::Person.me
34
+ BasecampClassic::Person.me
35
35
  say "Authenticated as #{@username}", Thor::Shell::Color::GREEN
36
36
  rescue Octokit::Unauthorized => ex
37
- puts "Failed to authenticate you with those Basecamp credentials"
37
+ say "Failed to authenticate you with those Basecamp credentials", Thor::Shell::Color::RED
38
38
  exit false
39
39
  end
40
40
 
41
- token = Basecamp::API::Classic::Person.me.token
41
+ token = BasecampClassic::Person.me.token
42
42
 
43
43
  unless netrc[domain]
44
44
  netrc[domain] = @username, token
@@ -10,6 +10,7 @@ module Gitcamp
10
10
 
11
11
  @list = options[:list]
12
12
  @issue = options[:issue]
13
+ @find_by_title = options[:find_by_title]
13
14
 
14
15
  @title = @issue.title.gsub(/\[[\d\.]*\]\s+/, '')
15
16
  @title = "##{issue.number} #{@title}"
@@ -19,7 +20,7 @@ module Gitcamp
19
20
  find_or_build_todo
20
21
 
21
22
  if item.save
22
- say "Processed todo #{title} in Basecamp"
23
+ say "Processed todo '#{title}' in Basecamp"
23
24
  end
24
25
 
25
26
  create_or_update_comment
@@ -28,13 +29,18 @@ module Gitcamp
28
29
  private
29
30
 
30
31
  def find_or_build_todo
31
- todo_items = Basecamp::API::Classic::TodoItem.find(:all, :params => { :todo_list_id => list.id })
32
- @item = todo_items.find { |tdi| tdi.content =~ %r{\A##{issue.number}} }
32
+ todo_items = BasecampClassic::TodoItem.find(:all, :params => { :todo_list_id => list.id })
33
+
34
+ if @find_by_title
35
+ @item = todo_items.find { |tdi| tdi.content == issue.title }
36
+ else
37
+ @item = todo_items.find { |tdi| tdi.content =~ %r{\A##{issue.number}} }
38
+ end
33
39
 
34
40
  if @item
35
41
  @item.content = @title
36
42
  else
37
- @item = Basecamp::API::Classic::TodoItem.new(:content => @title, :notify => false, :todo_list_id => list.id)
43
+ @item = BasecampClassic::TodoItem.new(:content => @title, :notify => false, :todo_list_id => list.id)
38
44
  end
39
45
  end
40
46
 
@@ -42,7 +48,7 @@ module Gitcamp
42
48
  return if issue.body.blank?
43
49
 
44
50
  comment = @item.comments.find { |cm| cm.body =~ %r{\A\[##{issue.number}\]} }
45
- comment ||= Basecamp::API::Classic::Comment.new(:todo_item_id => item.id, :use_textile => true)
51
+ comment ||= BasecampClassic::Comment.new(:todo_item_id => item.id, :use_textile => true)
46
52
 
47
53
  comment.body = "[##{issue.number}] *Description:* #{issue.body}"
48
54
  comment.save
@@ -4,8 +4,10 @@ module Gitcamp
4
4
  class Cli < Thor
5
5
  include Thor::Actions
6
6
 
7
- desc 'todolist', 'creates BaseCamp todo list from a milestone of GitHub issues'
7
+ desc 'todolist', 'creates Basecamp todo list from a milestone of GitHub issues'
8
8
  def todolist
9
+ say "\n Creating a Basecamp todo list from a milestone of GitHub issues"
10
+ say " Press ^C at any time to abort\n\n"
9
11
 
10
12
  # Authenicate with Github
11
13
  auth = Gitcamp::Github::Auth.new
@@ -18,8 +20,9 @@ module Gitcamp
18
20
  milestones = @github.list_milestones(repo)
19
21
 
20
22
  # Ask user to select a milestone to use
23
+ say "\n Which GitHub milestone do you want to import?\n\n"
21
24
  choose do |menu|
22
- menu.prompt = 'Which milestone do you want to import?'
25
+ menu.prompt = 'Enter a number from the list above:'
23
26
  milestones.each do |milestone|
24
27
  menu.choice "#{milestone.title} (#{milestone.open_issues} open issues)" do
25
28
  @milestone = milestone
@@ -34,11 +37,12 @@ module Gitcamp
34
37
  auth.login
35
38
 
36
39
  # Get all active projects
37
- projects = Basecamp::API::Classic::Project.all.select { |project| project.status == 'active' }
40
+ projects = BasecampClassic::Project.all.select { |project| project.status == 'active' }
38
41
 
39
42
  # Ask user to select a project
43
+ say "\n Which Basecamp project do you want to create the todo list in?\n\n"
40
44
  choose do |menu|
41
- menu.prompt = 'Which project do you want to create the TODO list in?'
45
+ menu.prompt = 'Enter a number from the list above:'
42
46
  projects.each do |project|
43
47
  menu.choice "#{project.name}" do
44
48
  @project = project
@@ -48,16 +52,18 @@ module Gitcamp
48
52
 
49
53
  say "Chosen project: #{@project.name}"
50
54
 
55
+ say "\n Read and confirm the action below:\n\n"
56
+
51
57
  # Confirm pending operation to user?
52
- if yes? "Are you sure you want to create a Todo list named '#{@milestone.title}' in the #{@project.name} Basecamp project and add the #{@milestone.open_issues} issues as Todo items? (y/n)", Thor::Shell::Color::CYAN
58
+ if yes? "Are you sure you want to create (or update) a Basecamp todo list named '#{@milestone.title}' in the '#{@project.name}' project and add the #{@milestone.open_issues} GitHub issues as Basecamp todo items? (y/n)", Thor::Shell::Color::CYAN
53
59
 
54
60
  # Try to find an existing TodoList in Basecamp
55
- todo_lists = Basecamp::API::Classic::TodoList.all(@project.id)
61
+ todo_lists = BasecampClassic::TodoList.all(@project.id)
56
62
  todo_list = todo_lists.find { |tdl| tdl.name == @milestone.title }
57
63
 
58
64
  # If it doesn't exist, create one
59
65
  unless todo_list
60
- todo_list = Basecamp::API::Classic::TodoList.new(:name => @milestone.title, :description => @milestone.description, :project_id => @project.id)
66
+ todo_list = BasecampClassic::TodoList.new(:name => @milestone.title, :description => @milestone.description, :project_id => @project.id)
61
67
  todo_list.save
62
68
 
63
69
  say "Created todo list: #{todo_list.name}"
@@ -77,5 +83,75 @@ module Gitcamp
77
83
  end
78
84
  end
79
85
 
86
+ desc 'milestone', 'creates GitHub milestone & issues from a Basecamp todo list'
87
+ def milestone
88
+ say "\n Creating a GitHub milestone & issues from a Basecamp todo list"
89
+ say " Press ^C at any time to abort\n\n"
90
+
91
+ # Authenticate with Basecamp
92
+ auth = Gitcamp::Basecamp::Auth.new
93
+ auth.login
94
+
95
+ # Get all active projects
96
+ projects = BasecampClassic::Project.all.select { |project| project.status == 'active' }
97
+
98
+ # Ask user to select a project
99
+ say "\n Which Basecamp project is the todo list in?\n\n"
100
+ choose do |menu|
101
+ menu.prompt = 'Enter a number from the list above:'
102
+ projects.each do |project|
103
+ menu.choice "#{project.name}" do
104
+ @project = project
105
+ end
106
+ end
107
+ end
108
+
109
+ say "Chosen project: #{@project.name}"
110
+
111
+ todolists = BasecampClassic::TodoList.all(@project.id)
112
+
113
+ # Ask user to select a todolist to use
114
+ say "\n Which Basecamp todo list do you want to import?\n\n"
115
+ choose do |menu|
116
+ menu.prompt = 'Enter a number from the list above:'
117
+ todolists.each do |todolist|
118
+ menu.choice "#{todolist.name} (#{todolist.uncompleted_count} incomplete todos)" do
119
+ @todolist = todolist
120
+ end
121
+ end
122
+ end
123
+
124
+ say "Chosen todo list: #{@todolist.name}"
125
+
126
+ # Authenicate with Github
127
+ auth = Gitcamp::Github::Auth.new
128
+ @github = auth.login
129
+
130
+ # What repo are we adding to?
131
+ repo = ask 'Enter a GitHub repo name (eg. owner/repo) to create the milestone in:'
132
+
133
+ # Confirm pending operation to user?
134
+ say "\n Read and confirm the action below:\n\n"
135
+
136
+ if yes? "Are you sure you want to create (or update) a GitHub milestone named '#{@todolist.name}' in the repo '#{repo}' and add the #{@todolist.uncompleted_count} todos in Basecamp todo list '#{@todolist.name}' as issues? (y/n)", Thor::Shell::Color::CYAN
137
+
138
+ # Find or create a milestone
139
+ milestone = Gitcamp::Github::Milestone.new(github: @github, repo: repo, todolist: @todolist)
140
+ milestone.save
141
+
142
+ todo_items = BasecampClassic::TodoItem.find(:all, :params => { :todo_list_id => @todolist.id })
143
+
144
+ todo_items.each do |todo_item|
145
+ next if todo_item.completed
146
+
147
+ issue = Gitcamp::Github::Issue.new(github: @github, milestone: milestone, repo: repo, todo: todo_item, todolist: @todolist)
148
+ issue.save
149
+ end
150
+
151
+ else
152
+ say 'No action taken, exiting'
153
+ exit true
154
+ end
155
+ end
80
156
  end
81
157
  end
@@ -0,0 +1,37 @@
1
+ module Gitcamp
2
+ module Github
3
+ class Issue
4
+ attr_reader :todo, :todolist, :github, :milestone, :issue, :repo, :title
5
+
6
+ def initialize(options = {})
7
+ unless options[:todo]
8
+ raise ArgumentError, 'no Basecamp todo given'
9
+ end
10
+
11
+ @github = options[:github]
12
+ @repo = options[:repo]
13
+ @milestone = options[:milestone]
14
+ @todo = options[:todo]
15
+ @todolist = options[:todolist]
16
+
17
+ @title = @todo.content.gsub(/#\d+\s+/, '')
18
+ end
19
+
20
+ def save
21
+ issues = @github.list_issues(repo, milestone: milestone.number)
22
+ @issue = issues.find { |issue| issue.title == title }
23
+
24
+ if @issue
25
+ @issue = @github.update_issue(repo, issue.number, issue.title, issue.body, milestone: milestone.number, labels: 'basecamp')
26
+ else
27
+ @issue = @github.create_issue(repo, title, '', milestone: milestone.number, labels: 'basecamp')
28
+
29
+ todo = Gitcamp::Basecamp::Todo.new(list: todolist, issue: issue, find_by_title: true)
30
+ todo.save
31
+ end
32
+
33
+ say "Processed issue '#{issue.title}' in GitHub"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ module Gitcamp
2
+ module Github
3
+ class Milestone
4
+ attr_reader :todolist, :title, :milestone, :repo
5
+
6
+ def initialize(options = {})
7
+ unless options[:todolist]
8
+ raise ArgumentError, 'no Basecamp todolist given'
9
+ end
10
+
11
+ @repo = options[:repo]
12
+ @github = options[:github]
13
+ @todolist = options[:todolist]
14
+ end
15
+
16
+ def number
17
+ @milestone.number
18
+ end
19
+
20
+ def save
21
+ milestones = @github.list_milestones(repo)
22
+ @milestone = milestones.find { |mls| mls.title == todolist.name.strip }
23
+
24
+ unless @milestone
25
+ @milestone = @github.create_milestone(repo, @todolist.name)
26
+ end
27
+
28
+ if @milestone
29
+ say "Using milestone '#{milestone.title}' in GitHub #{repo}"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,3 +1,3 @@
1
1
  module Gitcamp
2
- VERSION = '0.0.3'
2
+ VERSION = '0.1.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitcamp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-18 00:00:00.000000000 Z
12
+ date: 2013-05-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
16
- requirement: !ruby/object:Gem::Requirement
16
+ requirement: &70103963145420 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,15 +21,10 @@ dependencies:
21
21
  version: 0.18.1
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ~>
28
- - !ruby/object:Gem::Version
29
- version: 0.18.1
24
+ version_requirements: *70103963145420
30
25
  - !ruby/object:Gem::Dependency
31
26
  name: octokit
32
- requirement: !ruby/object:Gem::Requirement
27
+ requirement: &70103963171080 !ruby/object:Gem::Requirement
33
28
  none: false
34
29
  requirements:
35
30
  - - ~>
@@ -37,15 +32,10 @@ dependencies:
37
32
  version: 1.24.0
38
33
  type: :runtime
39
34
  prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ~>
44
- - !ruby/object:Gem::Version
45
- version: 1.24.0
35
+ version_requirements: *70103963171080
46
36
  - !ruby/object:Gem::Dependency
47
37
  name: highline
48
- requirement: !ruby/object:Gem::Requirement
38
+ requirement: &70103963169440 !ruby/object:Gem::Requirement
49
39
  none: false
50
40
  requirements:
51
41
  - - ~>
@@ -53,15 +43,10 @@ dependencies:
53
43
  version: 1.6.19
54
44
  type: :runtime
55
45
  prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
- requirements:
59
- - - ~>
60
- - !ruby/object:Gem::Version
61
- version: 1.6.19
46
+ version_requirements: *70103963169440
62
47
  - !ruby/object:Gem::Dependency
63
48
  name: json
64
- requirement: !ruby/object:Gem::Requirement
49
+ requirement: &70103963166220 !ruby/object:Gem::Requirement
65
50
  none: false
66
51
  requirements:
67
52
  - - ~>
@@ -69,15 +54,10 @@ dependencies:
69
54
  version: 1.7.7
70
55
  type: :runtime
71
56
  prerelease: false
72
- version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ~>
76
- - !ruby/object:Gem::Version
77
- version: 1.7.7
57
+ version_requirements: *70103963166220
78
58
  - !ruby/object:Gem::Dependency
79
59
  name: basecamp
80
- requirement: !ruby/object:Gem::Requirement
60
+ requirement: &70103963165480 !ruby/object:Gem::Requirement
81
61
  none: false
82
62
  requirements:
83
63
  - - ~>
@@ -85,15 +65,10 @@ dependencies:
85
65
  version: 0.0.9
86
66
  type: :runtime
87
67
  prerelease: false
88
- version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
- requirements:
91
- - - ~>
92
- - !ruby/object:Gem::Version
93
- version: 0.0.9
68
+ version_requirements: *70103963165480
94
69
  - !ruby/object:Gem::Dependency
95
70
  name: rake
96
- requirement: !ruby/object:Gem::Requirement
71
+ requirement: &70103963164640 !ruby/object:Gem::Requirement
97
72
  none: false
98
73
  requirements:
99
74
  - - ~>
@@ -101,15 +76,10 @@ dependencies:
101
76
  version: 10.0.4
102
77
  type: :development
103
78
  prerelease: false
104
- version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
- requirements:
107
- - - ~>
108
- - !ruby/object:Gem::Version
109
- version: 10.0.4
79
+ version_requirements: *70103963164640
110
80
  - !ruby/object:Gem::Dependency
111
81
  name: rspec
112
- requirement: !ruby/object:Gem::Requirement
82
+ requirement: &70103963163900 !ruby/object:Gem::Requirement
113
83
  none: false
114
84
  requirements:
115
85
  - - ~>
@@ -117,12 +87,7 @@ dependencies:
117
87
  version: 2.13.0
118
88
  type: :development
119
89
  prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
- requirements:
123
- - - ~>
124
- - !ruby/object:Gem::Version
125
- version: 2.13.0
90
+ version_requirements: *70103963163900
126
91
  description: Gitcamp is a handy command line tool for syncing GitHub issues & milestones
127
92
  with Basecamp todo lists, and visa-versa.
128
93
  email:
@@ -145,6 +110,8 @@ files:
145
110
  - lib/gitcamp/cli.rb
146
111
  - lib/gitcamp/config.rb
147
112
  - lib/gitcamp/github/auth.rb
113
+ - lib/gitcamp/github/issue.rb
114
+ - lib/gitcamp/github/milestone.rb
148
115
  - lib/gitcamp/version.rb
149
116
  homepage: http://github.com/simpleweb/gitcamp
150
117
  licenses: []
@@ -166,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
166
133
  version: '0'
167
134
  requirements: []
168
135
  rubyforge_project:
169
- rubygems_version: 1.8.24
136
+ rubygems_version: 1.8.16
170
137
  signing_key:
171
138
  specification_version: 3
172
139
  summary: Gitcamp is a handy command line tool for syncing GitHub issues & milestones