rimless 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'rimless'
6
+ require 'pp'
7
+
8
+ $stdout.sync = true
9
+
10
+ exit 1 if ARGV.empty?
11
+
12
+ ARGV.each { |cmd| eval(cmd); $stdout.flush }
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ if [ -f ~/.bashrc ]; then
2
+ . ~/.bashrc
3
+ fi
@@ -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>
@@ -0,0 +1,8 @@
1
+ version: "3"
2
+ services:
3
+ test:
4
+ build: .
5
+ network_mode: bridge
6
+ working_dir: /app
7
+ volumes:
8
+ - .:/app
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was generated by Appraisal
4
+
5
+ source 'https://rubygems.org'
6
+
7
+ gem 'activesupport', '~> 4.2.11'
8
+ gem 'railties', '~> 4.2.11'
9
+
10
+ gemspec path: '../'
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was generated by Appraisal
4
+
5
+ source 'https://rubygems.org'
6
+
7
+ gem 'activesupport', '~> 5.0.7'
8
+ gem 'railties', '~> 5.0.7'
9
+
10
+ gemspec path: '../'
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was generated by Appraisal
4
+
5
+ source 'https://rubygems.org'
6
+
7
+ gem 'activesupport', '~> 5.1.6'
8
+ gem 'railties', '~> 5.1.6'
9
+
10
+ gemspec path: '../'
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was generated by Appraisal
4
+
5
+ source 'https://rubygems.org'
6
+
7
+ gem 'activesupport', '~> 5.2.2'
8
+ gem 'railties', '~> 5.2.2'
9
+
10
+ gemspec path: '../'
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