octokom 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1e09a35e0cf96d8108161d2e98d3b0bb2eef732d
4
- data.tar.gz: 53ab6973726062831ce6fdea8e5abfe87eead951
3
+ metadata.gz: a827d2870894f3b5f4c778cbc97475081b536758
4
+ data.tar.gz: 925ab8ea02b3347905a44f4752f79348329b2801
5
5
  SHA512:
6
- metadata.gz: 748cbaac055db47202bfb129952bf8656b96054606599ef11790ec8beeeaf34a189266608cb0c1f72d937aafb978aa84335e7377c3573bc71b969beef4aea3c8
7
- data.tar.gz: 1ef8d30e2359cb1d5292f876acaeb306dccc11cb52e8a2edaab8ea5094fc7a793228eb3cf7f2e17dec0769caee7eed5cecce7751bad4fbee5c63d305f243383a
6
+ metadata.gz: 1cb21e97e894109e9b5e05a0a1c30df072d3de2f2a446a197f838ce6c6d07340d4896fcadca62b7007406990bc6cf3ea3d749d0a33d0f22009e05d1da0e7796a
7
+ data.tar.gz: cdde0f3ac7c090bb8ce170091a1ffcd2d5a3849d4d6ec93b5aee7cac933e48d091755d70596c8a2ccd4411797f3cef9f0e9dcc4025fd9fb11195f400a0b5387f
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ spec/support/.netrc
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
1
  --color
2
- --fail-fast
3
2
  --order random
data/.travis.yml CHANGED
@@ -1,15 +1,22 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
+ - rbx-19mode
5
+ - jruby-19mode
6
+ - 1.9.2
4
7
  - 1.9.3
5
8
  - 2.0.0
9
+ - 2.1.0
6
10
  - ruby-head
7
11
 
8
12
  matrix:
9
13
  allow_failures:
14
+ - rvm: rbx-19mode
15
+ - rvm: jruby-19mode
16
+ - rvm: 1.9.2
10
17
  - rvm: ruby-head
11
18
 
12
19
  notifications:
13
20
  email:
14
- on_success: change
21
+ on_success: never
15
22
  on_failure: always
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Martin Jagusch
1
+ Copyright (c) 2013-2014 Martin Jagusch
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Octokom
2
2
 
3
- Work in progress command line application for your every day GitHub workflow.
3
+ Create pull requests, GitHub issues etc. quickly from your command line in three words `octokom pull-request create`. Octokom finds automatically the name of your repository and Git branch.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,10 +8,18 @@ Work in progress command line application for your every day GitHub workflow.
8
8
  $ gem install octokom
9
9
  ```
10
10
 
11
- ## Examples
11
+ ## The Interface
12
12
 
13
- #### Create a new pull request
13
+ Run `octokom --help` to see a list of all supported commands and options.
14
+
15
+ #### Pull Request
14
16
 
15
17
  ```bash
16
18
  $ octokom pull-request create --title "New feature"
