gitcamp 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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