simple-service 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rubocop.yml +96 -0
- data/.tm_properties +1 -0
- data/Gemfile +14 -0
- data/Makefile +4 -0
- data/README.md +1 -0
- data/Rakefile +6 -0
- data/VERSION +1 -0
- data/bin/bundle +105 -0
- data/bin/console +15 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/lib/simple/service/action/comment.rb +57 -0
- data/lib/simple/service/action/method_reflection.rb +70 -0
- data/lib/simple/service/action/parameter.rb +46 -0
- data/lib/simple/service/action.rb +193 -0
- data/lib/simple/service/context.rb +52 -0
- data/lib/simple/service/version.rb +24 -0
- data/lib/simple/service.rb +89 -0
- data/lib/simple-service.rb +3 -0
- data/lib/simple.rb +2 -0
- data/log/.gitkeep +0 -0
- data/scripts/release +2 -0
- data/scripts/release.rb +91 -0
- data/scripts/stats +4 -0
- data/scripts/watch +2 -0
- data/simple-service.gemspec +25 -0
- data/spec/simple/service/context_spec.rb +37 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/004_simplecov.rb +13 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9641586855a538c0b450d4731d39d28f23c4ee19abec379f5ac9047888fe9106
|
4
|
+
data.tar.gz: a395f0a7815935557163cc81532ef7e3fc2b97dfc2bf7347e32460481589dd1d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 43a8d60d9c7b1f14913f431b24ebd50db6a41c2b951a41bd8b1c136d466b2d198b0582e7adcbc34b0df0c81206abe79fda05ea25f8e9c74c4f52a5722e23146a
|
7
|
+
data.tar.gz: a0376b41661076f53a4ffe6a20650efc6dda513f762ddd0e96d7c7da0f568b6dc2e687c4fbfb67da759f7ac076ce115468a0fd2ba63a686f7ab15504a1ee5cd8
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.3
|
3
|
+
Exclude:
|
4
|
+
- 'spec/**/*'
|
5
|
+
- 'test/**/*'
|
6
|
+
- 'bin/**/*'
|
7
|
+
- 'tasks/release.rake'
|
8
|
+
- '*.gemspec'
|
9
|
+
- 'Gemfile'
|
10
|
+
- 'Rakefile'
|
11
|
+
- 'scripts/*.rb'
|
12
|
+
|
13
|
+
Metrics/LineLength:
|
14
|
+
Max: 140
|
15
|
+
|
16
|
+
Metrics/MethodLength:
|
17
|
+
Max: 20
|
18
|
+
|
19
|
+
Style/SpecialGlobalVars:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
Style/StringLiterals:
|
23
|
+
EnforcedStyle: double_quotes
|
24
|
+
ConsistentQuotesInMultiline: false
|
25
|
+
|
26
|
+
Style/ClassAndModuleChildren:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
Style/ModuleFunction:
|
30
|
+
Enabled: false
|
31
|
+
|
32
|
+
Style/FrozenStringLiteralComment:
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
Style/Documentation:
|
36
|
+
Enabled: false
|
37
|
+
|
38
|
+
Style/MutableConstant:
|
39
|
+
Enabled: false
|
40
|
+
|
41
|
+
Style/FormatStringToken:
|
42
|
+
Enabled: false
|
43
|
+
|
44
|
+
Style/Lambda:
|
45
|
+
Enabled: false
|
46
|
+
|
47
|
+
Style/SymbolArray:
|
48
|
+
Enabled: false
|
49
|
+
|
50
|
+
Style/FormatString:
|
51
|
+
Enabled: false
|
52
|
+
|
53
|
+
Style/PercentLiteralDelimiters:
|
54
|
+
Enabled: false
|
55
|
+
|
56
|
+
Lint/MissingCopEnableDirective:
|
57
|
+
Enabled: false
|
58
|
+
|
59
|
+
Style/NumericPredicate:
|
60
|
+
Enabled: false
|
61
|
+
|
62
|
+
Style/RegexpLiteral:
|
63
|
+
Enabled: false
|
64
|
+
|
65
|
+
Style/ClassVars:
|
66
|
+
Enabled: false
|
67
|
+
|
68
|
+
Style/ConditionalAssignment:
|
69
|
+
Enabled: false
|
70
|
+
|
71
|
+
Style/IfUnlessModifier:
|
72
|
+
Enabled: false
|
73
|
+
|
74
|
+
Style/PerlBackrefs:
|
75
|
+
Enabled: false
|
76
|
+
|
77
|
+
Style/TrailingUnderscoreVariable:
|
78
|
+
Enabled: false
|
79
|
+
|
80
|
+
Style/StderrPuts:
|
81
|
+
Enabled: false
|
82
|
+
|
83
|
+
Style/NonNilCheck:
|
84
|
+
Enabled: false
|
85
|
+
|
86
|
+
Metrics/ParameterLists:
|
87
|
+
Enabled: false
|
88
|
+
|
89
|
+
Style/StringLiteralsInInterpolation:
|
90
|
+
Enabled: false
|
91
|
+
|
92
|
+
Style/DoubleNegation:
|
93
|
+
Enabled: false
|
94
|
+
|
95
|
+
Style/ParallelAssignment:
|
96
|
+
Enabled: false
|
data/.tm_properties
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
excludeDirectories = "{_build,coverage,assets/node_modules,node_modules,deps,db,cover,priv/static,storage,github,vendor,arena,}"
|
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in {gemname}.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
# --- Development and test dependencies ------------------------------
|
7
|
+
|
8
|
+
group :development, :test do
|
9
|
+
gem 'rake', '~> 11'
|
10
|
+
gem 'rspec', '~> 3.7'
|
11
|
+
# gem 'rubocop', '~> 0.61.1'
|
12
|
+
gem 'simplecov', '~> 0'
|
13
|
+
gem 'byebug'
|
14
|
+
end
|
data/Makefile
ADDED
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# simple-service – a pretty simple and somewhat abstract service description
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
data/bin/bundle
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'bundle' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "rubygems"
|
12
|
+
|
13
|
+
m = Module.new do
|
14
|
+
module_function
|
15
|
+
|
16
|
+
def invoked_as_script?
|
17
|
+
File.expand_path($0) == File.expand_path(__FILE__)
|
18
|
+
end
|
19
|
+
|
20
|
+
def env_var_version
|
21
|
+
ENV["BUNDLER_VERSION"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def cli_arg_version
|
25
|
+
return unless invoked_as_script? # don't want to hijack other binstubs
|
26
|
+
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
|
27
|
+
bundler_version = nil
|
28
|
+
update_index = nil
|
29
|
+
ARGV.each_with_index do |a, i|
|
30
|
+
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
|
31
|
+
bundler_version = a
|
32
|
+
end
|
33
|
+
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
|
34
|
+
bundler_version = $1 || ">= 0.a"
|
35
|
+
update_index = i
|
36
|
+
end
|
37
|
+
bundler_version
|
38
|
+
end
|
39
|
+
|
40
|
+
def gemfile
|
41
|
+
gemfile = ENV["BUNDLE_GEMFILE"]
|
42
|
+
return gemfile if gemfile && !gemfile.empty?
|
43
|
+
|
44
|
+
File.expand_path("../../Gemfile", __FILE__)
|
45
|
+
end
|
46
|
+
|
47
|
+
def lockfile
|
48
|
+
lockfile =
|
49
|
+
case File.basename(gemfile)
|
50
|
+
when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
|
51
|
+
else "#{gemfile}.lock"
|
52
|
+
end
|
53
|
+
File.expand_path(lockfile)
|
54
|
+
end
|
55
|
+
|
56
|
+
def lockfile_version
|
57
|
+
return unless File.file?(lockfile)
|
58
|
+
lockfile_contents = File.read(lockfile)
|
59
|
+
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
|
60
|
+
Regexp.last_match(1)
|
61
|
+
end
|
62
|
+
|
63
|
+
def bundler_version
|
64
|
+
@bundler_version ||= begin
|
65
|
+
env_var_version || cli_arg_version ||
|
66
|
+
lockfile_version || "#{Gem::Requirement.default}.a"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def load_bundler!
|
71
|
+
ENV["BUNDLE_GEMFILE"] ||= gemfile
|
72
|
+
|
73
|
+
# must dup string for RG < 1.8 compatibility
|
74
|
+
activate_bundler(bundler_version.dup)
|
75
|
+
end
|
76
|
+
|
77
|
+
def activate_bundler(bundler_version)
|
78
|
+
if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0")
|
79
|
+
bundler_version = "< 2"
|
80
|
+
end
|
81
|
+
gem_error = activation_error_handling do
|
82
|
+
gem "bundler", bundler_version
|
83
|
+
end
|
84
|
+
return if gem_error.nil?
|
85
|
+
require_error = activation_error_handling do
|
86
|
+
require "bundler/version"
|
87
|
+
end
|
88
|
+
return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION))
|
89
|
+
warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`"
|
90
|
+
exit 42
|
91
|
+
end
|
92
|
+
|
93
|
+
def activation_error_handling
|
94
|
+
yield
|
95
|
+
nil
|
96
|
+
rescue StandardError, LoadError => e
|
97
|
+
e
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
m.load_bundler!
|
102
|
+
|
103
|
+
if m.invoked_as_script?
|
104
|
+
load Gem.bin_path("bundler", "bundle")
|
105
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
Bundler.require
|
5
|
+
require "simple-service"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start
|
data/bin/rake
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rake", "rake")
|
data/bin/rspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rspec-core", "rspec")
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# returns the comment for an action
|
2
|
+
class ::Simple::Service::Action::Comment
|
3
|
+
attr_reader :short
|
4
|
+
attr_reader :full
|
5
|
+
|
6
|
+
def self.extract(action:)
|
7
|
+
file, line = action.source_location
|
8
|
+
lines = Extractor.extract_comment_lines(file: file, before_line: line)
|
9
|
+
full = lines[2..-1].join("\n") if lines.length >= 2
|
10
|
+
new short: lines[0], full: full
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(short:, full:)
|
14
|
+
@short, @full = short, full
|
15
|
+
end
|
16
|
+
|
17
|
+
module Extractor
|
18
|
+
extend self
|
19
|
+
|
20
|
+
# reads the source \a file and turns each non-comment into :code and each comment
|
21
|
+
# into a string without the leading comment markup.
|
22
|
+
def parse_source(file)
|
23
|
+
@parsed_sources ||= {}
|
24
|
+
@parsed_sources[file] = _parse_source(file)
|
25
|
+
end
|
26
|
+
|
27
|
+
def _parse_source(file)
|
28
|
+
File.readlines(file).map do |line|
|
29
|
+
case line
|
30
|
+
when /^\s*# ?(.*)$/ then $1
|
31
|
+
when /^\s*end/ then :end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def extract_comment_lines(file:, before_line:)
|
37
|
+
parsed_source = parse_source(file)
|
38
|
+
|
39
|
+
# go down from before_line until we see a line which is either a comment
|
40
|
+
# or an :end. Note that the line at before_line-1 should be the first
|
41
|
+
# line of the method definition in question.
|
42
|
+
last_line = before_line - 1
|
43
|
+
last_line -= 1 while last_line >= 0 && !parsed_source[last_line]
|
44
|
+
|
45
|
+
first_line = last_line
|
46
|
+
first_line -= 1 while first_line >= 0 && parsed_source[first_line]
|
47
|
+
first_line += 1
|
48
|
+
|
49
|
+
comments = parsed_source[first_line..last_line]
|
50
|
+
if comments.include?(:end)
|
51
|
+
[]
|
52
|
+
else
|
53
|
+
parsed_source[first_line..last_line]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# rubocop:disable Metrics/AbcSize
|
2
|
+
|
3
|
+
module ::Simple::Service::Action::MethodReflection # :nodoc:
|
4
|
+
extend self
|
5
|
+
|
6
|
+
#
|
7
|
+
# returns an array with entries like the following:
|
8
|
+
#
|
9
|
+
# [ :key, name, default_value ]
|
10
|
+
# [ :keyreq, name [, nil ] ]
|
11
|
+
# [ :req, name [, nil ] ]
|
12
|
+
# [ :opt, name [, nil ] ]
|
13
|
+
# [ :rest, name [, nil ] ]
|
14
|
+
#
|
15
|
+
def parameters(service, method_id)
|
16
|
+
method = service.instance_method(method_id)
|
17
|
+
parameters = method.parameters
|
18
|
+
|
19
|
+
# method parameters with a :key mode are optional keyword arguments. We only
|
20
|
+
# support defaults for those - if there are none we abort here already.
|
21
|
+
keys = parameters.map { |mode, name| name if mode == :key }.compact
|
22
|
+
return parameters if keys.empty?
|
23
|
+
|
24
|
+
# We are now doing a fake call to the method, with a minimal viable set of
|
25
|
+
# arguments, to let the ruby runtime fill in default values for arguments.
|
26
|
+
# We do not, however, let the call complete. Instead we use a TracePoint to
|
27
|
+
# abort as soon as the method is called, and use the its binding to determine
|
28
|
+
# the default values.
|
29
|
+
|
30
|
+
fake_recipient = Object.new.extend(service)
|
31
|
+
fake_call_args = minimal_arguments(method)
|
32
|
+
|
33
|
+
trace_point = TracePoint.trace(:call) do |tp|
|
34
|
+
throw :received_fake_call, tp.binding if tp.defined_class == service && tp.method_id == method_id
|
35
|
+
end
|
36
|
+
|
37
|
+
bnd = catch(:received_fake_call) do
|
38
|
+
fake_recipient.send(method_id, *fake_call_args)
|
39
|
+
end
|
40
|
+
|
41
|
+
trace_point.disable
|
42
|
+
|
43
|
+
# extract default values from the received binding, and merge with the
|
44
|
+
# parameters array.
|
45
|
+
default_values = keys.each_with_object({}) do |key_parameter, hsh|
|
46
|
+
hsh[key_parameter] = bnd.local_variable_get(key_parameter)
|
47
|
+
end
|
48
|
+
|
49
|
+
parameters.map do |mode, name|
|
50
|
+
[mode, name, default_values[name]]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# returns a minimal Array of arguments, which is suitable for a call to the method
|
57
|
+
def minimal_arguments(method)
|
58
|
+
# Build an arguments array with holds all required parameters. The actual
|
59
|
+
# values for these arguments doesn't matter at all.
|
60
|
+
args = method.parameters.select { |mode, _name| mode == :req }
|
61
|
+
|
62
|
+
# Add a hash with all required keyword arguments
|
63
|
+
required_keyword_args = method.parameters.each_with_object({}) do |(mode, name), hsh|
|
64
|
+
hsh[name] = :anything if mode == :keyreq
|
65
|
+
end
|
66
|
+
args << required_keyword_args if required_keyword_args
|
67
|
+
|
68
|
+
args
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative "method_reflection"
|
2
|
+
|
3
|
+
class ::Simple::Service::Action::Parameter
|
4
|
+
def self.reflect_on_method(service:, name:)
|
5
|
+
reflected_parameters = ::Simple::Service::Action::MethodReflection.parameters(service, name)
|
6
|
+
@parameters = reflected_parameters.map { |ary| new(*ary) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def keyword?
|
10
|
+
[:key, :keyreq].include? @kind
|
11
|
+
end
|
12
|
+
|
13
|
+
def anonymous?
|
14
|
+
[:req, :opt].include? @kind
|
15
|
+
end
|
16
|
+
|
17
|
+
def required?
|
18
|
+
[:req, :keyreq].include? @kind
|
19
|
+
end
|
20
|
+
|
21
|
+
def variadic?
|
22
|
+
@kind == :rest
|
23
|
+
end
|
24
|
+
|
25
|
+
def optional?
|
26
|
+
!required?
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :name
|
30
|
+
attr_reader :kind
|
31
|
+
|
32
|
+
# The parameter's default value (if any)
|
33
|
+
attr_reader :default_value
|
34
|
+
|
35
|
+
def initialize(kind, name, *default_value)
|
36
|
+
# The parameter list matches the values returned from MethodReflection.parameters,
|
37
|
+
# which has two or three entries: <tt>kind, name [ . default_value ]</tt>
|
38
|
+
|
39
|
+
expect! kind => [:req, :opt, :keyreq, :key, :rest]
|
40
|
+
expect! default_value.length => [0, 1]
|
41
|
+
|
42
|
+
@kind = kind
|
43
|
+
@name = name
|
44
|
+
@default_value = default_value[0]
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
2
|
+
# rubocop:disable Metrics/AbcSize
|
3
|
+
# rubocop:disable Metrics/MethodLength
|
4
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
5
|
+
|
6
|
+
module Simple::Service
|
7
|
+
class Action
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
require_relative "./action/comment"
|
12
|
+
require_relative "./action/parameter"
|
13
|
+
|
14
|
+
module Simple::Service
|
15
|
+
class Action
|
16
|
+
ArgumentError = ::Simple::Service::ArgumentError
|
17
|
+
|
18
|
+
IDENTIFIER_PATTERN = "[a-z][a-z0-9_]*"
|
19
|
+
IDENTIFIER_REGEXP = Regexp.compile("\\A#{IDENTIFIER_PATTERN}\\z")
|
20
|
+
|
21
|
+
# determines all services provided by the +service+ service module.
|
22
|
+
def self.enumerate(service:) # :nodoc:
|
23
|
+
service.public_instance_methods(false)
|
24
|
+
.grep(IDENTIFIER_REGEXP)
|
25
|
+
.each_with_object({}) { |name, hsh| hsh[name] = Action.new(service, name) }
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :service
|
29
|
+
attr_reader :name
|
30
|
+
|
31
|
+
# returns an Array of Parameter structures.
|
32
|
+
def parameters
|
33
|
+
@parameters ||= Parameter.reflect_on_method(service: service, name: name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(service, name)
|
37
|
+
@service = service
|
38
|
+
@name = name
|
39
|
+
|
40
|
+
parameters
|
41
|
+
end
|
42
|
+
|
43
|
+
def short_description
|
44
|
+
comment.short
|
45
|
+
end
|
46
|
+
|
47
|
+
def full_description
|
48
|
+
comment.full
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# returns a Comment object
|
54
|
+
#
|
55
|
+
# The comment object is extracted on demand on the first call.
|
56
|
+
def comment
|
57
|
+
@comment ||= Comment.extract(action: self)
|
58
|
+
end
|
59
|
+
|
60
|
+
public
|
61
|
+
|
62
|
+
def source_location
|
63
|
+
@service.instance_method(name).source_location
|
64
|
+
end
|
65
|
+
|
66
|
+
# build a service_instance and run the action, with arguments constructed from
|
67
|
+
# args_hsh and params_hsh.
|
68
|
+
def invoke(args, options)
|
69
|
+
args ||= {}
|
70
|
+
options ||= {}
|
71
|
+
|
72
|
+
# convert Array arguments into a Hash of named arguments. This is strictly
|
73
|
+
# necessary to be able to apply default value-based type conversions. (On
|
74
|
+
# the downside this also means we convert an array to a hash and then back
|
75
|
+
# into an array. This, however, should only be an issue for CLI based action
|
76
|
+
# invocations, because any other use case (that I can think of) should allow
|
77
|
+
# us to provide arguments as a Hash.
|
78
|
+
if args.is_a?(Array)
|
79
|
+
args = convert_argument_array_to_hash(args)
|
80
|
+
end
|
81
|
+
|
82
|
+
# [TODO] Type conversion according to default values.
|
83
|
+
args_ary = build_method_arguments(args, options)
|
84
|
+
|
85
|
+
service_instance = Object.new
|
86
|
+
service_instance.extend service
|
87
|
+
service_instance.public_send(@name, *args_ary)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
module IndifferentHashEx
|
93
|
+
def self.fetch(hsh, name)
|
94
|
+
missing_key!(name) unless hsh
|
95
|
+
|
96
|
+
hsh.fetch(name.to_sym) do
|
97
|
+
hsh.fetch(name.to_s) do
|
98
|
+
missing_key!(name)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.key?(hsh, name)
|
104
|
+
return false unless hsh
|
105
|
+
|
106
|
+
hsh.key?(name.to_sym) || hsh.key?(name.to_s)
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.missing_key!(name)
|
110
|
+
raise ArgumentError, "Missing argument in arguments hash: #{name}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
I = IndifferentHashEx
|
115
|
+
|
116
|
+
# returns an array of arguments suitable to be sent to the action method.
|
117
|
+
def build_method_arguments(args_hsh, params_hsh)
|
118
|
+
args = []
|
119
|
+
keyword_args = {}
|
120
|
+
|
121
|
+
parameters.each do |parameter|
|
122
|
+
if parameter.keyword?
|
123
|
+
if I.key?(params_hsh, parameter.name)
|
124
|
+
keyword_args[parameter.name] = I.fetch(params_hsh, parameter.name)
|
125
|
+
end
|
126
|
+
else
|
127
|
+
if parameter.variadic?
|
128
|
+
if I.key?(args_hsh, parameter.name)
|
129
|
+
args.concat(Array(I.fetch(args_hsh, parameter.name)))
|
130
|
+
end
|
131
|
+
else
|
132
|
+
if !parameter.optional? || I.key?(args_hsh, parameter.name)
|
133
|
+
args << I.fetch(args_hsh, parameter.name)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
unless keyword_args.empty?
|
140
|
+
args << keyword_args
|
141
|
+
end
|
142
|
+
|
143
|
+
args
|
144
|
+
end
|
145
|
+
|
146
|
+
def convert_argument_array_to_hash(ary)
|
147
|
+
# enumerate all of the action's anonymous arguments, trying to match them
|
148
|
+
# against the values in +ary+. If afterwards any arguments are still left
|
149
|
+
# in +ary+ they will be assigned to the variadic arguments array, which
|
150
|
+
# - if a variadic parameter is defined in this action - will be added to
|
151
|
+
# the hash as well.
|
152
|
+
hsh = {}
|
153
|
+
variadic_parameter_name = nil
|
154
|
+
|
155
|
+
parameters.each do |parameter|
|
156
|
+
next if parameter.keyword?
|
157
|
+
parameter_name = parameter.name
|
158
|
+
|
159
|
+
if parameter.variadic?
|
160
|
+
variadic_parameter_name = parameter_name
|
161
|
+
next
|
162
|
+
end
|
163
|
+
|
164
|
+
if ary.empty? && !parameter.optional?
|
165
|
+
raise ::Simple::Service::ArgumentError, "Missing #{parameter_name} parameter"
|
166
|
+
end
|
167
|
+
|
168
|
+
next if ary.empty?
|
169
|
+
|
170
|
+
hsh[parameter_name] = ary.shift
|
171
|
+
end
|
172
|
+
|
173
|
+
# Any arguments are left? Set variadic parameter, if defined, raise an error otherwise.
|
174
|
+
unless ary.empty?
|
175
|
+
unless variadic_parameter_name
|
176
|
+
raise ::Simple::Service::ArgumentError, "Extra parameters: #{ary.map(&:inspect).join(", ")}"
|
177
|
+
end
|
178
|
+
|
179
|
+
hsh[variadic_parameter_name] = ary
|
180
|
+
end
|
181
|
+
|
182
|
+
hsh
|
183
|
+
end
|
184
|
+
|
185
|
+
def full_name
|
186
|
+
"#{service}##{name}"
|
187
|
+
end
|
188
|
+
|
189
|
+
def to_s
|
190
|
+
full_name
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Simple::Service
|
2
|
+
class Context
|
3
|
+
class ReadOnlyError < RuntimeError
|
4
|
+
def initialize(key)
|
5
|
+
super "Cannot overwrite existing context setting #{key.inspect}"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@hsh = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
@hsh[key]
|
17
|
+
end
|
18
|
+
|
19
|
+
def []=(key, value)
|
20
|
+
existing_value = @hsh[key]
|
21
|
+
|
22
|
+
unless existing_value.nil? || existing_value == value
|
23
|
+
raise ReadOnlyError, key
|
24
|
+
end
|
25
|
+
|
26
|
+
@hsh[key] = value
|
27
|
+
end
|
28
|
+
|
29
|
+
IDENTIFIER_PATTERN = "[a-z][a-z0-9_]*"
|
30
|
+
IDENTIFIER_REGEXP = Regexp.compile("\\A#{IDENTIFIER_PATTERN}\\z")
|
31
|
+
ASSIGNMENT_REGEXP = Regexp.compile("\\A(#{IDENTIFIER_PATTERN})=\\z")
|
32
|
+
|
33
|
+
def method_missing(sym, *args, &block)
|
34
|
+
if block
|
35
|
+
super
|
36
|
+
elsif args.count == 0 && sym =~ IDENTIFIER_REGEXP
|
37
|
+
self[sym]
|
38
|
+
elsif args.count == 1 && sym =~ ASSIGNMENT_REGEXP
|
39
|
+
self[$1.to_sym] = args.first
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def respond_to_missing?(sym, include_private = false)
|
46
|
+
return true if IDENTIFIER_REGEXP.maptch?(sym)
|
47
|
+
return true if ASSIGNMENT_REGEXP.maptch?(sym)
|
48
|
+
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Simple::Service
|
2
|
+
module GemHelper
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def version(name)
|
6
|
+
spec = Gem.loaded_specs[name]
|
7
|
+
version = spec ? spec.version.to_s : "0.0.0"
|
8
|
+
version += "+unreleased" if !spec || unreleased?(spec)
|
9
|
+
version
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def unreleased?(spec)
|
15
|
+
return false unless defined?(Bundler::Source::Gemspec)
|
16
|
+
return true if spec.source.is_a?(::Bundler::Source::Gemspec)
|
17
|
+
return true if spec.source.is_a?(::Bundler::Source::Path)
|
18
|
+
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
VERSION = GemHelper.version "simple-service"
|
24
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Simple::Service
|
2
|
+
class ArgumentError < ::ArgumentError
|
3
|
+
end
|
4
|
+
end
|
5
|
+
|
6
|
+
require_relative "service/action"
|
7
|
+
require_relative "service/context"
|
8
|
+
|
9
|
+
# The Simple::Service module.
|
10
|
+
#
|
11
|
+
# To mark a target module as a service module one must include the
|
12
|
+
# Simple::Service module into the target module.
|
13
|
+
#
|
14
|
+
# This serves as a marker that this module is actually intended
|
15
|
+
# to be used as a service.
|
16
|
+
module Simple::Service
|
17
|
+
def self.included(klass)
|
18
|
+
klass.extend ClassMethods
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the current context.
|
22
|
+
def self.context
|
23
|
+
Thread.current[:"Simple::Service.context"]
|
24
|
+
end
|
25
|
+
|
26
|
+
# yields a block with a given context, and restores the previous context
|
27
|
+
# object afterwards.
|
28
|
+
def self.with_context(ctx, &block)
|
29
|
+
expect! ctx => [Simple::Service::Context, nil]
|
30
|
+
_ = block
|
31
|
+
|
32
|
+
old_ctx = Thread.current[:"Simple::Service.context"]
|
33
|
+
Thread.current[:"Simple::Service.context"] = ctx
|
34
|
+
yield
|
35
|
+
ensure
|
36
|
+
Thread.current[:"Simple::Service.context"] = old_ctx
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.action(service, name)
|
40
|
+
actions = self.actions(service)
|
41
|
+
actions[name] || begin
|
42
|
+
action_names = actions.keys.sort
|
43
|
+
informal = "service #{service} has these actions: #{action_names.map(&:inspect).join(", ")}"
|
44
|
+
raise "No such action #{name.inspect}; #{informal}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.service?(service)
|
49
|
+
service.is_a?(Module) && service.include?(self)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.actions(service)
|
53
|
+
raise ArgumentError, "service must be a #{self}" unless service?(service)
|
54
|
+
|
55
|
+
service.__simple_service_actions__
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.invoke(service, name, arguments, params, context: nil)
|
59
|
+
with_context(context) do
|
60
|
+
action(service, name).invoke(arguments, params)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module ClassMethods
|
65
|
+
# returns a Hash of actions provided by the service module.
|
66
|
+
def __simple_service_actions__ # :nodoc:
|
67
|
+
@__simple_service_actions__ ||= Action.enumerate(service: self)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Resolves a service by name. Returns nil if the name does not refer to a service,
|
72
|
+
# or the service module otherwise.
|
73
|
+
def self.resolve(str)
|
74
|
+
return unless str =~ /^[A-Z][A-Za-z0-9_]*(::[A-Z][A-Za-z0-9_]*)*$/
|
75
|
+
|
76
|
+
service = resolve_constant(str)
|
77
|
+
|
78
|
+
return unless service.is_a?(Module)
|
79
|
+
return unless service.include?(::Simple::Service)
|
80
|
+
|
81
|
+
service
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.resolve_constant(str)
|
85
|
+
const_get(str)
|
86
|
+
rescue NameError
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
end
|
data/lib/simple.rb
ADDED
data/log/.gitkeep
ADDED
File without changes
|
data/scripts/release
ADDED
data/scripts/release.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# -- helpers ------------------------------------------------------------------
|
4
|
+
|
5
|
+
def sys(cmd)
|
6
|
+
STDERR.puts "> #{cmd}"
|
7
|
+
system cmd
|
8
|
+
return true if $?.success?
|
9
|
+
|
10
|
+
STDERR.puts "> #{cmd} returned with exitstatus #{$?.exitstatus}"
|
11
|
+
$?.success?
|
12
|
+
end
|
13
|
+
|
14
|
+
def sys!(cmd, error: nil)
|
15
|
+
return true if sys(cmd)
|
16
|
+
STDERR.puts error if error
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
|
20
|
+
def die!(msg)
|
21
|
+
STDERR.puts msg
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
ROOT = File.expand_path("#{File.dirname(__FILE__)}/..")
|
26
|
+
|
27
|
+
GEMSPEC = Dir.glob("*.gemspec").first || die!("Missing gemspec file.")
|
28
|
+
|
29
|
+
# -- Version reading and bumping ----------------------------------------------
|
30
|
+
|
31
|
+
module Version
|
32
|
+
extend self
|
33
|
+
|
34
|
+
VERSION_FILE = "#{Dir.getwd}/VERSION"
|
35
|
+
|
36
|
+
def read_version
|
37
|
+
version = File.exist?(VERSION_FILE) ? File.read(VERSION_FILE) : "0.0.1"
|
38
|
+
version.chomp!
|
39
|
+
raise "Invalid version number in #{VERSION_FILE}" unless version =~ /^\d+\.\d+\.\d+$/
|
40
|
+
version
|
41
|
+
end
|
42
|
+
|
43
|
+
def auto_version_bump
|
44
|
+
old_version_number = read_version
|
45
|
+
old = old_version_number.split('.')
|
46
|
+
|
47
|
+
current = old[0..-2] << old[-1].next
|
48
|
+
current.join('.')
|
49
|
+
end
|
50
|
+
|
51
|
+
def bump_version
|
52
|
+
next_version = ENV["VERSION"] || auto_version_bump
|
53
|
+
File.open(VERSION_FILE, "w") { |io| io.write next_version }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# -- check, bump, release a new gem version -----------------------------------
|
58
|
+
|
59
|
+
Dir.chdir ROOT
|
60
|
+
$BASE_BRANCH = ENV['BRANCH'] || 'master'
|
61
|
+
|
62
|
+
# ENV["BUNDLE_GEMFILE"] = "#{Dir.getwd}/Gemfile"
|
63
|
+
# sys! "bundle install"
|
64
|
+
|
65
|
+
sys! "git diff --exit-code > /dev/null", error: 'There are unstaged changes in your working directory'
|
66
|
+
sys! "git diff --cached --exit-code > /dev/null", error: 'There are staged but uncommitted changes'
|
67
|
+
|
68
|
+
sys! "git checkout #{$BASE_BRANCH}"
|
69
|
+
sys! "git pull"
|
70
|
+
|
71
|
+
Version.bump_version
|
72
|
+
version = Version.read_version
|
73
|
+
|
74
|
+
sys! "git add VERSION"
|
75
|
+
sys! "git commit -m \"bump gem to v#{version}\""
|
76
|
+
sys! "git tag -a v#{version} -m \"Tag #{version}\""
|
77
|
+
|
78
|
+
sys! "gem build #{GEMSPEC}"
|
79
|
+
|
80
|
+
sys! "git push origin #{$BASE_BRANCH}"
|
81
|
+
sys! 'git push --tags --force'
|
82
|
+
sys! "gem push #{Dir.glob('*.gem').first}"
|
83
|
+
|
84
|
+
sys! "mkdir -p pkg"
|
85
|
+
sys! "mv *.gem pkg"
|
86
|
+
|
87
|
+
STDERR.puts <<-MSG
|
88
|
+
================================================================================
|
89
|
+
Thank you for releasing a new gem version. You made my day.
|
90
|
+
================================================================================
|
91
|
+
MSG
|
data/scripts/stats
ADDED
data/scripts/watch
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# lib = File.expand_path('../lib', __FILE__)
|
2
|
+
# $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "simple-service"
|
6
|
+
gem.version = File.read("VERSION")
|
7
|
+
|
8
|
+
gem.authors = [ "radiospiel" ]
|
9
|
+
gem.email = "eno@radiospiel.org"
|
10
|
+
gem.homepage = "http://github.com/radiospiel/simple-service"
|
11
|
+
gem.summary = "Pretty simple and somewhat abstract service description"
|
12
|
+
|
13
|
+
gem.description = "Pretty simple and somewhat abstract service description"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = %w(lib)
|
18
|
+
|
19
|
+
# executables are used for development purposes only
|
20
|
+
gem.executables = []
|
21
|
+
|
22
|
+
gem.required_ruby_version = '~> 2.5'
|
23
|
+
|
24
|
+
gem.add_dependency "expectation", "~> 1"
|
25
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Simple::Service::Context do
|
4
|
+
let(:context) { Simple::Service::Context.new }
|
5
|
+
|
6
|
+
before do
|
7
|
+
context.one = 1
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "reading" do
|
11
|
+
it "returns a value if set" do
|
12
|
+
expect(context.one).to eq(1)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns nil if not set" do
|
16
|
+
expect(context.two).to be_nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "writing" do
|
21
|
+
it "sets a value if it does not exist yet" do
|
22
|
+
context.two = 2
|
23
|
+
expect(context.two).to eq(2)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "raises a ReadOnly error if the value exists and is not equal" do
|
27
|
+
expect {
|
28
|
+
context.one = 2
|
29
|
+
}.to raise_error(::Simple::Service::Context::ReadOnlyError)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "sets the value if it exists and is equal" do
|
33
|
+
context.one = 1
|
34
|
+
expect(context.one).to eq(1)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
ENV["RACK_ENV"] = "test"
|
2
|
+
ENV["RAILS_ENV"] = "test"
|
3
|
+
|
4
|
+
require "byebug"
|
5
|
+
require "rspec"
|
6
|
+
|
7
|
+
Dir.glob("./spec/support/**/*.rb").sort.each { |path| load path }
|
8
|
+
|
9
|
+
require "simple/service"
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.run_all_when_everything_filtered = true
|
13
|
+
config.filter_run focus: (ENV["CI"] != "true")
|
14
|
+
config.expect_with(:rspec) { |c| c.syntax = :expect }
|
15
|
+
config.order = "random"
|
16
|
+
config.example_status_persistence_file_path = ".rspec.data"
|
17
|
+
|
18
|
+
config.backtrace_exclusion_patterns << /spec\/support/
|
19
|
+
config.backtrace_exclusion_patterns << /spec_helper/
|
20
|
+
config.backtrace_exclusion_patterns << /database_cleaner/
|
21
|
+
|
22
|
+
# config.around(:each) do |example|
|
23
|
+
# example.run
|
24
|
+
# end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple-service
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- radiospiel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-11-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: expectation
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
description: Pretty simple and somewhat abstract service description
|
28
|
+
email: eno@radiospiel.org
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- ".gitignore"
|
34
|
+
- ".rubocop.yml"
|
35
|
+
- ".tm_properties"
|
36
|
+
- Gemfile
|
37
|
+
- Makefile
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- VERSION
|
41
|
+
- bin/bundle
|
42
|
+
- bin/console
|
43
|
+
- bin/rake
|
44
|
+
- bin/rspec
|
45
|
+
- lib/simple-service.rb
|
46
|
+
- lib/simple.rb
|
47
|
+
- lib/simple/service.rb
|
48
|
+
- lib/simple/service/action.rb
|
49
|
+
- lib/simple/service/action/comment.rb
|
50
|
+
- lib/simple/service/action/method_reflection.rb
|
51
|
+
- lib/simple/service/action/parameter.rb
|
52
|
+
- lib/simple/service/context.rb
|
53
|
+
- lib/simple/service/version.rb
|
54
|
+
- log/.gitkeep
|
55
|
+
- scripts/release
|
56
|
+
- scripts/release.rb
|
57
|
+
- scripts/stats
|
58
|
+
- scripts/watch
|
59
|
+
- simple-service.gemspec
|
60
|
+
- spec/simple/service/context_spec.rb
|
61
|
+
- spec/spec_helper.rb
|
62
|
+
- spec/support/004_simplecov.rb
|
63
|
+
homepage: http://github.com/radiospiel/simple-service
|
64
|
+
licenses: []
|
65
|
+
metadata: {}
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '2.5'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubygems_version: 3.0.4
|
82
|
+
signing_key:
|
83
|
+
specification_version: 4
|
84
|
+
summary: Pretty simple and somewhat abstract service description
|
85
|
+
test_files:
|
86
|
+
- spec/simple/service/context_spec.rb
|
87
|
+
- spec/spec_helper.rb
|
88
|
+
- spec/support/004_simplecov.rb
|