pennyworth 9.5.0 → 11.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.adoc +341 -31
  5. data/bin/pennyworth +1 -3
  6. data/lib/pennyworth.rb +11 -2
  7. data/lib/pennyworth/cli/actions/config.rb +35 -0
  8. data/lib/pennyworth/cli/actions/encodings.rb +23 -0
  9. data/lib/pennyworth/cli/actions/git_hub.rb +27 -0
  10. data/lib/pennyworth/cli/actions/http_statuses.rb +23 -0
  11. data/lib/pennyworth/cli/actions/ruby_gems.rb +27 -0
  12. data/lib/pennyworth/cli/actions/system/errors.rb +25 -0
  13. data/lib/pennyworth/cli/actions/system/signals.rb +25 -0
  14. data/lib/pennyworth/cli/actions/text.rb +23 -0
  15. data/lib/pennyworth/cli/configuration/content.rb +28 -0
  16. data/lib/pennyworth/cli/configuration/defaults.yml +25 -0
  17. data/lib/pennyworth/cli/configuration/loader.rb +37 -0
  18. data/lib/pennyworth/cli/parsers.rb +11 -0
  19. data/lib/pennyworth/cli/parsers/assembler.rb +32 -0
  20. data/lib/pennyworth/cli/parsers/core.rb +100 -0
  21. data/lib/pennyworth/cli/parsers/git_hub.rb +51 -0
  22. data/lib/pennyworth/cli/parsers/ruby_gems.rb +35 -0
  23. data/lib/pennyworth/cli/shell.rb +82 -0
  24. data/lib/pennyworth/container.rb +40 -0
  25. data/lib/pennyworth/identity.rb +2 -1
  26. data/lib/pennyworth/inflector.rb +18 -0
  27. data/lib/pennyworth/integrations/git_hub/client.rb +59 -0
  28. data/lib/pennyworth/integrations/git_hub/response.rb +33 -0
  29. data/lib/pennyworth/integrations/ruby_gems/client.rb +29 -0
  30. data/lib/pennyworth/loaders/encodings.rb +31 -0
  31. data/lib/pennyworth/loaders/http_statuses.rb +28 -0
  32. data/lib/pennyworth/loaders/projects.rb +22 -0
  33. data/lib/pennyworth/loaders/system/errors.rb +33 -0
  34. data/lib/pennyworth/loaders/system/signals.rb +21 -0
  35. data/lib/pennyworth/loaders/text.rb +27 -0
  36. data/lib/pennyworth/models/encoding.rb +8 -0
  37. data/lib/pennyworth/models/http_status.rb +8 -0
  38. data/lib/pennyworth/models/system/error.rb +10 -0
  39. data/lib/pennyworth/models/system/signal.rb +10 -0
  40. data/lib/pennyworth/models/text.rb +8 -0
  41. data/lib/pennyworth/presenters/encoding.rb +22 -0
  42. data/lib/pennyworth/presenters/gem.rb +48 -0
  43. data/lib/pennyworth/presenters/http_status.rb +41 -0
  44. data/lib/pennyworth/presenters/repository.rb +35 -0
  45. data/lib/pennyworth/presenters/system/error.rb +28 -0
  46. data/lib/pennyworth/presenters/system/signal.rb +22 -0
  47. data/lib/pennyworth/presenters/text.rb +25 -0
  48. data/lib/pennyworth/processor.rb +68 -0
  49. data/lib/pennyworth/serializers/encoding.rb +33 -0
  50. data/lib/pennyworth/serializers/http_status.rb +41 -0
  51. data/lib/pennyworth/serializers/project.rb +52 -0
  52. data/lib/pennyworth/serializers/system/error.rb +46 -0
  53. data/lib/pennyworth/serializers/system/signal.rb +40 -0
  54. data/lib/pennyworth/serializers/text.rb +31 -0
  55. metadata +125 -34
  56. metadata.gz.sig +0 -0
  57. data/lib/pennyworth/cli.rb +0 -140
  58. data/lib/pennyworth/templates/workflows/user.workflow.B59B22A2-1880-4765-9358-412791BE9202/icon.png +0 -0
  59. data/lib/pennyworth/templates/workflows/user.workflow.B59B22A2-1880-4765-9358-412791BE9202/info.plist +0 -159
  60. data/lib/pennyworth/templates/workflows/user.workflow.C431E56A-0EC2-47EE-94D5-D67D9FE323BE/icon.png +0 -0
  61. data/lib/pennyworth/templates/workflows/user.workflow.C431E56A-0EC2-47EE-94D5-D67D9FE323BE/info.plist +0 -272
  62. data/lib/pennyworth/templates/workflows/user.workflow.C431E56A-0EC2-47EE-94D5-D67D9FE323BE/string-camelcase.png +0 -0
  63. data/lib/pennyworth/templates/workflows/user.workflow.C431E56A-0EC2-47EE-94D5-D67D9FE323BE/string-capitalize.png +0 -0
  64. data/lib/pennyworth/templates/workflows/user.workflow.C431E56A-0EC2-47EE-94D5-D67D9FE323BE/string-lowercase.png +0 -0
  65. data/lib/pennyworth/templates/workflows/user.workflow.C431E56A-0EC2-47EE-94D5-D67D9FE323BE/string-size.png +0 -0
  66. data/lib/pennyworth/templates/workflows/user.workflow.C431E56A-0EC2-47EE-94D5-D67D9FE323BE/string-snakecase.png +0 -0
  67. data/lib/pennyworth/templates/workflows/user.workflow.C431E56A-0EC2-47EE-94D5-D67D9FE323BE/string-titleize.png +0 -0
  68. data/lib/pennyworth/templates/workflows/user.workflow.C431E56A-0EC2-47EE-94D5-D67D9FE323BE/string-uppercase.png +0 -0
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "refinements/structs"
4
+
5
+ module Pennyworth
6
+ module CLI
7
+ module Parsers
8
+ # Handles parsing of Command Line Interface (CLI) GitHub options.
9
+ class GitHub
10
+ using Refinements::Structs
11
+
12
+ def self.call(...) = new(...).call
13
+
14
+ def initialize configuration = Configuration::Loader.call, client: CLIENT
15
+ @configuration = configuration
16
+ @client = client
17
+ end
18
+
19
+ def call arguments = []
20
+ client.separator "\nGITHUB OPTIONS:\n"
21
+ private_methods.sort.grep(/add_/).each { |method| __send__ method }
22
+ arguments.empty? ? arguments : client.parse!(arguments)
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :configuration, :client
28
+
29
+ def add_organization
30
+ client.on(
31
+ "--organization [HANDLE]",
32
+ %(Set organization. Default: "#{organization}".)
33
+ ) do |handle|
34
+ configuration.merge! action_git_hub: :organization,
35
+ git_hub_organization: handle || organization
36
+ end
37
+ end
38
+
39
+ def add_user
40
+ client.on "--user [HANDLE]", %(Set user. Default: "#{user}".) do |handle|
41
+ configuration.merge! action_git_hub: :user, git_hub_user: handle || user
42
+ end
43
+ end
44
+
45
+ def organization = configuration.git_hub_organization
46
+
47
+ def user = configuration.git_hub_user
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pennyworth
4
+ module CLI
5
+ module Parsers
6
+ # Handles parsing of Command Line Interface (CLI) RubyGems options.
7
+ class RubyGems
8
+ def self.call(...) = new(...).call
9
+
10
+ def initialize configuration = Configuration::Loader.call, client: CLIENT
11
+ @configuration = configuration
12
+ @client = client
13
+ end
14
+
15
+ def call arguments = []
16
+ client.separator "\nRUBYGEMS OPTIONS:\n"
17
+ add_owner
18
+ arguments.empty? ? arguments : client.parse!(arguments)
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :configuration, :client
24
+
25
+ def add_owner
26
+ client.on "--owner [HANDLE]", %(Set owner. Default: "#{owner}".) do |handle|
27
+ configuration.ruby_gems_owner = handle || owner
28
+ end
29
+ end
30
+
31
+ def owner = configuration.ruby_gems_owner
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pennyworth
4
+ module CLI
5
+ # The main Command Line Interface (CLI) object.
6
+ class Shell
7
+ ACTIONS = {
8
+ config: Actions::Config.new,
9
+ encodings: Actions::Encodings.new,
10
+ git_hub: Actions::GitHub.new,
11
+ http_statuses: Actions::HTTPStatuses.new,
12
+ ruby_gems: Actions::RubyGems.new,
13
+ system_errors: Actions::System::Errors.new,
14
+ system_signals: Actions::System::Signals.new,
15
+ text: Actions::Text.new
16
+ }.freeze
17
+
18
+ def initialize parser: Parsers::Assembler.new, actions: ACTIONS, container: Container
19
+ @parser = parser
20
+ @actions = actions
21
+ @container = container
22
+ end
23
+
24
+ def call arguments = []
25
+ perform parser.call(arguments)
26
+ rescue OptionParser::ParseError, KeyError => error
27
+ logger.error error.message
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :parser, :actions, :container
33
+
34
+ # rubocop:disable Metrics/MethodLength
35
+ def perform configuration
36
+ case configuration
37
+ in action_config: Symbol => action then config action
38
+ in action_encodings: true then encodings
39
+ in action_git_hub: :organization then git_hub_organization configuration
40
+ in action_git_hub: :user then git_hub_user configuration
41
+ in action_http_statuses: true then http_statuses
42
+ in action_ruby_gems: true then ruby_gems configuration
43
+ in action_system_signals: true then system_signals
44
+ in action_system_errors: true then system_errors
45
+ in action_text: String => content then text content
46
+ in action_version: String => version then logger.info version
47
+ in action_help: then usage
48
+ else usage
49
+ end
50
+ end
51
+ # rubocop:enable Metrics/MethodLength
52
+
53
+ def config(action) = actions.fetch(__method__).call(action)
54
+
55
+ def encodings = actions.fetch(__method__).call
56
+
57
+ def git_hub_organization configuration
58
+ actions.fetch(:git_hub).call("orgs/#{configuration.git_hub_organization}/repos")
59
+ end
60
+
61
+ def git_hub_user configuration
62
+ actions.fetch(:git_hub).call("users/#{configuration.git_hub_user}/repos")
63
+ end
64
+
65
+ def http_statuses = actions.fetch(__method__).call
66
+
67
+ def ruby_gems configuration
68
+ actions.fetch(__method__).call("owners/#{configuration.ruby_gems_owner}/gems.json")
69
+ end
70
+
71
+ def system_errors = actions.fetch(__method__).call
72
+
73
+ def system_signals = actions.fetch(__method__).call
74
+
75
+ def text(content) = actions.fetch(__method__).call(content)
76
+
77
+ def usage = logger.unknown { parser.to_s }
78
+
79
+ def logger = container[__method__]
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-container"
4
+ require "logger"
5
+ require "http"
6
+ require "pastel"
7
+
8
+ module Pennyworth
9
+ # Provides a global gem container for injection into other objects.
10
+ module Container
11
+ extend Dry::Container::Mixin
12
+
13
+ register(:configuration) { CLI::Configuration::Loader.call }
14
+ register(:environment) { ENV }
15
+ register(:kernel) { Kernel }
16
+ register(:http) { HTTP }
17
+ register(:colorizer) { Pastel.new enabled: $stdout.tty? }
18
+
19
+ register :log_colors do
20
+ {
21
+ "DEBUG" => self[:colorizer].white.detach,
22
+ "INFO" => self[:colorizer].green.detach,
23
+ "WARN" => self[:colorizer].yellow.detach,
24
+ "ERROR" => self[:colorizer].red.detach,
25
+ "FATAL" => self[:colorizer].white.bold.on_red.detach,
26
+ "ANY" => self[:colorizer].white.bold.detach
27
+ }
28
+ end
29
+
30
+ register :logger do
31
+ Logger.new $stdout,
32
+ level: Logger.const_get(ENV.fetch("LOG_LEVEL", "INFO")),
33
+ formatter: (
34
+ lambda do |severity, _at, _name, message|
35
+ self[:log_colors][severity].call "#{message}\n"
36
+ end
37
+ )
38
+ end
39
+ end
40
+ end
@@ -5,7 +5,8 @@ module Pennyworth
5
5
  module Identity
