sync_issues 0.0.1a1 → 0.0.1
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.
- checksums.yaml +4 -4
- data/lib/sync_issues/command.rb +6 -5
- data/lib/sync_issues/error.rb +6 -0
- data/lib/sync_issues/github.rb +40 -0
- data/lib/sync_issues/issue.rb +32 -0
- data/lib/sync_issues/parser.rb +41 -0
- data/lib/sync_issues/sync_issues_module.rb +8 -3
- data/lib/sync_issues/synchronizer.rb +81 -7
- data/lib/sync_issues/version.rb +1 -1
- data/lib/sync_issues.rb +0 -1
- metadata +35 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 851be84063a71f5f66d9f4433ef9e412b81ed1a4
|
4
|
+
data.tar.gz: 525105d38ebbb876c30e49c37b5b0e3ebbde1470
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba04b87e9d73cc9ffd051750c9738a9bc7ec8a7edea7b759274fd89aaf3d970fbeed37bbbb5dd74f31c73da855681a9f4d862229d3c4198fbfa3d22ef8bfcf27
|
7
|
+
data.tar.gz: 4606a46c96a4468bc805cb9f68f6c1dd4fd87e20834410c9166e576a28d57c6d8e565df336ecdc8fd2405d27a27561ac45835409900610557062fc750697fcf2
|
data/lib/sync_issues/command.rb
CHANGED
@@ -10,13 +10,14 @@ module SyncIssues
|
|
10
10
|
sync_issues: A tool that synchronizes a local directory with GitHub issues.
|
11
11
|
|
12
12
|
Usage:
|
13
|
-
sync_issues DIRECTORY REPOSITORY...
|
13
|
+
sync_issues [options] DIRECTORY REPOSITORY...
|
14
14
|
sync_issues -h | --help
|
15
15
|
sync_issues --version
|
16
16
|
|
17
17
|
Options:
|
18
|
-
-
|
19
|
-
--
|
18
|
+
-u --update Only update existing issues.
|
19
|
+
-h --help Output this help information.
|
20
|
+
--version Output the sync_issues version (#{VERSION}).
|
20
21
|
DOC
|
21
22
|
|
22
23
|
def initialize
|
@@ -39,8 +40,8 @@ module SyncIssues
|
|
39
40
|
end
|
40
41
|
|
41
42
|
def handle_args(options)
|
42
|
-
SyncIssues.synchronizer(options['DIRECTORY'],
|
43
|
-
options['
|
43
|
+
SyncIssues.synchronizer(options['DIRECTORY'], options['REPOSITORY'],
|
44
|
+
update_only: options['--update']).run
|
44
45
|
@exit_status
|
45
46
|
end
|
46
47
|
end
|
data/lib/sync_issues/error.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative 'error'
|
2
|
+
require 'octokit'
|
3
|
+
require 'safe_yaml/load'
|
4
|
+
|
5
|
+
module SyncIssues
|
6
|
+
# GitHub is responsible access to GitHub's API
|
7
|
+
class GitHub
|
8
|
+
def initialize
|
9
|
+
@client = Octokit::Client.new access_token: token
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_issue(repository, issue)
|
13
|
+
@client.create_issue(repository.full_name, issue.title, issue.content)
|
14
|
+
end
|
15
|
+
|
16
|
+
def issues(repository)
|
17
|
+
@client.issues(repository)
|
18
|
+
end
|
19
|
+
|
20
|
+
def repository(repository_name)
|
21
|
+
@client.repository(repository_name)
|
22
|
+
rescue Octokit::InvalidRepository => exc
|
23
|
+
raise Error, exc.message
|
24
|
+
rescue Octokit::NotFound
|
25
|
+
raise Error, 'repository not found'
|
26
|
+
end
|
27
|
+
|
28
|
+
def update_issue(repository, github_issue, issue)
|
29
|
+
@client.update_issue(repository.full_name, github_issue.number,
|
30
|
+
issue.new_title || issue.title, issue.content)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def token
|
36
|
+
path = File.expand_path('~/.config/sync_issues.yaml')
|
37
|
+
SafeYAML.load(File.read(path))['token']
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative 'error'
|
2
|
+
|
3
|
+
module SyncIssues
|
4
|
+
# Issue represents an issue to be added or updated.
|
5
|
+
#
|
6
|
+
# new_title is only used when an issue should be renamed. Issues with
|
7
|
+
# new_title set will never be created
|
8
|
+
class Issue
|
9
|
+
attr_reader :assignee, :content, :new_title, :title
|
10
|
+
|
11
|
+
def initialize(content, title:, assignee: nil, new_title: nil)
|
12
|
+
@assignee = verify_string 'assignee', assignee
|
13
|
+
@content = content
|
14
|
+
@title = verify_string 'title', title, allow_nil: false
|
15
|
+
@new_title = verify_string 'new_title', new_title, allow_nil: true
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def verify_string(field, value, allow_nil: true)
|
21
|
+
if value.nil?
|
22
|
+
raise IssueError, "'#{field}' must be provided" unless allow_nil
|
23
|
+
elsif !value.is_a?(String)
|
24
|
+
raise IssueError, "'#{field}' must be a string"
|
25
|
+
else
|
26
|
+
value.strip!
|
27
|
+
raise IssueError, "'#{field}' must not be blank" if value == ''
|
28
|
+
end
|
29
|
+
value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative 'error'
|
2
|
+
require_relative 'issue'
|
3
|
+
require 'safe_yaml/load'
|
4
|
+
|
5
|
+
module SyncIssues
|
6
|
+
# Synchronizer is responsible for the actual synchronization.
|
7
|
+
class Parser
|
8
|
+
attr_reader :issue
|
9
|
+
|
10
|
+
def initialize(data)
|
11
|
+
@issue = nil
|
12
|
+
parse(data)
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse(data)
|
16
|
+
unless data =~ /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
|
17
|
+
raise ParseError, 'missing frontmatter'
|
18
|
+
end
|
19
|
+
|
20
|
+
if (content = $POSTMATCH).empty?
|
21
|
+
raise ParseError, 'empty markdown content'
|
22
|
+
elsif (metadata = SafeYAML.load(Regexp.last_match(1))).nil?
|
23
|
+
raise ParseError, 'empty frontmatter'
|
24
|
+
else
|
25
|
+
@issue = Issue.new content, **hash_keys_to_symbols(metadata)
|
26
|
+
end
|
27
|
+
rescue ArgumentError => exc
|
28
|
+
raise ParseError, exc.message
|
29
|
+
rescue Psych::SyntaxError
|
30
|
+
raise ParseError, 'invalid frontmatter'
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def hash_keys_to_symbols(hash)
|
36
|
+
hash.each_with_object({}) do |(key, value), tmp_hash|
|
37
|
+
tmp_hash[key.to_sym] = value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,15 +1,20 @@
|
|
1
1
|
require_relative 'command'
|
2
|
+
require_relative 'github'
|
2
3
|
require_relative 'synchronizer'
|
3
4
|
|
4
5
|
# SyncIssues
|
5
6
|
module SyncIssues
|
6
7
|
class << self
|
7
8
|
def command
|
8
|
-
@command ||=
|
9
|
+
@command ||= Command.new
|
9
10
|
end
|
10
11
|
|
11
|
-
def
|
12
|
-
|
12
|
+
def github
|
13
|
+
@github ||= GitHub.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def synchronizer(*args)
|
17
|
+
Synchronizer.new(*args)
|
13
18
|
end
|
14
19
|
end
|
15
20
|
end
|
@@ -1,23 +1,97 @@
|
|
1
|
+
require_relative 'error'
|
2
|
+
require_relative 'parser'
|
3
|
+
require 'English'
|
4
|
+
|
1
5
|
module SyncIssues
|
2
6
|
# Synchronizer is responsible for the actual synchronization.
|
3
7
|
class Synchronizer
|
4
|
-
def initialize(directory,
|
5
|
-
@
|
6
|
-
@repositories =
|
8
|
+
def initialize(directory, repository_names, update_only: false)
|
9
|
+
@issues = issues(directory)
|
10
|
+
@repositories = repositories(repository_names)
|
11
|
+
@update_only = update_only
|
7
12
|
end
|
8
13
|
|
9
14
|
def run
|
10
|
-
puts "
|
15
|
+
puts "Synchronize #{@issues.count} issue#{@issues.count == 1 ? '' : 's'}"
|
16
|
+
@issues.each { |issue| puts " * #{issue.title}" }
|
17
|
+
@repositories.each { |repository| synchronize(repository) }
|
11
18
|
end
|
12
19
|
|
13
20
|
private
|
14
21
|
|
15
|
-
def
|
16
|
-
directory
|
22
|
+
def issues(directory)
|
23
|
+
unless File.directory?(directory)
|
24
|
+
raise Error, "'#{directory}' is not a valid directory"
|
25
|
+
end
|
26
|
+
|
27
|
+
issues = Dir.glob(File.join(directory, '**/*')).map do |entry|
|
28
|
+
next unless entry.end_with?('.md') && File.file?(entry)
|
29
|
+
begin
|
30
|
+
Parser.new(File.read(entry)).issue
|
31
|
+
rescue ParseError => exc
|
32
|
+
puts "'#{entry}': #{exc}"
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end.compact
|
36
|
+
|
37
|
+
if issues.empty?
|
38
|
+
raise Error, "'#{directory}' does not contain any .md files"
|
39
|
+
end
|
40
|
+
|
41
|
+
issues
|
17
42
|
end
|
18
43
|
|
19
|
-
def
|
44
|
+
def repositories(repository_names)
|
45
|
+
repositories = repository_names.map do |repository_name|
|
46
|
+
begin
|
47
|
+
SyncIssues.github.repository(repository_name)
|
48
|
+
rescue Error => exc
|
49
|
+
puts "'#{repository_name}' #{exc}"
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end.compact
|
53
|
+
|
54
|
+
raise Error, 'No valid repositories specified' if repositories.empty?
|
55
|
+
|
20
56
|
repositories
|
21
57
|
end
|
58
|
+
|
59
|
+
def synchronize(repository)
|
60
|
+
puts "Repository: #{repository.full_name}"
|
61
|
+
|
62
|
+
existing_by_title = {}
|
63
|
+
SyncIssues.github.issues(repository.full_name).each do |issue|
|
64
|
+
existing_by_title[issue.title] = issue
|
65
|
+
end
|
66
|
+
|
67
|
+
@issues.each do |issue|
|
68
|
+
if existing_by_title.include?(issue.title)
|
69
|
+
update_issue(repository, issue, existing_by_title[issue.title])
|
70
|
+
else
|
71
|
+
create_issue(repository, issue)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def create_issue(repository, issue)
|
79
|
+
if @update_only || issue.new_title
|
80
|
+
puts "Skipping create issue: #{issue.title}"
|
81
|
+
else
|
82
|
+
puts "Adding issue: #{issue.title}"
|
83
|
+
SyncIssues.github.create_issue(repository, issue)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def update_issue(repository, issue, github_issue)
|
88
|
+
changed = []
|
89
|
+
changed << 'title' unless issue.new_title.nil?
|
90
|
+
changed << 'body' unless issue.content == github_issue.body
|
91
|
+
return if changed.empty?
|
92
|
+
|
93
|
+
puts "Updating #{changed.join(', ')} on ##{github_issue.number}"
|
94
|
+
SyncIssues.github.update_issue(repository, github_issue, issue)
|
95
|
+
end
|
22
96
|
end
|
23
97
|
end
|
data/lib/sync_issues/version.rb
CHANGED
data/lib/sync_issues.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sync_issues
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bryce Boe
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-02-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: docopt
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: octokit
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: safe_yaml
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
27
55
|
description: |2
|
28
56
|
sync_issues is a ruby gem to that allows the easy creation and
|
29
57
|
synchronization of issues on GitHub with structured local data.
|
@@ -39,6 +67,9 @@ files:
|
|
39
67
|
- lib/sync_issues.rb
|
40
68
|
- lib/sync_issues/command.rb
|
41
69
|
- lib/sync_issues/error.rb
|
70
|
+
- lib/sync_issues/github.rb
|
71
|
+
- lib/sync_issues/issue.rb
|
72
|
+
- lib/sync_issues/parser.rb
|
42
73
|
- lib/sync_issues/sync_issues_module.rb
|
43
74
|
- lib/sync_issues/synchronizer.rb
|
44
75
|
- lib/sync_issues/version.rb
|
@@ -57,9 +88,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
57
88
|
version: '0'
|
58
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
90
|
requirements:
|
60
|
-
- - "
|
91
|
+
- - ">="
|
61
92
|
- !ruby/object:Gem::Version
|
62
|
-
version:
|
93
|
+
version: '0'
|
63
94
|
requirements: []
|
64
95
|
rubyforge_project:
|
65
96
|
rubygems_version: 2.4.8
|