17
19
  ```
20
+
21
+ #### GitHub Issue
22
+
23
+ ```bash
24
+ $ octokom issue create --title "Nasty Bug"
25
+ ```
data/bin/octokom CHANGED
@@ -5,4 +5,4 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  require 'octokom'
7
7
 
8
- Octokom.start(*ARGV)
8
+ Octokom::CLI.run
data/lib/octokom.rb CHANGED
@@ -1,15 +1,11 @@
1
- require 'clamp'
2
- require 'octokit'
3
- require 'security'
4
-
5
- require 'octokom/authentication'
6
- require 'octokom/command'
7
- require 'octokom/keychain'
8
- require 'octokom/repository'
9
- require 'octokom/version'
1
+ require 'octokom/core_ext/blank'
10
2
 
11
3
  module Octokom
12
- def self.start(*args)
13
- Octokom::Command.run(args)
14
- end
4
+ autoload :CLI, 'octokom/cli'
5
+ autoload :Client, 'octokom/client'
6
+ autoload :Command, 'octokom/command'
7
+ autoload :Editor, 'octokom/editor'
8
+ autoload :Repository, 'octokom/repository'
9
+ autoload :Shell, 'octokom/shell'
10
+ autoload :VERSION, 'octokom/version'
15
11
  end
@@ -0,0 +1,6 @@
1
+ module Octokom
2
+ class CLI < Command
3
+ sub_command 'issue', 'Create or update GitHub issues.', Issue
4
+ sub_command 'pull-request', 'Create, update or merge pull requests.', PullRequest
5
+ end
6
+ end
@@ -0,0 +1,99 @@
1
+ require 'io/console'
2
+ require 'netrc'
3
+
4
+ module Octokom
5
+ class Client
6
+ private_class_method :new
7
+
8
+ attr_reader :token
9
+
10
+ KEY = 'api.github.com'
11
+ URL = 'https://github.com/mjio/octokom'
12
+
13
+ def self.authenticate
14
+ new.authenticate
15
+ end
16
+
17
+ # Use the existing credentials form the ~/.netrc file to verify API
18
+ # requests to GitHub. When no credentials exist, ask for login and
19
+ # password to request a new token from GitHub.
20
+ def authenticate
21
+ _, @token = netrc[KEY]
22
+
23
+ unless token
24
+ login, password = ask_for_credentials
25
+ create_authorization(login, password)
26
+ end
27
+
28
+ self
29
+ end
30
+
31
+ def client
32
+ require 'octokit'
33
+ Octokit::Client.new(access_token: token)
34
+ end
35
+
36
+ private
37
+
38
+ def path
39
+ @path ||= Netrc.default_path
40
+ end
41
+
42
+ def file
43
+ @file ||= File.open(path, File::RDWR|File::CREAT, 0600)
44
+ end
45
+
46
+ def data
47
+ @data ||= Netrc.parse(Netrc.lex(file.each_line.to_a))
48
+ end
49
+
50
+ def netrc
51
+ @netrc ||= Netrc.new(path, data)
52
+ end
53
+
54
+ def ask_for_credentials
55
+ login = ask_for_login
56
+ password = ask_for_password
57
+ puts # newline
58
+ [login, password]
59
+ end
60
+
61
+ def ask_for_login
62
+ print 'GitHub Login: '
63
+ STDIN.gets.chomp
64
+ end
65
+
66
+ def ask_for_password
67
+ print 'Password: '
68
+ STDIN.noecho(&:gets).chomp
69
+ end
70
+
71
+ def create_authorization(login, password)
72
+ require 'octokit'
73
+
74
+ begin
75
+ client = Octokit::Client.new(login: login, password: password)
76
+ auth = client.create_authorization(scopes: ['repo'], note: api_note, note_url: URL)
77
+ rescue Octokit::Unauthorized
78
+ puts "Unable to authorise user @#{login}"
79
+ exit(1)
80
+ end
81
+
82
+ @token = auth.token
83
+ save_token_to_netrc(login)
84
+ end
85
+
86
+ def save_token_to_netrc(login)
87
+ netrc[KEY] = login, @token
88
+ netrc.save
89
+ end
90
+
91
+ def api_note
92
+ "Octokom #{hostname}@#{Time.now.usec}"
93
+ end
94
+
95
+ def hostname
96
+ `hostname`.chomp
97
+ end
98
+ end
99
+ end
@@ -1,7 +1,70 @@
1
+ require 'clamp'
2
+
3
+ autoload :Issue, 'octokom/command/issue'
4
+ autoload :PullRequest, 'octokom/command/pull_request'
5
+
1
6
  module Octokom
2
7
  class Command < Clamp::Command
3
- autoload :PullRequest, 'octokom/command/pull_request'
8
+ include Octokom::Shell
9
+
10
+ # Shortcut for `Clamp::Command.option` with `:flag` argument.
11
+ #
12
+ # For example:
13
+ # flag ['-a'], A option'
14
+ #
15
+ # Is the same as:
16
+ # option ['-a'], :flag, 'A option'
17
+ def self.flag(tags, desc, &block)
18
+ option_without_type(tags, :flag, desc, &block)
19
+ end
20
+
21
+ # Alias for the `Clamp::Command.option` method that uses the long option
22
+ # tag as type string.
23
+ #
24
+ # For example:
25
+ # Octokom::Command.option ['-a', '--abc'], 'ABC option'
26
+ #
27
+ # Is the same as:
28
+ # Clamp::Command.option ['-a', '--abc'], 'ABC', 'ABC option'
29
+ def self.option_with_type(tags, desc, &block)
30
+ type = tags.first[/^--(.*)$/, 1].upcase
31
+ option_without_type(tags, type, desc, &block)
32
+ end
33
+
34
+ # Override `Clamp::Command.execute` method so it can be used as a block.
35
+ def self.execute(&block)
36
+ define_method(:execute, block) do
37
+ yield if block_given?
38
+ end
39
+ end
40
+
41
+ # Create aliases for the Clamp::Command `option` and `subcommand` methods.
42
+ singleton_class.class_eval do
43
+ alias_method :option_without_type, :option
44
+ alias_method :option, :option_with_type
45
+ alias_method :sub_command, :subcommand
46
+ end
47
+
48
+ flag ['--dry-run', '-y'], 'Do nothing, only show what would happen.'
49
+ flag ['--verbose', '-v'], 'Show the debug output.'
50
+
51
+ flag ['--version'], 'Version number.' do
52
+ puts "Octokom #{Octokom::VERSION}"
53
+ end
54
+
55
+ # Override the Clamp help message option.
56
+ flag ['--help'], 'Help message.' do
57
+ request_help
58
+ end
59
+
60
+ # Initializes a new Repository instance that is available in all
61
+ # Octokom::Command instances.
62
+ def git
63
+ @git ||= Octokom::Repository.new(verbose?)
64
+ end
4
65
 
5
- subcommand 'pull-request', 'Create, update or merge pull requests.', PullRequest
66
+ def authenticate(&block)
67
+ yield Octokom::Client.authenticate
68
+ end
6
69
  end
7
70
  end
@@ -0,0 +1,26 @@
1
+ class Issue < Octokom::Command
2
+ sub_command 'create', 'Create a GitHub issue.' do
3
+ option ['--title', '-t'], 'Title for the issue.'
4
+ option ['--desc', '-d'], 'Description for the issue.'
5
+ option ['--repo', '-r'], 'The GitHub repository you want to update. (e.g. mjio/octokom)'
6
+ flag ['--editor', '-e'], 'Open the editor even when the issue title is set.'
7
+
8
+ execute do
9
+ @repo ||= git.repo_path
10
+
11
+ if @editor || @title.blank?
12
+ @title, @desc = Octokom::Editor.open(git, @title, @desc)
13
+ end
14
+
15
+ if @title.blank?
16
+ error('The title can not be empty')
17
+ end
18
+
19
+ authenticate do |auth|
20
+ task("Create issue #{@repo}") do
21
+ auth.client.create_issue(@repo, @title, @desc) unless dry_run?
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,29 +1,38 @@
1
- module Octokom
2
- class Command
3
- class PullRequest < Clamp::Command
4
- include Octokom::Authentication
1
+ class PullRequest < Octokom::Command
2
+ sub_command 'create', 'Create a pull request.' do
3
+ option ['--title', '-t'], 'Title for the pull request.'
4
+ option ['--desc', '-d'], 'Description for the pull request.'
5
+ option ['--base', '-b'], 'The branch you want your changes pulled into. (e.g. master)'
6
+ option ['--head', '-h'], 'The branch where your changes are implemented. (e.g. feature)'
7
+ option ['--repo', '-r'], 'The GitHub repository you want to update. (e.g. mjio/octokom)'
8
+ flag ['--editor', '-e'], 'Open the editor even when the pull request title is set.'
5
9
 
6
- subcommand 'create', 'Create a new pull request.' do
7
- option ['--repo', '-r'], '', 'The GitHub repository. Example: "mjio/octokom"'
8
- # TODO make ancestor default base
9
- option ['--base', '-b'], '', 'The branch you want your changes pulled into. Example: "master"', default: 'master'
10
- option ['--head', '-d'], '', 'The branch where your changes are implemented. Example: "new_feature"'
11
- # option ['--issue', '-i'], '', 'Create pull request from an existing issue. Example: "123"'
12
- option ['--title', '-t'], '', 'Title for the pull request.'
13
- option ['--body', '-b'], '', 'Body for the pull request.'
10
+ execute do
11
+ @base ||= git.base_branch
12
+ @head ||= git.head_branch
13
+ @repo ||= git.repo_path
14
14
 
15
- def execute
16
- authenticate do |client|
17
- repo ||= Repository.remote_path
18
- head ||= Repository.current_branch
15
+ if @base == @head
16
+ error("Base and head branch can not be the same (#{@base}:#{@head})")
17
+ end
18
+
19
+ if @editor || @title.blank?
20
+ @title, @desc = Octokom::Editor.open(git, @title, @desc)
21
+ end
19
22
 
20
- if Repository.unpushed_commits?
21
- exit unless system 'git push'
22
- end
23
+ if @title.blank?
24
+ error('The title can not be empty')
25
+ end
26
+
27
+ if git.pending_commits?
28
+ task('Push latests commits') do
29
+ git.push(@head) unless dry_run?
30
+ end
31
+ end
23
32
 
24
- puts "Create pull request (#{repo}) #{base}:#{head}"
25
- client.create_pull_request(repo, base, head, title, body)
26
- end
33
+ authenticate do |auth|
34
+ task("Create pull-request #{@repo} (#{@base}:#{@head})") do
35
+ auth.client.create_pull_request(@repo, @base, @head, @title, @desc) unless dry_run?
27
36
  end
28
37
  end
29
38
  end
@@ -0,0 +1,51 @@
1
+ # This file is from "activesupport/lib/active_support/core_ext/object/blank.rb"
2
+
3
+ class Object
4
+ def blank?
5
+ respond_to?(:empty?) ? !!empty? : !self
6
+ end
7
+
8
+ def present?
9
+ !blank?
10
+ end
11
+ end
12
+
13
+ class NilClass
14
+ def blank?
15
+ true
16
+ end
17
+ end
18
+
19
+ class FalseClass
20
+ def blank?
21
+ true
22
+ end
23
+ end
24
+
25
+ class TrueClass
26
+ def blank?
27
+ false
28
+ end
29
+ end
30
+
31
+ class Array
32
+ alias_method :blank?, :empty?
33
+ end
34
+
35
+ class Hash
36
+ alias_method :blank?, :empty?
37
+ end
38
+
39
+ class String
40
+ BLANK_RE = /\A[[:space:]]*\z/
41
+
42
+ def blank?
43
+ BLANK_RE === self
44
+ end
45
+ end
46
+
47
+ class Numeric
48
+ def blank?
49
+ false
50
+ end
51
+ end
@@ -0,0 +1,106 @@
1
+ module Octokom
2
+ # The `Octokom::Editor` class opens and parses files where you can insert
3
+ # a title and description for pull-requests or GitHub issues. This class is
4
+ # inspired by the `Pry::Editor` class (http://pry.github.io).
5
+ class Editor
6
+ def self.open(repo, title, desc)
7
+ editor = Editor.new(repo, title, desc)
8
+ editor.prepare_tempfile
9
+ editor.open_editor
10
+ editor.parse_user_input
11
+ end
12
+
13
+ def initialize(repo, title, desc)
14
+ @repo = repo
15
+ @title = title
16
+ @desc = desc
17
+ end
18
+
19
+ # Save a tempfile that includes the input from the command line or the
20
+ # last commit message.
21
+ # TODO Remove #Title and #Description section and use first line; following lines.
22
+ # TODO use last commit message for title
23
+ def prepare_tempfile
24
+ File.open(file_name, 'w') do |f|
25
+ f.puts '# Please enter a title and optional a description below'
26
+ f.puts '# the comments. To continue, save end close the file.'
27
+ f.puts "# Comment lines starting with '#' will be ignored."
28
+ f.puts
29
+ f.puts '# Title'
30
+ f.puts @title
31
+ f.puts
32
+ f.puts '# Description'
33
+ f.puts @desc
34
+ end
35
+ end
36
+
37
+ # Execute the command that is used to start the editor. This includes the
38
+ # waiting flag as well as the file and line number.
39
+ def open_editor
40
+ system("#{editor_name} #{waiting_flag} #{file_inc_line(file_name, 6)}")
41
+ end
42
+
43
+ # Opens the tempfile once again to parse the content and return the `title`
44
+ # and `description`.
45
+ def parse_user_input
46
+ input = {title: [], desc: []}
47
+ parsing = false
48
+
49
+ read_tempfile.each do |line|
50
+ match = /^\s*#\s*(title|desc)/i.match(line)
51
+ parsing = match[1].downcase.to_sym if match
52
+
53
+ unless line =~ /^\s*#/
54
+ input[parsing] << line if parsing
55
+ end
56
+ end
57
+
58
+ input.update(input) { |_, lines| lines.join("\n") }.values
59
+ end
60
+
61
+ private
62
+
63
+ def editor_name
64
+ @editor_name ||= ENV['GIT_EDITOR'] || ENV['VISUAL'] || ENV['EDITOR'] || 'vi'
65
+ end
66
+
67
+ # Make sure the editor waits for the file to be closed.
68
+ def waiting_flag
69
+ case editor_name
70
+ when /^[gm]?vi/
71
+ '--nofork'
72
+ when /^subl/, /^mate/
73
+ '--wait'
74
+ when /^jedit/
75
+ '-wait'
76
+ end
77
+ end
78
+
79
+ # Different editors have different ways how to open a file starting at
80
+ # a specific line number.
81
+ def file_inc_line(file_name, line)
82
+ case editor_name
83
+ when /^[gm]?vi/, /^emacs/, /^nano/, /^pico/, /^gedit/, /^kate/
84
+ "+#{line} #{file_name}"
85
+ when /^mate/, /^geany/
86
+ "--line #{line} #{file_name}"
87
+ when /^subl/
88
+ "#{file_name}:#{line}"
89
+ when /^uedit32/
90
+ "#{file_name}/#{line}"
91
+ when /^jedit/
92
+ "#{file_name} +line:#{line}"
93
+ else
94
+ file_name
95
+ end
96
+ end
97
+
98
+ def file_name
99
+ @file_name ||= "#{@repo.toplevel_path}/.git/OCTOKOM-#{Time.now.usec}"
100
+ end
101
+
102
+ def read_tempfile
103
+ File.open(file_name).map(&:chop).reject(&:empty?)
104
+ end
105
+ end
106
+ end