metacrunch 2.1.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +14 -6
  3. data/Rakefile +1 -0
  4. data/bin/console +11 -0
  5. data/lib/metacrunch/file/reader/file_system_fetcher.rb +21 -0
  6. data/lib/metacrunch/file/reader/plain_file_reader.rb +33 -0
  7. data/lib/metacrunch/file/reader/scp_fetcher.rb +56 -0
  8. data/lib/metacrunch/file/reader/tar_file_reader.rb +37 -0
  9. data/lib/metacrunch/file/reader/zip_file_reader.rb +30 -0
  10. data/lib/metacrunch/file/reader.rb +72 -0
  11. data/lib/metacrunch/file.rb +23 -0
  12. data/lib/metacrunch/file_reader.rb +3 -3
  13. data/lib/metacrunch/file_writer.rb +3 -3
  14. data/lib/metacrunch/hash.rb +51 -0
  15. data/lib/metacrunch/processor.rb +10 -0
  16. data/lib/metacrunch/snr/section.rb +15 -4
  17. data/lib/metacrunch/snr.rb +16 -0
  18. data/lib/metacrunch/transformator/transformation/step.rb +45 -0
  19. data/lib/metacrunch/transformator/transformation.rb +48 -0
  20. data/lib/metacrunch/transformator.rb +5 -0
  21. data/lib/metacrunch/transformer.rb +6 -2
  22. data/lib/metacrunch/version.rb +1 -1
  23. data/lib/metacrunch.rb +6 -6
  24. data/metacrunch.gemspec +5 -1
  25. data/spec/assets/file/some_file +1 -0
  26. data/spec/assets/file/some_file.gz +0 -0
  27. data/spec/assets/file/tar_archive.tar +0 -0
  28. data/spec/assets/file/tar_gz_archive.tar.gz +0 -0
  29. data/spec/assets/file/tgz_archive.tgz +0 -0
  30. data/spec/assets/file/zip_archive.zip +0 -0
  31. data/spec/assets/sql_lite_config.rb +17 -0
  32. data/spec/file_writer_spec.rb +3 -3
  33. data/spec/metacrunch/file/reader/plain_file_reader_spec.rb +11 -0
  34. data/spec/metacrunch/file/reader/tar_reader_spec.rb +15 -0
  35. data/spec/metacrunch/file/reader/zip_reader_spec.rb +13 -0
  36. data/spec/metacrunch/file/reader_spec.rb +47 -0
  37. data/spec/metacrunch/file_spec.rb +15 -0
  38. data/spec/metacrunch/hash_spec.rb +93 -0
  39. data/spec/metacrunch/transformator/transformation_spec.rb +28 -0
  40. data/spec/snr_spec.rb +2 -2
  41. data/spec/spec_helper.rb +31 -0
  42. metadata +100 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5bcfeeb60f1625e155e0af9a3b97fdc4d580f5d8
4
- data.tar.gz: 9f157ad5acb41c7fe3fc8bad5585653a5a6ff41a
3
+ metadata.gz: ca4236a24f7c856c544dc3eef7f933eca1e0b4d7
4
+ data.tar.gz: 4ae2e318c548e41183759499d67211b0ed316b7c
5
5
  SHA512:
6
- metadata.gz: d2a45e6ba7218f8b283c9b018b76129ce98187037c84986a11e27bffdad3cde4ce9f51c0112c5e34d17b40b5c46e101ddba74fe93f0fe48e00f51a4859da96a2
7
- data.tar.gz: 39ff3a35ab79367db1af87ae9ba7abc92a957eb8b92a1c040eaca3cdc4140cde0e15501fe7204afb451fc971ecf9ecae934f74aac39f68f71f6e0cc0fe95e7a6
6
+ metadata.gz: fbc4b38921697648b09fa6f5b70665b3fceb3f9fa9fe1407da2d578515389646405b3decb7a0ce10dd056bd53a85b0a4cf9db909644f7fe658fc347b2a1938ad
7
+ data.tar.gz: 861bee6defe53134bd865742eac8aeb7731d4cf9c7252f12cc57521105057241637354278ec598f900a70bb52785e6f3f5dda351b7d8b9b38ae5ea732e64e063
data/Gemfile CHANGED
@@ -1,17 +1,25 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
+ # Specify your gem's dependencies in your gemspec
3
4
  gemspec
4
5
 
5
- gem "rake"
6
- gem "rspec", "~> 3.2.0"
6
+ group :development do
7
+ gem "bundler"
8
+ gem "nokogiri"
9
+ gem "rake"
10
+ gem "rspec", ">= 3.0.0", "< 4.0.0"
11
+ gem "simplecov", ">= 0.8.0"
7
12
 
8
- if !ENV["CI"]
9
- group :development do
13
+ if !ENV["CI"]
10
14
  gem "hashdiff"
11
15
  gem "pry", "~> 0.9.12.6"
12
16
  gem "pry-byebug", "<= 1.3.2"
13
- gem "pry-rescue", "~> 1.4.1", github: "ConradIrwin/pry-rescue", branch: :master
17
+ gem "pry-rescue", "~> 1.4.2"
14
18
  gem "pry-stack_explorer", "~> 0.4.9.1"
15
19
  gem "pry-syntax-hacks", "~> 0.0.6"
16
20
  end
17
21
  end
22
+
23
+ group :test do
24
+ gem "codeclimate-test-reporter", require: nil
25
+ end
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ require "bundler/gem_tasks"
1
2
  require "rspec/core/rake_task"
2
3
 
