codeunion 0.0.2 → 0.0.3

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.
@@ -0,0 +1,97 @@
1
+ require "uri"
2
+ require "codeunion/github_api"
3
+ require "addressable/uri"
4
+
5
+ module CodeUnion
6
+ # Sends feedback requests to CodeUnion for review
7
+ class FeedbackRequest
8
+ WEB_URL_REGEX = /\A#{URI.regexp(['http', 'https'])}\z/
9
+
10
+ GIT_URL_REGEX = %r{\A(git://)?git@github.com:(.*)(.git)?\z}
11
+ REPO_CAPTURE_INDEX = 1
12
+
13
+ MISSING_ARTIFACT = "You must provide something to provide feedback on"
14
+ INVALID_ARTIFACT = "The artifact provided was not a web URL. We only provide feedback on code hosted online."
15
+
16
+ ISSUE_TITLE = "Please give my code feedback. Submitted #{Time.now}"
17
+
18
+ attr_reader :artifact
19
+
20
+ def initialize(artifact, github_token, feedback_repository, options = {})
21
+ @github_api = options.fetch(:github_api, GithubAPI).new(github_token)
22
+ @artifact = artifact
23
+ @feedback_repository = FeedbackRepository.coerce_to_proper_url(feedback_repository)
24
+ end
25
+
26
+ def send!
27
+ @github_api.create_issue(ISSUE_TITLE, @artifact, @feedback_repository)
28
+ end
29
+
30
+ def valid?
31
+ errors.empty?
32
+ end
33
+
34
+ def errors
35
+ errors = []
36
+ errors.push(MISSING_ARTIFACT) if artifact.empty?
37
+ errors.push(INVALID_ARTIFACT) unless url?(artifact)
38
+
39
+ errors.join("\n")
40
+ end
41
+
42
+ private
43
+
44
+ def url?(url)
45
+ url =~ WEB_URL_REGEX
46
+ end
47
+
48
+ # Ensures a feedback repository location string is usable via the github api
49
+ class FeedbackRepository
50
+ DEFAULT_OWNER = "codeunion"
51
+
52
+ def self.coerce_to_proper_url(location)
53
+ new(location).to_s
54
+ end
55
+
56
+ def initialize(location)
57
+ @location = CLEAN.call(location)
58
+ end
59
+
60
+ def to_s
61
+ extract_owner_and_repo
62
+ end
63
+
64
+ private
65
+
66
+ CLEAN = ->(location) { location.gsub(/^\//, "").gsub(".git", "") }
67
+ EXTRACTORS = [
68
+ {
69
+ :match => ->(location) { location =~ GIT_URL_REGEX },
70
+ :extract => lambda do |location|
71
+ match = location.match(GIT_URL_REGEX)
72
+ CLEAN.call(match.captures[REPO_CAPTURE_INDEX])
73
+ end
74
+ },
75
+ {
76
+ :match => ->(location) { location =~ WEB_URL_REGEX },
77
+ :extract => ->(location) { CLEAN.call(URI(location).path) }
78
+ },
79
+ {
80
+ :match => ->(location) { location.split(/\//).length == 2 },
81
+ :extract => ->(location) { location }
82
+ },
83
+ {
84
+ :match => ->(_) { true },
85
+ :extract => ->(location) { "#{DEFAULT_OWNER}/#{location}" }
86
+ }
87
+ ]
88
+ def extract_owner_and_repo
89
+ EXTRACTORS.each do |extractor|
90
+ if extractor[:match].call(@location)
91
+ return extractor[:extract].call(@location)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,19 @@
1
+ require "codeunion/http_client"
2
+
3
+ module CodeUnion
4
+ # Intent-revealing methods for interacting with Github with interfaces
5
+ # that aren't tied to the api calls.
6
+ class GithubAPI
7
+ def initialize(access_token)
8
+ @access_token = access_token
9
+ @http_client = HTTPClient.new("https://api.github.com")
10
+ end
11
+
12
+ def create_issue(title, content, repository)
13
+ @http_client.post("repos/#{repository}/issues", {
14
+ "title" => title,
15
+ "body" => content
16
+ }, { "access_token" => @access_token })
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ require "codeunion"
2
+ require "io/console"
3
+
4
+ module CodeUnion
5
+ module Helpers
6
+ # Formats text for presenting to a console
7
+ module Text
8
+ def format_output(text)
9
+ _rows, cols = IO.console.winsize
10
+ line_length = [cols, 80].min
11
+
12
+ indent(wrap(text, line_length))
13
+ end
14
+
15
+ def wrap(text, max_width = IO.console.winsize.last)
16
+ return "" if text.empty?
17
+
18
+ words = text.split(/\s+/)
19
+ output = words.shift
20
+ chars_left = max_width - output.length
21
+
22
+ words.inject(output) do |output_builder, word|
23
+ if word.length + 1 > chars_left
24
+ chars_left = max_width - word.length
25
+ output_builder << "\n" + word
26
+ else
27
+ chars_left -= word.length + 1
28
+ output_builder << " " + word
29
+ end
30
+ end
31
+ end
32
+
33
+ def indent(lines, level = 1)
34
+ lines.split("\n").map { |line| (" " * level) + line }.join("\n")
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,55 @@
1
+ require "faraday"
2
+ require "faraday_middleware"
3
+ require "multi_json"
4
+ require "addressable/template"
5
+
6
+ module CodeUnion
7
+ # More friendly RESTful interactions via Faraday and Addressable
8
+ class HTTPClient
9
+ def initialize(api_host)
10
+ @api_host = api_host
11
+ end
12
+
13
+ def get(endpoint, params = {})
14
+ connection.get(request_url(endpoint, params)).body
15
+ end
16
+
17
+ def post(endpoint, body = {}, params = {})
18
+ response = connection.post do |req|
19
+ req.url request_url(endpoint, params)
20
+ req.headers["Content-Type"] = "application/json"
21
+ req.body = body.to_json
22
+ end
23
+
24
+ unless (200..300).cover?(response.status)
25
+ message = "POST to #{request_url(endpoint, params)} with " +
26
+ "#{body} responded with #{response.status} and " +
27
+ "#{response.body}"
28
+ fail(HTTPClient::RequestError, message)
29
+ end
30
+ response.body
31
+ end
32
+
33
+ def request_url(endpoint, params = {})
34
+ expand_url({ :endpoint => endpoint, :params => params })
35
+ end
36
+
37
+ private
38
+
39
+ def expand_url(options = {})
40
+ template.expand(options)
41
+ end
42
+
43
+ def template
44
+ @template ||= Addressable::Template.new("{+endpoint}{?params*}")
45
+ end
46
+
47
+ def connection
48
+ @connection ||= Faraday.new(@api_host) do |conn|
49
+ conn.response :json
50
+ conn.adapter Faraday.default_adapter
51
+ end
52
+ end
53
+ class RequestError < StandardError; end
54
+ end
55
+ end
@@ -0,0 +1,51 @@
1
+ require "rainbow"
2
+ require "codeunion/helpers/text"
3
+
4
+ module CodeUnion
5
+ class Search
6
+ # Prepares a search result for presentation in the CLI
7
+ class ResultPresenter
8
+ include CodeUnion::Helpers::Text
9
+ EXCERPT_REGEX = /<match>([^<]*)<\/match>/
10
+
11
+ def initialize(result)
12
+ @result = result
13
+ end
14
+
15
+ def to_s
16
+ [:name, :description, :url, :tags, :excerpt].map do |attribute|
17
+ format_output(send(attribute))
18
+ end.join("\n")
19
+ end
20
+
21
+ private
22
+
23
+ def name
24
+ "#{category}: " + Rainbow(@result["name"]).color(:blue)
25
+ end
26
+
27
+ def category
28
+ Rainbow(@result["category"].gsub(/s$/, "").capitalize).color(:red)
29
+ end
30
+
31
+ def description
32
+ Rainbow(@result["description"]).color(:yellow)
33
+ end
34
+
35
+ def excerpt
36
+ "Excerpt: " + @result["excerpt"].gsub(EXCERPT_REGEX) do
37
+ query_term = Regexp.last_match[1]
38
+ Rainbow(query_term).color(:blue)
39
+ end + "..."
40
+ end
41
+
42
+ def url
43
+ Rainbow(@result["url"]).color(:green)
44
+ end
45
+
46
+ def tags
47
+ "tags: " + @result["tags"].sort.join(", ")
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,3 +1,3 @@
1
1
  module CodeUnion
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ # Resolve the pathname for this executable
5
+ require "pathname"
6
+ bin_file = Pathname.new(__FILE__).realpath
7
+
8
+ # Add the gem's "lib" directory to library path
9
+ $LOAD_PATH.unshift File.expand_path("../../lib", bin_file)
10
+
11
+ require "codeunion/command/config"
12
+ require "optparse"
13
+
14
+ options = {}
15
+ subtext = <<HELP
16
+ Commands are:
17
+ get NAME - Retrieves and prints the named configuration value.
18
+ set NAME VALUE - Sets the named configuration to the given value.
19
+ unset NAME - Removes the named configuration value.
20
+
21
+ See 'codeunion config COMMAND --help' for more information on a specific command.
22
+ HELP
23
+ parser = OptionParser.new do |opts|
24
+ opts.instance_exec do
25
+ self.banner = "Usage: codeunion config COMMAND"
26
+ separator ""
27
+
28
+ separator subtext
29
+ separator ""
30
+
31
+ separator "Global options:"
32
+ on_tail("-d", "--[no-]debug", "Adds debug output.") do |debug|
33
+ options[:debug] = debug
34
+ end
35
+
36
+ on_tail("-h", "--help", "Print this help message") do
37
+ puts self
38
+ exit
39
+ end
40
+ end
41
+ end
42
+
43
+ commands = {}
44
+ get_subtext = <<HELP
45
+ Displays config variable NAME. NAME may be a top-level key (i.e. 'codeunion')
46
+ or nested, (i.e. 'codeunion.slack.username')
47
+ HELP
48
+ commands["get"] = OptionParser.new do |opts|
49
+ opts.instance_exec do
50
+ self.banner = "Usage: codeunion config get NAME"
51
+
52
+ separator ""
53
+ separator get_subtext
54
+ separator ""
55
+
56
+ separator "Options:"
57
+ on_tail("-h", "--help", "Print this help message") do
58
+ puts self
59
+ exit
60
+ end
61
+ end
62
+ end
63
+
64
+ set_subtext = <<HELP
65
+ Sets config variable NAME to VALUE. NAME may be a top-level key (i.e. 'codeunion')
66
+ or nested, (i.e. 'codeunion.slack.username').
67
+ HELP
68
+
69
+ commands["set"] = OptionParser.new do |opts|
70
+ opts.instance_exec do
71
+ self.banner = "Usage: codeunion config set NAME VALUE"
72
+
73
+ separator ""
74
+ separator set_subtext
75
+ separator ""
76
+
77
+ separator "Options:"
78
+ on_tail("-h", "--help", "Print this help message") do
79
+ puts self
80
+ exit
81
+ end
82
+ end
83
+ end
84
+
85
+ unset_subtext = <<HELP
86
+ Unsets config variable NAME. NAME may be a top-level key (i.e. 'codeunion') or
87
+ nested, (i.e. 'codeunion.slack.username').
88
+ HELP
89
+ commands["unset"] = OptionParser.new do |opts|
90
+ opts.instance_exec do
91
+ self.banner = "Usage: codeunion config unset NAME"
92
+
93
+ separator ""
94
+ separator unset_subtext
95
+ separator ""
96
+
97
+ separator "Options:"
98
+ on_tail("-h", "--help", "Print this help message") do
99
+ puts self
100
+ exit
101
+ end
102
+ end
103
+ end
104
+
105
+ parser.order!
106
+ command = ARGV.shift
107
+ if !command || !commands[command]
108
+ puts parser
109
+ exit
110
+ else
111
+ commands[command].order!
112
+ options[:command] = command
113
+ options[:input] = ARGV
114
+ result = CodeUnion::Command::Config.new(options).run
115
+ puts result if result
116
+ end
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ # Resolve the pathname for this executable
5
+ require "pathname"
6
+ bin_file = Pathname.new(__FILE__).realpath
7
+
8
+ # Add the gem's "lib" directory to library path
9
+ $LOAD_PATH.unshift File.expand_path("../../lib", bin_file)
10
+
11
+ require "codeunion/command/search"
12
+ require "optparse"
13
+
14
+ options = {}
15
+ parser = OptionParser.new do |opts|
16
+ opts.instance_exec do
17
+ self.banner = "Usage: codeunion examples <terms>"
18
+
19
+ on_tail("-h", "--help", "Print this help message") do
20
+ puts self
21
+ exit
22
+ end
23
+ end
24
+ end
25
+
26
+ if ARGV.empty?
27
+ puts parser
28
+ exit
29
+ else
30
+ parser.parse!(ARGV)
31
+ end
32
+
33
+ options[:category] = "examples"
34
+ options[:query] = ARGV
35
+
36
+ result = CodeUnion::Command::Search.new(options).run
37
+ puts result if result
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ # Resolve the pathname for this executable
5
+ require "pathname"
6
+ bin_file = Pathname.new(__FILE__).realpath
7
+
8
+ # Add the gem's "lib" directory to library path
9
+ $LOAD_PATH.unshift File.expand_path("../../lib", bin_file)
10
+
11
+ require "codeunion/command/feedback"
12
+ require "optparse"
13
+
14
+ options = {}
15
+ commands = {}
16
+ subtext = <<HELP
17
+ Commonly used command are:
18
+ request URL - (Default) - Sends a request for feedback to codeunion.
19
+
20
+ See 'codeunion feedback COMMAND --help' for more information on a specific command.
21
+
22
+ Make sure you `codeunion config set` the following:
23
+ github.access_token - Allows codeunion-client to interact with Github as you.
24
+ See: https://help.github.com/articles/creating-an-access-token-for-command-line-use/
25
+
26
+ feedback.repository - The github repository to submit feedback requests in.
27
+ (I.e. codeunion/feedback-requests-web-fundamentals)
28
+ HELP
29
+
30
+ top_level_parser = OptionParser.new do |opts|
31
+ opts.instance_exec do
32
+ self.banner = "Usage: codeunion feedback COMMAND"
33
+
34
+ separator ""
35
+ separator subtext
36
+
37
+ separator ""
38
+ separator "Global options:"
39
+ on_tail("-d", "--[no-]debug", "Adds debug output.") do |debug|
40
+ options[:debug] = debug
41
+ end
42
+
43
+ on_tail("-h", "--help", "Print this help message") do
44
+ puts self
45
+ exit
46
+ end
47
+ end
48
+ end
49
+
50
+ request_subtext = <<HELP
51
+ URL should be a pull request or a specific commit.
52
+
53
+ * How to get the URL for a commit: http://andrew.yurisich.com/work/2014/07/16/dont-link-that-line-number/
54
+ * How to create a pull request: https://help.github.com/articles/creating-a-pull-request/
55
+ HELP
56
+
57
+ commands["request"] = OptionParser.new do |opts|
58
+ opts.instance_exec do
59
+ self.banner = "Usage: codeunion feedback request URL"
60
+
61
+ separator ""
62
+ separator request_subtext
63
+
64
+ separator ""
65
+ separator "Options:"
66
+
67
+ on_tail("-h", "--help", "Print this help message") do
68
+ puts self
69
+ exit
70
+ end
71
+ end
72
+ end
73
+
74
+ top_level_parser.order!
75
+ command = ARGV.shift
76
+
77
+ unless command
78
+ puts top_level_parser
79
+ exit
80
+ end
81
+
82
+ if command && !commands[command]
83
+ options[:input] = [command]
84
+ command = "request"
85
+ else
86
+ commands[command].order!
87
+ options[:input] = ARGV
88
+ end
89
+ begin
90
+ CodeUnion::Command::Feedback.new(options).run
91
+ rescue CodeUnion::Command::InvalidInput => e
92
+ puts "Woops! Error(s) found!"
93
+ puts e.message
94
+ puts commands[command]
95
+ rescue CodeUnion::Command::MissingConfig => e
96
+ puts "Woops! You need to configure this command"
97
+ puts e.message
98
+ end