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