3
4
  RSpec::Core::RakeTask.new(:spec)
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "metacrunch"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ require "pry"
11
+ Pry.start
@@ -0,0 +1,21 @@
1
+ require_relative "../reader"
2
+
3
+ # This fetcher does not do anything besides returning the filename withouzt the
4
+ # file:// protocol prefix. It only exists to keep the implementation consistent.
5
+ class Metacrunch::File::Reader::FileSystemFetcher
6
+ include Enumerable
7
+
8
+ RECOGNIZED_PROTOCOL_REGEX = /\Afile:\/\//
9
+
10
+ def self.accepts?(url)
11
+ !!url[RECOGNIZED_PROTOCOL_REGEX]
12
+ end
13
+
14
+ def initialize(url, options = {})
15
+ @url = url
16
+ end
17
+
18
+ def each
19
+ yield @url.sub(RECOGNIZED_PROTOCOL_REGEX, "")
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ require_relative "../reader"
2
+
3
+ class Metacrunch::File::Reader::PlainFileReader
4
+ include Enumerable
5
+
6
+ def self.accepts?(filename)
7
+ true
8
+ end
9
+
10
+ def initialize(filename)
11
+ @filename = filename
12
+ end
13
+
14
+ def each
15
+ return enum_for(__method__) unless block_given?
16
+
17
+ io =
18
+ if @filename.end_with?("gz") # catches tgz and tar.gz
19
+ Zlib::GzipReader.open(@filename)
20
+ else
21
+ File.open(@filename)
22
+ end
23
+
24
+ yield Metacrunch::File.new({
25
+ content: io.read,
26
+ entry_name: File.basename(@filename),
27
+ file_name: @filename,
28
+ is_directory: false
29
+ })
30
+
31
+ io.close
32
+ end
33
+ end
@@ -0,0 +1,56 @@
1
+ require "etc"
2
+ require "net/scp"
3
+ require "net/ssh"
4
+ require "securerandom"
5
+ require_relative "../reader"
6
+
7
+ class Metacrunch::File::Reader::ScpFetcher
8
+ include Enumerable
9
+
10
+ RECOGNIZED_PROTOCOL_REGEX = /\Ascp:\/\//
11
+ TILDE_REPLACEMENT = "/__TILDE__"
12
+
13
+ def self.accepts?(url)
14
+ !!url[RECOGNIZED_PROTOCOL_REGEX]
15
+ end
16
+
17
+ def initialize(url, options = {})
18
+ URI(url.sub("~", TILDE_REPLACEMENT)).try do |_uri|
19
+ @host = _uri.host
20
+ @password = options[:password] || _uri.password
21
+ @path = _uri.path.sub(TILDE_REPLACEMENT, "~")
22
+ @username = options[:username] || _uri.user || Etc.getlogin
23
+ end
24
+ end
25
+
26
+ def each
27
+ return enum_for(__method__) unless block_given?
28
+
29
+ begin
30
+ begin
31
+ Dir.mkdir temporary_directory = File.join(Dir.tmpdir, SecureRandom.hex)
32
+ rescue Errno::EEXIST
33
+ retry
34
+ end
35
+
36
+ remote_filenames.each do |_remote_filename|
37
+ _local_filename = File.join(temporary_directory, File.basename(_remote_filename))
38
+ Net::SCP.download!(@host, @username, _remote_filename, _local_filename, ssh: { password: @password })
39
+ yield _local_filename
40
+ File.delete(_local_filename)
41
+ end
42
+ ensure
43
+ FileUtils.remove_dir(temporary_directory)
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def remote_filenames
50
+ @remote_filenames ||= [].tap do |_remote_filenames|
51
+ Net::SSH.start(@host, @username, password: @password) do |_ssh|
52
+ _remote_filenames.concat _ssh.exec!("ruby -e \"puts Dir.glob(File.expand_path('#{@path}'))\"").try(:split, "\n") || []
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,37 @@
1
+ require "rubygems/package"
2
+ require_relative "../reader"
3
+
4
+ class Metacrunch::File::Reader::TarFileReader
5
+ include Enumerable
6
+
7
+ def self.accepts?(filename)
8
+ !!filename[/\.tar\Z|\.tar\.gz\Z|\.tgz\Z/]
9
+ end
10
+
11
+ def initialize(filename)
12
+ @filename = filename
13
+ end
14
+
15
+ def each
16
+ return enum_for(__method__) unless block_given?
17
+
18
+ io =
19
+ if @filename.end_with?("gz") # catches tgz and tar.gz
20
+ Zlib::GzipReader.open(@filename)
21
+ else
22
+ File.open(@filename)
23
+ end
24
+
25
+ Gem::Package::TarReader.new(io).each do |_tar_entry|
26
+ unless _tar_entry.directory?
27
+ yield Metacrunch::File.new({
28
+ content: _tar_entry.read,
29
+ entry_name: _tar_entry.full_name,
30
+ file_name: @filename
31
+ })
32
+ end
33
+ end
34
+
35
+ io.close
36
+ end
37
+ end
@@ -0,0 +1,30 @@
1
+ require "zip"
2
+ require_relative "../reader"
3
+
4
+ class Metacrunch::File::Reader::ZipFileReader
5
+ include Enumerable
6
+
7
+ def self.accepts?(filename)
8
+ !!filename[/\.zip\Z/]
9
+ end
10
+
11
+ def initialize(filename)
12
+ @filename = filename
13
+ end
14
+
15
+ def each
16
+ return enum_for(__method__) unless block_given?
17
+
18
+ Zip::File.open(@filename) do |_zip_file|
19
+ _zip_file.each do |_zip_entry|
20
+ unless _zip_entry.directory?
21
+ yield Metacrunch::File.new({
22
+ content: _zip_entry.get_input_stream.read,
23
+ entry_name: _zip_entry.name,
24
+ file_name: @filename
25
+ })
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,72 @@
1
+ require_relative "../file"
2
+ require_relative "../processor"
3
+
4
+ class Metacrunch::File::Reader < Metacrunch::Processor
5
+ require_relative "./reader/file_system_fetcher"
6
+ require_relative "./reader/plain_file_reader"
7
+ require_relative "./reader/scp_fetcher"
8
+ require_relative "./reader/tar_file_reader"
9
+ require_relative "./reader/zip_file_reader"
10
+
11
+ include Enumerable
12
+
13
+ def initialize(options = {})
14
+ @bulk_size = options[:bulk_size].try(:to_i) || 1
15
+ @urls = [
16
+ options[:filename], options[:filenames],
17
+ options[:url], options[:urls]
18
+ ]
19
+ .flatten
20
+ .compact
21
+ .map do |_filename_or_url|
22
+ if (_url = _filename_or_url)[/\A\w+:\/\//]
23
+ _url
24
+ else
25
+ Dir.glob(File.expand_path(_filename_or_url)).map do |_filename|
26
+ "file://#{_filename}"
27
+ end
28
+ end
29
+ end
30
+ .flatten # because there might be arrays again because of Dir.glob
31
+
32
+ @force_content_encoding = options[:force_content_encoding]
33
+ @password = options[:password]
34
+ @username = options[:username]
35
+ end
36
+
37
+ def call(items = [], pipeline = nil)
38
+ @chunks_of_entries_enumerator ||= each_slice(@bulk_size) # instance method from Enumerable
39
+
40
+ begin
41
+ items.concat(@chunks_of_entries_enumerator.next)
42
+ rescue StopIteration
43
+ pipeline.try(:terminate!)
44
+ end
45
+ end
46
+
47
+ def each
48
+ return enum_for(__method__) unless block_given?
49
+
50
+ @urls.each do |_url|
51
+ [FileSystemFetcher, ScpFetcher].find do |_fetcher|
52
+ _fetcher.accepts?(_url)
53
+ end
54
+ .try do |_appropriate_fetcher|
55
+ _appropriate_fetcher.new(_url, username: @username, password: @password).each do |_filename|
56
+ [TarFileReader, ZipFileReader, PlainFileReader].find do |_reader| # PlainFileReader as last will read any file
57
+ _reader.accepts?(_filename)
58
+ end
59
+ .try do |_appropriate_reader|
60
+ _appropriate_reader.new(_filename).each do |_file|
61
+ if @force_content_encoding.present?
62
+ _file.content.try(:force_encoding, @force_content_encoding)
63
+ end
64
+
65
+ yield _file
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,23 @@
1
+ require_relative "../metacrunch"
2
+
3
+ class Metacrunch::File
4
+ require_relative "./file/reader"
5
+
6
+ attr_accessor :content
7
+ attr_accessor :entry_name # equals file_name for plain files
8
+ attr_accessor :file_name
9
+
10
+ def initialize(options = {})
11
+ @content = options[:content]
12
+ @entry_name = options[:entry_name]
13
+ @file_name = options[:file_name]
14
+ end
15
+
16
+ def to_h
17
+ {
18
+ content: @content,
19
+ entry_name: @entry_name,
20
+ file_name: @file_name
21
+ }
22
+ end
23
+ end
@@ -31,14 +31,14 @@ module Metacrunch
31
31
  end
32
32
 
33
33
  def read_regular_file(filename, &block)
34
- if File.file?(filename)
35
- io = is_gzip_file?(filename) ? Zlib::GzipReader.open(filename) : File.open(filename, "r")
34
+ if ::File.file?(filename)
35
+ io = is_gzip_file?(filename) ? Zlib::GzipReader.open(filename) : ::File.open(filename, "r")
36
36
  yield Entry.new(filename: filename, archive_filename: nil, contents: io.read)
37
37
  end
38
38
  end
39
39
 
40
40
  def read_archive(filename, &block)
41
- io = is_gzip_file?(filename) ? Zlib::GzipReader.open(filename) : File.open(filename, "r")
41
+ io = is_gzip_file?(filename) ? Zlib::GzipReader.open(filename) : ::File.open(filename, "r")
42
42
  tarReader = Gem::Package::TarReader.new(io)
43
43
 
44
44
  tarReader.each do |_tar_entry|
@@ -5,10 +5,10 @@ module Metacrunch
5
5
 
6
6
 
7
7
  def initialize(filename, override: false, compress: nil)
8
- @path = File.expand_path(filename)
8
+ @path = ::File.expand_path(filename)
9
9
  @compressed = (compress ||= @path.ends_with?(".gz"))
10
10
 
11
- if File.exist?(@path) && !override
11
+ if ::File.exist?(@path) && !override
12
12
  raise FileExistError, "File #{@path} already exists. Set override = true to override the existing file."
13
13
  end
14
14
  end
@@ -33,7 +33,7 @@ module Metacrunch
33
33
  private
34
34
 
35
35
  def io
36
- @io ||= (@compressed == true) ? Zlib::GzipWriter.open(@path) : File.open(@path, "w")
36
+ @io ||= (@compressed == true) ? Zlib::GzipWriter.open(@path) : ::File.open(@path, "w")
37
37
  end
38
38
 
39
39
  end
@@ -0,0 +1,51 @@
1
+ require_relative "../metacrunch"
2
+
3
+ module Metacrunch::Hash
4
+ def self.add(object, *args)
5
+ if args.length < 2
6
+ raise ArgumentError
7
+ else
8
+ return args.last if args.last.nil? || (args.last.respond_to?(:empty?) && args.last.empty?)
9
+
10
+ if args.length == 2
11
+ args_first_is_a_string = args.first.is_a?(String) # memoize
12
+
13
+ if args_first_is_a_string && args.first.include?("/")
14
+ add(object, *args.first.split("/"), args.last)
15
+ else
16
+ if args_first_is_a_string && args.first.start_with?(":")
17
+ _add(object, args.first[1..-1].to_sym, args.last)
18
+ else
19
+ _add(object, args.first, args.last)
20
+ end
21
+ end
22
+ else
23
+ nested_hash = args[0..-3].inject(object) do |_memo, _key|
24
+ if _key.is_a?(String) && _key.start_with?(":")
25
+ _key = _key[1..-1].to_sym
26
+ end
27
+
28
+ _memo[_key] ||= object.class.new
29
+ end
30
+
31
+ _add(nested_hash, args[-2], args[-1])
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def self._add(hash, key, value)
39
+ #if value.is_a?(FalseClass) || (value.respond_to?(:empty?) ? !value.empty? : !!value) # like ActiveSupport implements blank?/present?
40
+ if hash[key].nil?
41
+ hash[key] = value.is_a?(Array) && value.length == 1 ? value.first : value
42
+ elsif hash[key].is_a?(Array)
43
+ (hash[key] << value).flatten!(1)
44
+ else
45
+ (hash[key] = [hash[key], value]).flatten!(1)
46
+ end
47
+ #end
48
+
49
+ hash[key]
50
+ end
51
+ end
@@ -0,0 +1,10 @@
1
+ require_relative "../metacrunch"
2
+
3
+ class Metacrunch::Processor
4
+ def initialize(options = {})
5
+ @options ||= options
6
+ end
7
+
8
+ def call(items = [], pipeline = nil)
9
+ end
10
+ end
@@ -19,7 +19,13 @@ module Metacrunch
19
19
  # Adds a field
20
20
  #
21
21
  def add(field_name, value)
22
- add_field(Field.new(field_name, value))
22
+ if value.is_a?(Array)
23
+ value.each do |_value|
24
+ add_field(Field.new(field_name, _value))
25
+ end
26
+ else
27
+ add_field(Field.new(field_name, value))
28
+ end
23
29
  end
24
30
 
25
31
  # ------------------------------------------------------------------------------
@@ -27,12 +33,17 @@ module Metacrunch
27
33
  # ------------------------------------------------------------------------------
28
34
 
29
35
  #
30
- # Return all fields.
36
+ # Return all fields. A name can be provided to filter fields by name.
31
37
  #
38
+ # @param [String, nil] name
32
39
  # @return [Array<Metacrunch::SNR::Section::Field>]
33
40
  #
34
- def fields
35
- @fields
41
+ def fields(name = nil)
42
+ if name
43
+ @fields.select{|field| field.name == name}
44
+ else
45
+ @fields
46
+ end
36
47
  end
37
48
 
38
49
  #
@@ -33,6 +33,22 @@ module Metacrunch
33
33
  add_section(section)
34
34
  end
35
35
 
36
+ #
37
+ # Returns field values for a given path.
38
+ #
39
+ # @param [String] path A path to the fields seperated by /. E.g. section/field
40
+ # @return [Array<*>]
41
+ #
42
+ def values(path)
43
+ section_name, field_name = path.split("/")
44
+ section = self.section(section_name)
45
+ if section && field_name
46
+ section.fields(field_name).map{|field| field.value}
47
+ else
48
+ []
49
+ end
50
+ end
51
+
36
52
  # ------------------------------------------------------------------------------
37
53
  # Sections
38
54
  # ------------------------------------------------------------------------------
@@ -0,0 +1,45 @@
1
+ require_relative "../transformation"
2
+
3
+ class Metacrunch::Transformator::Transformation::Step
4
+ attr_accessor :transformation
5
+
6
+ def initialize(transformation = nil, options = {})
7
+ if transformation.is_a?(Hash)
8
+ options = transformation
9
+ transformation = nil
10
+ end
11
+
12
+ if transformation
13
+ @transformation = transformation
14
+ else
15
+ @transformation = Struct.new(:source, :target).new.tap do |_struct|
16
+ _struct.source = options[:source]
17
+ _struct.target = options[:target]
18
+ end
19
+ end
20
+ end
21
+
22
+ def call
23
+ end
24
+
25
+ #
26
+ # Each step has transparent access to all methods of it's transformation
27
+ #
28
+ def method_missing(method_name, *args, &block)
29
+ if @transformation.respond_to?(method_name)
30
+ @transformation.send(method_name, *args, &block)
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ def respond_to_missing?(method_name, include_private = false)
37
+ @transformation.respond_to?(method_name) || super
38
+ end
39
+
40
+ # avoid method_missing penalty for the most used transformation methods
41
+ def source; @transformation.source; end
42
+ def source=(value); @transformation.source=(value); end
43
+ def target; @transformation.target; end
44
+ def target=(value); @transformation.target=(value); end
45
+ end
@@ -0,0 +1,48 @@
1
+ require_relative "../transformator"
2
+
3
+ class Metacrunch::Transformator::Transformation
4
+ require_relative "./transformation/step"
5
+
6
+ attr_accessor :source
7
+ attr_accessor :target
8
+
9
+ class << self
10
+ def steps(value = nil)
11
+ unless value
12
+ @steps
13
+ else
14
+ @steps = value
15
+ end
16
+ end
17
+ alias_method :sequence, :steps
18
+ end
19
+
20
+ def self.call(*args)
21
+ new.call(*args)
22
+ end
23
+
24
+ # since a transformation can have many steps, writing a "require" for each is tedious
25
+ def self.require_directory(directory)
26
+ Dir.glob("#{File.expand_path(directory)}/*.rb").each do |_filename|
27
+ require _filename
28
+ end
29
+ end
30
+
31
+ def initialize
32
+ # steps are instanced once, which means that instance variables retain
33
+ @steps = self.class.steps.flatten.map do |_step|
34
+ _step.is_a?(Class) ? _step.new(self) : _step
35
+ end
36
+ end
37
+
38
+ def call(source, options = {})
39
+ @source = source
40
+ @target = options[:target]
41
+
42
+ @steps.each do |_step|
43
+ _step.is_a?(Proc) ? instance_exec(&_step) : _step.call
44
+ end
45
+
46
+ return @target
47
+ end
48
+ end
@@ -0,0 +1,5 @@
1
+ require_relative "../metacrunch"
2
+
3
+ module Metacrunch::Transformator
4
+ require_relative "./transformator/transformation"
5
+ end
@@ -4,6 +4,7 @@ module Metacrunch
4
4
  require_relative "./transformer/helper"
5
5
 
6
6
  attr_accessor :source, :target, :options
7
+ attr_reader :step
7
8
 
8
9
 
9
10
  def initialize(source:nil, target:nil, options: {})
@@ -14,10 +15,13 @@ module Metacrunch
14
15
 
15
16
  def transform(step_class = nil, &block)
16
17
  if block_given?
17
- Step.new(self).instance_eval(&block) # TODO: Benchmark this
18
+ @step = Step.new(self)
19
+ @step.instance_eval(&block)
18
20
  else
19
21
  raise ArgumentError, "You need to provide a STEP or a block" if step_class.nil?
20
- step_class.new(self).perform
22
+ clazz = step_class.is_a?(Class) ? step_class : step_class.to_s.constantize
23
+ @step = clazz.new(self)
24
+ @step.perform
21
25
  end
22
26
  end
23
27
 
@@ -1,3 +1,3 @@
1
1
  module Metacrunch
2
- VERSION = "2.1.1"
2
+ VERSION = "2.2.0"
3
3
  end
data/lib/metacrunch.rb CHANGED
@@ -7,21 +7,21 @@ require "rubygems/package"
7
7
  require "thor"
8
8
  require "ox"
9
9
 
10
- begin
11
- require "pry"
12
- rescue LoadError ; end
13
-
14
10
  module Metacrunch
15
11
  require_relative "./metacrunch/version"
16
12
  require_relative "./metacrunch/cli"
17
13
  require_relative "./metacrunch/command"
14
+ require_relative "./metacrunch/file"
18
15
  require_relative "./metacrunch/file_reader"
19
16
  require_relative "./metacrunch/file_reader_entry"
20
17
  require_relative "./metacrunch/file_writer"
21
- require_relative "./metacrunch/tar_writer"
18
+ require_relative "./metacrunch/hash"
19
+ require_relative "./metacrunch/parallel"
20
+ require_relative "./metacrunch/processor"
22
21
  require_relative "./metacrunch/snr"
22
+ require_relative "./metacrunch/tar_writer"
23
+ require_relative "./metacrunch/transformator"
23
24
  require_relative "./metacrunch/transformer"
24
- require_relative "./metacrunch/parallel"
25
25
 
26
26
  def self.load_plugins
27
27
  Gem.find_latest_files("metacrunch_plugin.rb").each do |path|
data/metacrunch.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
9
9
  s.licenses = ["MIT"]
10
10
 
11
11
  s.files = `git ls-files`.split($\)
12
- s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
12
+ s.executables = s.files.grep(%r{^bin/metacrunch}) { |f| File.basename(f) }
13
13
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
14
14
  s.name = "metacrunch"
15
15
  s.require_paths = ["lib"]
@@ -21,7 +21,11 @@ Gem::Specification.new do |s|
21
21
 
22
22
  s.add_dependency "activesupport", "~> 4.2", ">= 4.2.0"
23
23
  s.add_dependency "builder", "~> 3.2", ">= 3.2.2"
24
+ s.add_dependency "highline", "~> 1.7"
25
+ s.add_dependency "net-scp", "~> 1.2"
26
+ s.add_dependency "net-ssh", "~> 2.9"
24
27
  s.add_dependency "parallel", "~> 1.6", ">= 1.6.0"
28
+ s.add_dependency "rubyzip", ">= 1.0.0"
25
29
  s.add_dependency "thor", "~> 0.19"
26
30
  s.add_dependency "ox", "~> 2.1"
27
31
  end
@@ -0,0 +1 @@
1
+ Some text
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,17 @@
1
+ pipeline do
2
+ reader "Metacrunch::Archive::Reader", {
3
+ bulk_size: 2,
4
+ filenames: "foo.tar.gz"
5
+ }
6
+
7
+ processor -> (items, pipeline) {
8
+ items.map! do |_item|
9
+
10
+ end
11
+ }
12
+ #processor "Metacrunch::Sqlite::Writer", {
13
+ # database: "mab_xml.db"
14
+ #}
15
+
16
+ processor "Metacrunch::Debugger"
17
+ end
@@ -4,8 +4,8 @@ describe Metacrunch::FileWriter do
4
4
  let(:compressed_file) { "/tmp/metacrunch_spec_regular_file.txt.gz" }
5
5
 
6
6
  before do
7
- File.unlink(regular_file) if File.exist?(regular_file)
8
- File.unlink(compressed_file) if File.exist?(compressed_file)
7
+ ::File.unlink(regular_file) if ::File.exist?(regular_file)
8
+ ::File.unlink(compressed_file) if ::File.exist?(compressed_file)
9
9
  end
10
10
 
11
11
  it "can write a file" do
@@ -32,7 +32,7 @@ describe Metacrunch::FileWriter do
32
32
  end
33
33
 
34
34
  it "can override existing file" do
35
- File.write(regular_file, "FOO")
35
+ ::File.write(regular_file, "FOO")
36
36
 
37
37
  expect {
38
38
  Metacrunch::FileWriter.new(regular_file)
@@ -0,0 +1,11 @@
1
+ describe Metacrunch::File::Reader::PlainFileReader do
2
+ describe ".accepts?" do
3
+ it "accepts all filenames" do
4
+ expect(described_class.accepts?("foo")).to equal(true)
5
+ expect(described_class.accepts?("foo.gz")).to equal(true)
6
+ expect(described_class.accepts?("foo.tar.gz")).to equal(true)
7
+ expect(described_class.accepts?("foo.xml")).to equal(true)
8
+ expect(described_class.accepts?("foo.zip")).to equal(true)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ describe Metacrunch::File::Reader::TarFileReader do
2
+ describe ".accepts?" do
3
+ it "accepts filenames this reader can process" do
4
+ expect(described_class.accepts?("foo.tar")).to equal(true)
5
+ expect(described_class.accepts?("foo.tar.gz")).to equal(true)
6
+ expect(described_class.accepts?("foo.tgz")).to equal(true)
7
+ end
8
+
9
+ it "does not accept filenames this reader cannot process" do
10
+ expect(described_class.accepts?("plain_file")).to equal(false)
11
+ expect(described_class.accepts?("plain_file.gz")).to equal(false)
12
+ expect(described_class.accepts?("tatar.gz")).to equal(false)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ describe Metacrunch::File::Reader::ZipFileReader do
2
+ describe ".accepts?" do
3
+ it "accepts filenames this reader can process" do
4
+ expect(described_class.accepts?("foo.zip")).to equal(true)
5
+ end
6
+
7
+ it "does not accept filenames this reader cannot process" do
8
+ expect(described_class.accepts?("plain_file")).to equal(false)
9
+ expect(described_class.accepts?("plain.zip.file")).to equal(false)
10
+ expect(described_class.accepts?("foozip")).to equal(false)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ describe Metacrunch::File::Reader do
2
+ def pipeline_factory
3
+ Class.new do
4
+ def terminate!; @terminated = true; end
5
+ def terminated?; !!@terminated; end
6
+ end
7
+ .new
8
+ end
9
+
10
+ def reader_factory(filename)
11
+ described_class.new({
12
+ bulk_size: 10,
13
+ filenames: [filename],
14
+ force_content_encoding: "utf-8"
15
+ })
16
+ end
17
+
18
+ # filenames
19
+ let(:plain_file_name) { File.join(asset_dir, "file/some_file") }
20
+ let(:plain_gz_file_name) { File.join(asset_dir, "file/some_file.gz") }
21
+ let(:tar_file_name) { File.join(asset_dir, "file/tar_archive.tar") }
22
+ let(:tar_gz_file_name) { File.join(asset_dir, "file/tar_gz_archive.tar.gz") }
23
+ let(:tgz_file_name) { File.join(asset_dir, "file/tgz_archive.tgz") }
24
+ let(:zip_file_name) { File.join(asset_dir, "file/zip_archive.zip") }
25
+
26
+ # readers
27
+ let(:plain_file_reader) { reader_factory(plain_file_name) }
28
+ let(:plain_gz_file_reader) { reader_factory(plain_gz_file_name) }
29
+ let(:tar_reader) { reader_factory(tar_file_name) }
30
+ let(:tar_gz_reader) { reader_factory(tar_gz_file_name) }
31
+ let(:tgz_reader) { reader_factory(tgz_file_name) }
32
+ let(:zip_reader) { reader_factory(zip_file_name) }
33
+
34
+ describe "#call" do
35
+ it "fills items with #{_file_type = Metacrunch::File} objects" do
36
+ [plain_file_reader, plain_gz_file_reader, tar_reader, tar_gz_reader,tgz_reader, zip_reader].each do |_reader|
37
+ pipeline = pipeline_factory
38
+
39
+ _reader.call(items = [], pipeline)
40
+ expect(items.length).not_to eq(0)
41
+ expect(items).to all(be_a(_file_type))
42
+
43
+ _reader.call(items, pipeline) until pipeline.terminated?
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,15 @@
1
+ describe Metacrunch::File do
2
+ describe "#to_h" do
3
+ let(:file) do
4
+ described_class.new({
5
+ content: "content",
6
+ entry_name: "entry_name",
7
+ file_name: "file_name"
8
+ })
9
+ end
10
+
11
+ it "returns a hash representation of the file object" do
12
+ expect(file.to_h).to be_a(Hash)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,93 @@
1
+ describe Metacrunch::Hash do
2
+ describe ".add" do
3
+ it "adds the given key/value pair" do
4
+ described_class.add(hash = {}, :key, "value")
5
+ expect(hash[:key]).to eq("value")
6
+ end
7
+
8
+ it "respects the type of the key" do
9
+ hash = {}
10
+
11
+ described_class.add(hash, :symbol_key1, "value")
12
+ described_class.add(hash, ":symbol_key2", "value")
13
+ described_class.add(hash, "string_key", "value")
14
+
15
+ expect(hash[:symbol_key1]).to eq("value")
16
+ expect(hash[:symbol_key2]).to eq("value")
17
+ expect(hash["string_key"]).to eq("value")
18
+ end
19
+
20
+ context "if wrong arguments are given" do
21
+ it "raises an #{exception_type = ArgumentError}" do
22
+ expect { described_class.add(hash) }.to raise_error(exception_type)
23
+ end
24
+ end
25
+
26
+ context "if key includes forward dashes" do
27
+ it "splits the key by forward dashes" do
28
+ described_class.add(hash = {}, "key/subkey", "value")
29
+ expect(hash["key"]["subkey"]).to eq("value")
30
+ end
31
+
32
+ it "respects the type of the keys" do
33
+ described_class.add(hash = {}, "key/:subkey/sub_sub_key", "value")
34
+ expect(hash["key"][:subkey]["sub_sub_key"]).to eq("value")
35
+ end
36
+ end
37
+
38
+ context "if multiple keys are given" do
39
+ it "dives into the object and adds the key/value pair" do
40
+ hash = { "key" => { "subkey" => "former_value" } }
41
+ described_class.add(hash, "key", "subkey", "value")
42
+ expect(hash["key"]["subkey"]).to eq(["former_value", "value"])
43
+ end
44
+ end
45
+
46
+ context "if there already exists a value for the given key" do
47
+ context "if the value is an array" do
48
+ it "adds the value to the array" do
49
+ hash = { "key" => ["former_value1", "former_value2"] }
50
+ described_class.add(hash, "key", "value")
51
+ expect(hash["key"]).to eq(["former_value1", "former_value2", "value"])
52
+ end
53
+
54
+ it "returns aggregated value" do
55
+ hash = { "key" => ["former_value1", "former_value2"] }
56
+ expect(described_class.add(hash, "key", "value")).to eq(["former_value1", "former_value2", "value"])
57
+ end
58
+ end
59
+
60
+ it "makes the existing value an array and adds the given value" do
61
+ hash = { "key" => "former_value" }
62
+ described_class.add(hash, "key", "value")
63
+ expect(hash["key"]).to eq(["former_value", "value"])
64
+ end
65
+
66
+ it "returns aggregated value" do
67
+ hash = { "key" => "former_value" }
68
+ expect(described_class.add(hash, "key", "value")).to eq(["former_value", "value"])
69
+ end
70
+ end
71
+
72
+ context "if the given value is nil" do
73
+ it "does not add anything" do
74
+ described_class.add(hash = {}, "key/subkey", nil)
75
+ expect(hash).to eq({})
76
+ end
77
+ end
78
+
79
+ context "if the given value is empty" do
80
+ it "does not add anything" do
81
+ hash = {}
82
+ described_class.add(hash, "key/subkey1", "")
83
+ described_class.add(hash, "key/subkey2", [])
84
+ described_class.add(hash, "key/subkey3", Class.new { def empty?; true; end }.new)
85
+ expect(hash).to eq({})
86
+ end
87
+ end
88
+
89
+ it "returns the value" do
90
+ expect(described_class.add({}, "key", "value")).to eq("value")
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,28 @@
1
+ describe Metacrunch::Transformator::Transformation do
2
+ class SomeStep < described_class::Step
3
+ def call
4
+ self.target = { muff: 1 }
5
+ end
6
+ end
7
+
8
+ class SomeTransformation < described_class
9
+ sequence [
10
+ SomeStep
11
+ ]
12
+ end
13
+
14
+ it "foo" do
15
+ result = SomeTransformation.call({
16
+ my: {
17
+ awesome: {
18
+ source: {
19
+ title: "foo",
20
+ description: "bar"
21
+ }
22
+ }
23
+ }
24
+ })
25
+
26
+ result
27
+ end
28
+ end
data/spec/snr_spec.rb CHANGED
@@ -59,7 +59,7 @@ describe Metacrunch::SNR do
59
59
  describe ".to_xml" do
60
60
  before {
61
61
  snr.add("section1", "title", "Foo Bar")
62
- snr.add("section1", "artists", ["Sievers, Michael", "Sprotte, René"])
62
+ snr.add("section1", "artist", ["Sievers, Michael", "Sprotte, René"])
63
63
  snr.add("section2", "link", {label: "Click here", url: "http://example.com"})
64
64
  }
65
65
 
@@ -67,7 +67,7 @@ describe Metacrunch::SNR do
67
67
 
68
68
  it { is_expected.not_to be_nil }
69
69
  it { is_expected.not_to be_empty }
70
- it { is_expected.to eq("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<snr>\n <section1>\n <title>Foo Bar</title>\n <artists type=\"array\">\n <artist>Sievers, Michael</artist>\n <artist>Sprotte, René</artist>\n </artists>\n </section1>\n <section2>\n <link>\n <label>Click here</label>\n <url>http://example.com</url>\n </link>\n </section2>\n</snr>\n") }
70
+ it { is_expected.to eq("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<snr>\n <section1>\n <title>Foo Bar</title>\n <artist>Sievers, Michael</artist>\n <artist>Sprotte, René</artist>\n </section1>\n <section2>\n <link>\n <label>Click here</label>\n <url>http://example.com</url>\n </link>\n </section2>\n</snr>\n") }
71
71
  end
72
72
 
73
73
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,29 @@
1
+ if ENV["CODECLIMATE_REPO_TOKEN"]
2
+ require "codeclimate-test-reporter"
3
+ CodeClimate::TestReporter.start
4
+ else
5
+ require "simplecov"
6
+ SimpleCov.start
7
+ end
8
+
1
9
  require "metacrunch"
2
10
 
11
+ begin
12
+ require "pry"
13
+ require "yaml"
14
+ rescue LoadError
15
+ end
16
+
3
17
  RSpec.configure do |config|
18
+ # begin --- rspec 3.1 generator
19
+ config.expect_with :rspec do |expectations|
20
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
21
+ end
22
+
23
+ config.mock_with :rspec do |mocks|
24
+ mocks.verify_partial_doubles = true
25
+ end
26
+ # end --- rspec 3.1 generator
4
27
  end
5
28
 
6
29
  # Helper to provide RSpec.root
@@ -10,3 +33,11 @@ module ::RSpec
10
33
  @spec_root ||= Pathname.new(__dir__)
11
34
  end
12
35
  end
36
+
37
+ def asset_dir
38
+ File.expand_path(File.join(File.dirname(__FILE__), "assets"))
39
+ end
40
+
41
+ def read_asset(path_to_file)
42
+ File.read(File.expand_path(File.join(asset_dir, path_to_file)))
43
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metacrunch
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - René Sprotte
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2015-06-15 00:00:00.000000000 Z
13
+ date: 2015-09-25 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -52,6 +52,48 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: 3.2.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: highline
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.7'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: net-scp
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.2'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: net-ssh
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.9'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.9'
55
97
  - !ruby/object:Gem::Dependency
56
98
  name: parallel
57
99
  requirement: !ruby/object:Gem::Requirement
@@ -72,6 +114,20 @@ dependencies:
72
114
  - - ">="
73
115
  - !ruby/object:Gem::Version
74
116
  version: 1.6.0
117
+ - !ruby/object:Gem::Dependency
118
+ name: rubyzip
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: 1.0.0
124
+ type: :runtime
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: 1.0.0
75
131
  - !ruby/object:Gem::Dependency
76
132
  name: thor
77
133
  requirement: !ruby/object:Gem::Requirement
@@ -114,6 +170,7 @@ files:
114
170
  - License.txt
115
171
  - Rakefile
116
172
  - Readme.md
173
+ - bin/console
117
174
  - bin/metacrunch
118
175
  - lib/metacrunch.rb
119
176
  - lib/metacrunch/cli.rb
@@ -122,14 +179,26 @@ files:
122
179
  - lib/metacrunch/cli/command_registry.rb
123
180
  - lib/metacrunch/cli/main.rb
124
181
  - lib/metacrunch/command.rb
182
+ - lib/metacrunch/file.rb
183
+ - lib/metacrunch/file/reader.rb
184
+ - lib/metacrunch/file/reader/file_system_fetcher.rb
185
+ - lib/metacrunch/file/reader/plain_file_reader.rb
186
+ - lib/metacrunch/file/reader/scp_fetcher.rb
187
+ - lib/metacrunch/file/reader/tar_file_reader.rb
188
+ - lib/metacrunch/file/reader/zip_file_reader.rb
125
189
  - lib/metacrunch/file_reader.rb
126
190
  - lib/metacrunch/file_reader_entry.rb
127
191
  - lib/metacrunch/file_writer.rb
192
+ - lib/metacrunch/hash.rb
128
193
  - lib/metacrunch/parallel.rb
194
+ - lib/metacrunch/processor.rb
129
195
  - lib/metacrunch/snr.rb
130
196
  - lib/metacrunch/snr/field.rb
131
197
  - lib/metacrunch/snr/section.rb
132
198
  - lib/metacrunch/tar_writer.rb
199
+ - lib/metacrunch/transformator.rb
200
+ - lib/metacrunch/transformator/transformation.rb
201
+ - lib/metacrunch/transformator/transformation/step.rb
133
202
  - lib/metacrunch/transformer.rb
134
203
  - lib/metacrunch/transformer/helper.rb
135
204
  - lib/metacrunch/transformer/step.rb
@@ -137,10 +206,24 @@ files:
137
206
  - metacrunch.gemspec
138
207
  - spec/assets/archive.tar
139
208
  - spec/assets/archive.tar.gz
209
+ - spec/assets/file/some_file
210
+ - spec/assets/file/some_file.gz
211
+ - spec/assets/file/tar_archive.tar
212
+ - spec/assets/file/tar_gz_archive.tar.gz
213
+ - spec/assets/file/tgz_archive.tgz
214
+ - spec/assets/file/zip_archive.zip
140
215
  - spec/assets/regular_file.txt
141
216
  - spec/assets/regular_file.txt.gz
217
+ - spec/assets/sql_lite_config.rb
142
218
  - spec/file_reader_spec.rb
143
219
  - spec/file_writer_spec.rb
220
+ - spec/metacrunch/file/reader/plain_file_reader_spec.rb
221
+ - spec/metacrunch/file/reader/tar_reader_spec.rb
222
+ - spec/metacrunch/file/reader/zip_reader_spec.rb
223
+ - spec/metacrunch/file/reader_spec.rb
224
+ - spec/metacrunch/file_spec.rb
225
+ - spec/metacrunch/hash_spec.rb
226
+ - spec/metacrunch/transformator/transformation_spec.rb
144
227
  - spec/snr_spec.rb
145
228
  - spec/spec_helper.rb
146
229
  - spec/tar_writer_spec.rb
@@ -164,17 +247,31 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
247
  version: '0'
165
248
  requirements: []
166
249
  rubyforge_project:
167
- rubygems_version: 2.4.6
250
+ rubygems_version: 2.4.8
168
251
  signing_key:
169
252
  specification_version: 4
170
253
  summary: Data processing toolkit for Ruby
171
254
  test_files:
172
255
  - spec/assets/archive.tar
173
256
  - spec/assets/archive.tar.gz
257
+ - spec/assets/file/some_file
258
+ - spec/assets/file/some_file.gz
259
+ - spec/assets/file/tar_archive.tar
260
+ - spec/assets/file/tar_gz_archive.tar.gz
261
+ - spec/assets/file/tgz_archive.tgz
262
+ - spec/assets/file/zip_archive.zip
174
263
  - spec/assets/regular_file.txt
175
264
  - spec/assets/regular_file.txt.gz
265
+ - spec/assets/sql_lite_config.rb
176
266
  - spec/file_reader_spec.rb
177
267
  - spec/file_writer_spec.rb
268
+ - spec/metacrunch/file/reader/plain_file_reader_spec.rb
269
+ - spec/metacrunch/file/reader/tar_reader_spec.rb
270
+ - spec/metacrunch/file/reader/zip_reader_spec.rb
271
+ - spec/metacrunch/file/reader_spec.rb
272
+ - spec/metacrunch/file_spec.rb
273
+ - spec/metacrunch/hash_spec.rb
274
+ - spec/metacrunch/transformator/transformation_spec.rb
178
275
  - spec/snr_spec.rb
179
276
  - spec/spec_helper.rb
180
277
  - spec/tar_writer_spec.rb