rimless 0.1.0
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 +7 -0
- data/.editorconfig +30 -0
- data/.gitignore +15 -0
- data/.rspec +3 -0
- data/.rubocop.yml +53 -0
- data/.simplecov +3 -0
- data/.travis.yml +27 -0
- data/.yardopts +6 -0
- data/Appraisals +21 -0
- data/CHANGELOG.md +8 -0
- data/Dockerfile +28 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/Makefile +149 -0
- data/README.md +427 -0
- data/Rakefile +76 -0
- data/bin/console +16 -0
- data/bin/run +12 -0
- data/bin/setup +8 -0
- data/config/docker/.bash_profile +3 -0
- data/config/docker/.bashrc +49 -0
- data/config/docker/.inputrc +17 -0
- data/doc/assets/project.svg +68 -0
- data/docker-compose.yml +8 -0
- data/gemfiles/rails_4.2.gemfile +10 -0
- data/gemfiles/rails_5.0.gemfile +10 -0
- data/gemfiles/rails_5.1.gemfile +10 -0
- data/gemfiles/rails_5.2.gemfile +10 -0
- data/lib/rimless.rb +37 -0
- data/lib/rimless/avro_helpers.rb +46 -0
- data/lib/rimless/avro_utils.rb +96 -0
- data/lib/rimless/configuration.rb +57 -0
- data/lib/rimless/configuration_handling.rb +75 -0
- data/lib/rimless/dependencies.rb +55 -0
- data/lib/rimless/kafka_helpers.rb +106 -0
- data/lib/rimless/railtie.rb +25 -0
- data/lib/rimless/rspec.rb +40 -0
- data/lib/rimless/rspec/helpers.rb +17 -0
- data/lib/rimless/rspec/matchers.rb +286 -0
- data/lib/rimless/version.rb +6 -0
- data/rimless.gemspec +48 -0
- metadata +382 -0
data/Rakefile
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'rails/code_statistics'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
9
|
+
|
10
|
+
task default: :spec
|
11
|
+
|
12
|
+
# Load some railties tasks
|
13
|
+
load 'rails/tasks/statistics.rake'
|
14
|
+
load 'rails/tasks/annotations.rake'
|
15
|
+
|
16
|
+
# Clear the default statistics directory constant
|
17
|
+
#
|
18
|
+
# rubocop:disable Style/MutableConstant because we define it
|
19
|
+
Object.send(:remove_const, :STATS_DIRECTORIES)
|
20
|
+
::STATS_DIRECTORIES = []
|
21
|
+
# rubocop:enable Style/MutableConstant
|
22
|
+
|
23
|
+
# Monkey patch the Rails +CodeStatistics+ class to support configurable
|
24
|
+
# patterns per path. This is reuqired to support top-level only file matches.
|
25
|
+
class CodeStatistics
|
26
|
+
DEFAULT_PATTERN = /^(?!\.).*?\.(rb|js|coffee|rake)$/.freeze
|
27
|
+
|
28
|
+
# Pass the possible +pattern+ argument down to the
|
29
|
+
# +calculate_directory_statistics+ method call.
|
30
|
+
def calculate_statistics
|
31
|
+
Hash[@pairs.map do |pair|
|
32
|
+
[pair.first, calculate_directory_statistics(*pair[1..-1])]
|
33
|
+
end]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Match the pattern against the individual file name and the relative file
|
37
|
+
# path. This allows top-level only matches.
|
38
|
+
def calculate_directory_statistics(directory, pattern = DEFAULT_PATTERN)
|
39
|
+
stats = CodeStatisticsCalculator.new
|
40
|
+
|
41
|
+
Dir.foreach(directory) do |file_name|
|
42
|
+
path = "#{directory}/#{file_name}"
|
43
|
+
|
44
|
+
if File.directory?(path) && (/^\./ !~ file_name)
|
45
|
+
stats.add(calculate_directory_statistics(path, pattern))
|
46
|
+
elsif file_name =~ pattern || path =~ pattern
|
47
|
+
stats.add_by_file_path(path)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
stats
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Configure all code statistics directories
|
56
|
+
vendors = [
|
57
|
+
# @TODO: Add your library structure here
|
58
|
+
# [:unshift, 'Clients', 'lib/hausgold/client'],
|
59
|
+
# [:unshift, 'Top-levels', 'lib', %r{lib(/hausgold)?/[^/]+\.rb$}],
|
60
|
+
# [:unshift, 'Clients specs', 'spec/client'],
|
61
|
+
# [:unshift, 'Top-levels specs', 'spec',
|
62
|
+
# %r{spec/rimless(_spec\.rb|/[^/]+\.rb$)}]
|
63
|
+
].reverse
|
64
|
+
|
65
|
+
vendors.each do |method, type, dir, pattern|
|
66
|
+
::STATS_DIRECTORIES.send(method, [type, dir, pattern].compact)
|
67
|
+
::CodeStatistics::TEST_TYPES << type if type.include? 'specs'
|
68
|
+
end
|
69
|
+
|
70
|
+
# Setup annotations
|
71
|
+
ENV['SOURCE_ANNOTATION_DIRECTORIES'] = 'spec,doc'
|
72
|
+
|
73
|
+
desc 'Enumerate all annotations'
|
74
|
+
task :notes do
|
75
|
+
SourceAnnotationExtractor.enumerate '@?OPTIMIZE|@?FIXME|@?TODO', tag: true
|
76
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'rimless'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
9
|
+
# with your gem easier. You can also use a different console, if you like.
|
10
|
+
|
11
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
12
|
+
# require "pry"
|
13
|
+
# Pry.start
|
14
|
+
|
15
|
+
require 'irb'
|
16
|
+
IRB.start(__FILE__)
|
data/bin/run
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# ~/.bashrc: executed by bash(1) for non-login shells.
|
2
|
+
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
|
3
|
+
# for examples
|
4
|
+
|
5
|
+
_GEM_PATHS=$(ls -d1 ${HOME}/.gem/ruby/*/bin 2>/dev/null | paste -sd ':')
|
6
|
+
_APP_PATHS=$(ls -d1 /app/vendor/bundle/ruby/*/bin 2>/dev/null | paste -sd ':')
|
7
|
+
|
8
|
+
export PATH="${_GEM_PATHS}:${_APP_PATHS}:${PATH}"
|
9
|
+
export PATH="/app/node_modules/.bin:${HOME}/.bin:/app/bin:${PATH}"
|
10
|
+
|
11
|
+
# Disable the autostart of all supervisord units
|
12
|
+
sudo sed -i 's/autostart=.*/autostart=false/g' /etc/supervisor/conf.d/*
|
13
|
+
|
14
|
+
# Start the supervisord (empty, no units)
|
15
|
+
sudo supervisord >/dev/null 2>&1 &
|
16
|
+
|
17
|
+
# Wait for supervisord
|
18
|
+
while ! supervisorctl status >/dev/null 2>&1; do sleep 1; done
|
19
|
+
|
20
|
+
# Boot the mDNS stack
|
21
|
+
echo '# Start the mDNS stack'
|
22
|
+
sudo supervisorctl start dbus avahi
|
23
|
+
echo
|
24
|
+
|
25
|
+
export MAKE_ENV=baremetal
|
26
|
+
|
27
|
+
function watch-make-test()
|
28
|
+
{
|
29
|
+
while [ 1 ]; do
|
30
|
+
inotifywait --quiet -r `pwd` -e close_write --format '%e -> %w%f'
|
31
|
+
make test
|
32
|
+
done
|
33
|
+
}
|
34
|
+
|
35
|
+
function watch-make()
|
36
|
+
{
|
37
|
+
while [ 1 ]; do
|
38
|
+
inotifywait --quiet -r `pwd` -e close_write --format '%e -> %w%f'
|
39
|
+
make $@
|
40
|
+
done
|
41
|
+
}
|
42
|
+
|
43
|
+
function watch-run()
|
44
|
+
{
|
45
|
+
while [ 1 ]; do
|
46
|
+
inotifywait --quiet -r `pwd` -e close_write --format '%e -> %w%f'
|
47
|
+
bash -c "$@"
|
48
|
+
done
|
49
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# mappings for Ctrl-left-arrow and Ctrl-right-arrow for word moving
|
2
|
+
"\e[1;5C": forward-word
|
3
|
+
"\e[1;5D": backward-word
|
4
|
+
"\e[5C": forward-word
|
5
|
+
"\e[5D": backward-word
|
6
|
+
"\e\e[C": forward-word
|
7
|
+
"\e\e[D": backward-word
|
8
|
+
|
9
|
+
# handle common Home/End escape codes
|
10
|
+
"\e[1~": beginning-of-line
|
11
|
+
"\e[4~": end-of-line
|
12
|
+
"\e[7~": beginning-of-line
|
13
|
+
"\e[8~": end-of-line
|
14
|
+
"\eOH": beginning-of-line
|
15
|
+
"\eOF": end-of-line
|
16
|
+
"\e[H": beginning-of-line
|
17
|
+
"\e[F": end-of-line
|
@@ -0,0 +1,68 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
2
|
+
<svg
|
3
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
4
|
+
xmlns:cc="http://creativecommons.org/ns#"
|
5
|
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
6
|
+
xmlns:svg="http://www.w3.org/2000/svg"
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
8
|
+
version="1.1"
|
9
|
+
id="Ebene_1"
|
10
|
+
x="0px"
|
11
|
+
y="0px"
|
12
|
+
viewBox="0 0 800 200"
|
13
|
+
xml:space="preserve"
|
14
|
+
width="800"
|
15
|
+
height="200"><metadata
|
16
|
+
id="metadata33"><rdf:RDF><cc:Work
|
17
|
+
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
18
|
+
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
19
|
+
id="defs31" />
|
20
|
+
<style
|
21
|
+
type="text/css"
|
22
|
+
id="style2">
|
23
|
+
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#E73E11;}
|
24
|
+
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#0371B9;}
|
25
|
+
.st2{fill:#132E48;}
|
26
|
+
.st3{font-family:'OpenSans-Bold';}
|
27
|
+
.st4{font-size:29.5168px;}
|
28
|
+
.st5{fill-rule:evenodd;clip-rule:evenodd;fill:none;}
|
29
|
+
.st6{opacity:0.5;fill:#132E48;}
|
30
|
+
.st7{font-family:'OpenSans';}
|
31
|
+
.st8{font-size:12px;}
|
32
|
+
</style>
|
33
|
+
<g
|
34
|
+
transform="translate(0,1.53584)"
|
35
|
+
id="g828"><g
|
36
|
+
transform="translate(35.93985,35.66416)"
|
37
|
+
id="g8">
|
38
|
+
<path
|
39
|
+
style="clip-rule:evenodd;fill:#e73e11;fill-rule:evenodd"
|
40
|
+
id="path4"
|
41
|
+
d="m -0.1,124.4 c 0,0 33.7,-123.2 66.7,-123.2 12.8,0 26.9,21.9 38.8,47.2 -23.6,27.9 -66.6,59.7 -94,76 -7.1,0 -11.5,0 -11.5,0 z"
|
42
|
+
class="st0" />
|
43
|
+
<path
|
44
|
+
style="clip-rule:evenodd;fill:#0371b9;fill-rule:evenodd"
|
45
|
+
id="path6"
|
46
|
+
d="m 88.1,101.8 c 13.5,-10.4 18.4,-16.2 27.1,-25.4 10,25.7 16.7,48 16.7,48 0,0 -41.4,0 -78,0 14.6,-7.9 18.7,-10.7 34.2,-22.6 z"
|
47
|
+
class="st1" />
|
48
|
+
</g><text
|
49
|
+
y="106.40316"
|
50
|
+
x="192.43155"
|
51
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:29.51733398px;font-family:'Open Sans', sans-serif;-inkscape-font-specification:'OpenSans-Bold, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#132e48"
|
52
|
+
id="text10"
|
53
|
+
class="st2 st3 st4">rimless</text>
|
54
|
+
<rect
|
55
|
+
style="clip-rule:evenodd;fill:none;fill-rule:evenodd"
|
56
|
+
id="rect12"
|
57
|
+
height="24"
|
58
|
+
width="314.5"
|
59
|
+
class="st5"
|
60
|
+
y="118.06416"
|
61
|
+
x="194.23985" /><text
|
62
|
+
y="127.22146"
|
63
|
+
x="194.21715"
|
64
|
+
style="font-size:12px;font-family:'Open Sans', sans-serif;opacity:0.5;fill:#132e48;-inkscape-font-specification:'Open Sans, sans-serif, Normal';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;"
|
65
|
+
id="text14"
|
66
|
+
class="st6 st7 st8">A bundle of opinionated Apache Kafka / Confluent Schema Registry helpers</text>
|
67
|
+
</g>
|
68
|
+
</svg>
|
data/docker-compose.yml
ADDED
data/lib/rimless.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/concern'
|
5
|
+
require 'active_support/configurable'
|
6
|
+
require 'active_support/time'
|
7
|
+
require 'active_support/time_with_zone'
|
8
|
+
require 'active_support/core_ext/object'
|
9
|
+
require 'active_support/core_ext/module'
|
10
|
+
require 'active_support/core_ext/hash'
|
11
|
+
require 'active_support/core_ext/string'
|
12
|
+
require 'waterdrop'
|
13
|
+
require 'avro_turf/messaging'
|
14
|
+
require 'sparsify'
|
15
|
+
require 'erb'
|
16
|
+
require 'pp'
|
17
|
+
|
18
|
+
# The top level namespace for the rimless gem.
|
19
|
+
module Rimless
|
20
|
+
# Top level elements
|
21
|
+
autoload :Configuration, 'rimless/configuration'
|
22
|
+
autoload :ConfigurationHandling, 'rimless/configuration_handling'
|
23
|
+
autoload :AvroHelpers, 'rimless/avro_helpers'
|
24
|
+
autoload :AvroUtils, 'rimless/avro_utils'
|
25
|
+
autoload :KafkaHelpers, 'rimless/kafka_helpers'
|
26
|
+
autoload :Dependencies, 'rimless/dependencies'
|
27
|
+
|
28
|
+
# Load standalone code
|
29
|
+
require 'rimless/version'
|
30
|
+
require 'rimless/railtie' if defined? Rails
|
31
|
+
|
32
|
+
# Include top-level features
|
33
|
+
include Rimless::ConfigurationHandling
|
34
|
+
include Rimless::AvroHelpers
|
35
|
+
include Rimless::KafkaHelpers
|
36
|
+
include Rimless::Dependencies
|
37
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rimless
|
4
|
+
# The top-level Apache Avro helpers.
|
5
|
+
module AvroHelpers
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
# A top-level avro instance
|
10
|
+
mattr_accessor :avro
|
11
|
+
|
12
|
+
# The Apache Avro Ruby gem requires simple typed hashes for encoding.
|
13
|
+
# This forces us to convert eg. Grape entity representations into simple
|
14
|
+
# string-keyed hashes. Use this method to prepare a hash for the Apache
|
15
|
+
# Avro serialization.
|
16
|
+
#
|
17
|
+
# Note about the implementation: JSON serialization and parsing is the
|
18
|
+
# simplest and fastest way to accomplish this.
|
19
|
+
#
|
20
|
+
# @param hash [Hash{Mixed => Mixed}] the hash to sanitize
|
21
|
+
# @return [Hash{String => Mixed}] the simple typed input hash
|
22
|
+
def avro_to_h(hash)
|
23
|
+
JSON.parse(hash.to_json)
|
24
|
+
end
|
25
|
+
alias_method :avro_sanitize, :avro_to_h
|
26
|
+
|
27
|
+
# Convert the given deep hash into a sparsed flat hash while transforming
|
28
|
+
# all values to strings. This allows to convert a schema-less hash to a
|
29
|
+
# Apache Avro compatible map.
|
30
|
+
#
|
31
|
+
# @see http://avro.apache.org/docs/current/spec.html#Maps
|
32
|
+
# @example Convert schema-less hash
|
33
|
+
# avro_schemaless_map(a: { b: { c: true } })
|
34
|
+
# # => { "a.b.c" => "true" }
|
35
|
+
#
|
36
|
+
# @param hash [Hash{Mixed => Mixed}] the deep hash
|
37
|
+
# @return [Hash{String => String}] the flatted and sparsed hash
|
38
|
+
def avro_schemaless_h(hash)
|
39
|
+
Sparsify(hash, sparse_array: true)
|
40
|
+
.transform_values(&:to_s)
|
41
|
+
.transform_keys { |key| key.delete('\\') }
|
42
|
+
end
|
43
|
+
alias_method :avro_schemaless_map, :avro_schemaless_h
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rimless
|
4
|
+
# Due to dynamic contrains on the Apache Avro schemas we need to compile our
|
5
|
+
# schema templates to actual ready-to-consume schemas. The namespace part of
|
6
|
+
# the schemas and cross-references to other schemas must be rendered
|
7
|
+
# according to the dynamic namespace prefix which reflects the application
|
8
|
+
# environment. Unfortunately we need to mess around with actual files to
|
9
|
+
# support the Avro and AvroTurf gems.
|
10
|
+
class AvroUtils
|
11
|
+
attr_reader :namespace, :env
|
12
|
+
|
13
|
+
# Create a new instance of the +AvroUtil+ class.
|
14
|
+
#
|
15
|
+
# @return [AvroUtil] the new instance
|
16
|
+
def initialize
|
17
|
+
@namespace = ENV.fetch('KAFKA_SCHEMA_SUBJECT_PREFIX',
|
18
|
+
Rimless.topic_prefix).tr('-', '_').gsub(/\.$/, '')
|
19
|
+
@env = @namespace.split('.').first
|
20
|
+
end
|
21
|
+
|
22
|
+
# Clean and recompile all templated Avro schema files to their respective
|
23
|
+
# output path.
|
24
|
+
def recompile_schemas
|
25
|
+
clear
|
26
|
+
Dir[base_path.join('**', '*.erb')].each { |src| render_file(src) }
|
27
|
+
end
|
28
|
+
|
29
|
+
# Render (compile) a single Avro schema template. The given source file
|
30
|
+
# path will serve to calculate the destination path. So even deep path'ed
|
31
|
+
# templates will keep their hierarchy.
|
32
|
+
#
|
33
|
+
# @param src [String] the Avro schema template file path
|
34
|
+
def render_file(src)
|
35
|
+
# Convert the template path to the destination path
|
36
|
+
dest = schema_path(src)
|
37
|
+
# Create the deep path when not yet existing
|
38
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
39
|
+
# Write the rendered file contents to the destination
|
40
|
+
File.write(dest, ERB.new(File.read(src)).result(binding))
|
41
|
+
# Check the written file for correct JSON
|
42
|
+
validate_file(dest)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Check the given file for valid JSON.
|
46
|
+
#
|
47
|
+
# @param dest [Pathname, File, IO] the file to check
|
48
|
+
# @raise [JSON::ParserError] when invalid
|
49
|
+
#
|
50
|
+
# rubocop:disable Security/JSONLoad because we wrote the file contents
|
51
|
+
def validate_file(dest)
|
52
|
+
JSON.load(dest)
|
53
|
+
rescue JSON::ParserError => err
|
54
|
+
path = File.expand_path(dest.is_a?(File) ? dest.path : dest.to_s)
|
55
|
+
prefix = "Invalid JSON detected: #{path}"
|
56
|
+
Rimless.logger.fatal("#{prefix}\n#{err.message}")
|
57
|
+
err.message.prepend("#{prefix} - ")
|
58
|
+
raise err
|
59
|
+
end
|
60
|
+
# rubocop:enable Security/JSONLoad
|
61
|
+
|
62
|
+
# Clear previous compiled Avro schema files to provide a clean rebuild.
|
63
|
+
def clear
|
64
|
+
FileUtils.rm_rf(output_path)
|
65
|
+
FileUtils.mkdir_p(output_path)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return the compiled Avro schema file path for the given Avro schema
|
69
|
+
# template.
|
70
|
+
#
|
71
|
+
# @param src [String] the Avro schema template file path
|
72
|
+
# @return [Pathname] the resulting schema file path
|
73
|
+
def schema_path(src)
|
74
|
+
# No trailing dot on the prefix namespace directory
|
75
|
+
prefix = env.remove(/\.$/)
|
76
|
+
# Calculate the destination path based on the source file
|
77
|
+
Pathname.new(src.gsub(/^#{base_path}/, output_path.join(prefix).to_s)
|
78
|
+
.gsub(/\.erb$/, ''))
|
79
|
+
end
|
80
|
+
|
81
|
+
# Return the base path of the Avro schemas on our project.
|
82
|
+
#
|
83
|
+
# @return [Pathname] the Avro schemas base path
|
84
|
+
def base_path
|
85
|
+
Rimless.configuration.avro_schema_path
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return the path to the compiled Avro schemas. This path must be consumed
|
89
|
+
# by the +AvroTurf::Messaging+ constructor.
|
90
|
+
#
|
91
|
+
# @return [Pathname] the compiled Avro schemas path
|
92
|
+
def output_path
|
93
|
+
Rimless.configuration.compiled_avro_schema_path
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|