codeunion 0.0.2 → 0.0.3

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