6
6
  NAME = "pennyworth"
7
7
  LABEL = "Pennyworth"
8
- VERSION = "9.5.0"
8
+ SUMMARY = "A command line interface that augments Alfred workflows."
9
+ VERSION = "11.1.0"
9
10
  VERSION_LABEL = "#{LABEL} #{VERSION}"
10
11
  end
11
12
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pennyworth
4
+ # Overrides any string to desired form if matched, otherwise answers the original string.
5
+ class Inflector
6
+ DEFAULTS = Array(CLI::Configuration::Loader.call.inflections).reduce({}, :merge)
7
+
8
+ def initialize overrides = DEFAULTS
9
+ @overrides = overrides
10
+ end
11
+
12
+ def call(key) = overrides.fetch(key) { |fallback| fallback }
13
+
14
+ private
15
+
16
+ attr_reader :overrides
17
+ end
18
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "refinements/arrays"
4
+
5
+ module Pennyworth
6
+ module Integrations
7
+ module GitHub
8
+ # Provides low-level API access to the GitHub API.
9
+ class Client
10
+ using Refinements::Arrays
11
+
12
+ HEADERS = {
13
+ accept: "application/vnd.github.v3+json",
14
+ content_type: "Content-Type: application/json; charset=utf-8"
15
+ }.freeze
16
+
17
+ def initialize headers: HEADERS, container: Container
18
+ @headers = headers
19
+ @container = container
20
+ end
21
+
22
+ def get endpoint, parameters: {}
23
+ paginate ->(page) { sole_get endpoint, parameters: parameters.merge(page: page) }
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :headers, :container
29
+
30
+ # :reek:FeatureEnvy
31
+ def paginate request, records: [], page: 1
32
+ response = request.call page
33
+
34
+ records.including(response.records)
35
+ .then do |updated_records|
36
+ return updated_records if response.last_page.zero?
37
+
38
+ paginate request, records: updated_records, page: response.next_page
39
+ end
40
+ end
41
+
42
+ def sole_get endpoint, parameters: {}
43
+ http.headers(headers)
44
+ .basic_auth(user: api_login, pass: api_token)
45
+ .get("#{api_url}/#{endpoint}", params: parameters)
46
+ .then { |response| Response.new response }
47
+ end
48
+
49
+ def http = container[__method__]
50
+
51
+ def api_url = container[:configuration].git_hub_api_url
52
+
53
+ def api_login = container[:environment].fetch("GITHUB_API_LOGIN")
54
+
55
+ def api_token = container[:environment].fetch("GITHUB_API_TOKEN")
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pennyworth
4
+ module Integrations
5
+ module GitHub
6
+ # Represents a generic GitHub API response.
7
+ class Response
8
+ def initialize raw
9
+ @raw = raw
10
+ end
11
+
12
+ def next_page = page("next")
13
+
14
+ def last_page = page("last")
15
+
16
+ def records = success? ? JSON(raw.body, symbolize_names: true) : []
17
+
18
+ def success? = raw.status.success?
19
+
20
+ private
21
+
22
+ attr_reader :raw
23
+
24
+ def page reference
25
+ links.find { |link| link.include? reference }
26
+ .then { |link| String(link)[/page=(?<page>\d+)/, :page].to_i }
27
+ end
28
+
29
+ def links = String(raw.headers["Link"]).split(", ")
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pennyworth
4
+ module Integrations
5
+ module RubyGems
6
+ # Provides low-level API access to the RubyGems API.
7
+ class Client
8
+ def initialize container: Container
9
+ @container = container
10
+ end
11
+
12
+ def get endpoint
13
+ http.get("#{api_url}/#{endpoint}")
14
+ .then do |response|
15
+ response.status.success? ? JSON(response.body.to_s, symbolize_names: true) : {}
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :container
22
+
23
+ def http = container[__method__]
24
+
25
+ def api_url = container[:configuration].ruby_gems_api_url
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "refinements/arrays"
4
+
5
+ module Pennyworth
6
+ module Loaders
7
+ # Loads an array of encoding records.
8
+ class Encodings
9
+ using Refinements::Arrays
10
+
11
+ def initialize encoding: Encoding, model: Models::Encoding
12
+ @encoding = encoding
13
+ @model = model
14
+ end
15
+
16
+ def call _omit = nil
17
+ encoding.aliases
18
+ .map { |_second, first| first }
19
+ .uniq
20
+ .sort
21
+ .map do |first|
22
+ model[name: first, aliases: encoding.find(first).names.sort.excluding(first)]
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :encoding, :model
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack"
4
+
5
+ module Pennyworth
6
+ module Loaders
7
+ # Loads an array of HTTP status records.
8
+ class HTTPStatuses
9
+ def initialize codes: Rack::Utils::HTTP_STATUS_CODES,
10
+ model: Models::HTTPStatus,
11
+ configuration: CLI::Configuration::Loader.call
12
+ @codes = codes
13
+ @model = model
14
+ @configuration = configuration
15
+ end
16
+
17
+ def call _omit = nil
18
+ codes.map { |(code, label)| model[code: code, label: label, url: "#{url}/#{code}"] }
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :codes, :model, :configuration
24
+
25
+ def url = configuration.http_statuses_url
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pennyworth
4
+ module Loaders
5
+ # Loads projects from API requests for further processing.
6
+ class Projects
7
+ def self.for_git_hub = new(client: Integrations::GitHub::Client.new)
8
+
9
+ def self.for_ruby_gems = new(client: Integrations::RubyGems::Client.new)
10
+
11
+ def initialize client:
12
+ @client = client
13
+ end
14
+
15
+ def call(endpoint) = client.get(endpoint)
16
+
17
+ private
18
+
19
+ attr_reader :client
20
+ end
21
+ end
22
+ end