cure 0.1.1 → 0.1.2
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 +4 -4
- data/.rubocop.yml +3 -0
- data/Gemfile.lock +1 -1
- data/README.md +16 -6
- data/exe/cure +7 -3
- data/lib/cure/config.rb +17 -3
- data/lib/cure/coordinator.rb +72 -0
- data/lib/cure/export/exporter.rb +32 -7
- data/lib/cure/extract/builder.rb +27 -0
- data/lib/cure/extract/csv_lookup.rb +32 -0
- data/lib/cure/extract/extractor.rb +123 -0
- data/lib/cure/generator/base_generator.rb +51 -0
- data/lib/cure/generator/case_generator.rb +25 -0
- data/lib/cure/generator/character_generator.rb +35 -0
- data/lib/cure/generator/faker_generator.rb +25 -0
- data/lib/cure/generator/guid_generator.rb +16 -0
- data/lib/cure/generator/hex_generator.rb +16 -0
- data/lib/cure/generator/imports.rb +12 -0
- data/lib/cure/generator/number_generator.rb +16 -0
- data/lib/cure/generator/placeholder_generator.rb +20 -0
- data/lib/cure/generator/redact_generator.rb +16 -0
- data/lib/cure/generator/variable_generator.rb +20 -0
- data/lib/cure/helpers/file_helpers.rb +40 -0
- data/lib/cure/helpers/object_helpers.rb +29 -0
- data/lib/cure/log.rb +3 -3
- data/lib/cure/main.rb +40 -31
- data/lib/cure/strategy/append_strategy.rb +24 -0
- data/lib/cure/strategy/base_strategy.rb +123 -0
- data/lib/cure/strategy/end_with_strategy.rb +46 -0
- data/lib/cure/strategy/full_strategy.rb +24 -0
- data/lib/cure/strategy/imports.rb +10 -0
- data/lib/cure/strategy/match_strategy.rb +43 -0
- data/lib/cure/strategy/regex_strategy.rb +49 -0
- data/lib/cure/strategy/split_strategy.rb +53 -0
- data/lib/cure/strategy/start_with_strategy.rb +47 -0
- data/lib/cure/template/dispatch.rb +30 -0
- data/lib/cure/template/extraction.rb +38 -0
- data/lib/cure/template/template.rb +28 -0
- data/lib/cure/template/transformations.rb +26 -0
- data/lib/cure/transformation/candidate.rb +23 -9
- data/lib/cure/transformation/transform.rb +33 -41
- data/lib/cure/validators.rb +71 -0
- data/lib/cure/version.rb +1 -1
- data/lib/cure.rb +9 -4
- data/templates/aws_cur_template.json +130 -128
- data/templates/example_template.json +46 -30
- metadata +36 -9
- data/lib/cure/csv_helpers.rb +0 -6
- data/lib/cure/file_helpers.rb +0 -38
- data/lib/cure/generator/base.rb +0 -148
- data/lib/cure/object_helpers.rb +0 -27
- data/lib/cure/strategy/base.rb +0 -223
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 29e7c915346b45c1407208f6ba1810ece68004c8d7dd9ffb8eb2b5dcca903b35
         | 
| 4 | 
            +
              data.tar.gz: c356c649bc4a902fcb347e9e16f2299f4c8a7c0db576a7fc8242d080b575920d
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 5999a52c084656af0a6a18ba68884aec3de825be9f84f5c67cdf771031af414789aaebf24dfc9fed52f6a76b9a9495081f0f4e1c0f872927fbe34c271fa86e62
         | 
| 7 | 
            +
              data.tar.gz: 458962e9905380ef0e5832ef9e2ae2da1ede47f448992314bceba95c997c2af3bb4d6f2b92e1f23e906563c77ad1fbcd37d171d75732cf29c15b6b9b0e05e5bc
         | 
    
        data/.rubocop.yml
    CHANGED
    
    | @@ -26,6 +26,9 @@ Metrics/ClassLength: | |
| 26 26 | 
             
            Layout/SpaceAroundEqualsInParameterDefault:
         | 
| 27 27 | 
             
              EnforcedStyle: no_space
         | 
| 28 28 |  | 
| 29 | 
            +
            Layout/EmptyLinesAroundClassBody:
         | 
| 30 | 
            +
              Enabled: false
         | 
| 31 | 
            +
             | 
| 29 32 | 
             
            # We do not need to support Ruby 1.9, so this is good to use.
         | 
| 30 33 | 
             
            Style/SymbolArray:
         | 
| 31 34 | 
             
              Enabled: true
         | 
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -1,19 +1,29 @@ | |
| 1 1 | 
             
            # Cure
         | 
| 2 2 |  | 
| 3 3 | 
             
            
         | 
| 4 | 
            +
            [](https://badge.fury.io/rb/cure)
         | 
| 4 5 |  | 
| 5 | 
            -
            Cure is a simple tool to **remove/redact/anonymize** and **replace**  | 
| 6 | 
            -
            It has been written to anonymize private cloud billing data for use in public demo environments.
         | 
| 6 | 
            +
            Cure is a simple tool to **extract/clean/transform/remove/redact/anonymize** and **replace** information in a spreadsheet.
         | 
| 7 | 
            +
            It has been written to anonymize private cloud billing data for use in public demo environments.  Since then, it has grown to 
         | 
| 8 | 
            +
            additional processing capabilities that can take a CSV from junk to workable data.
         | 
| 7 9 |  | 
| 8 10 | 
             
            It has several key features:
         | 
| 9 | 
            -
            -  | 
| 10 | 
            -
            -  | 
| 11 | 
            -
            -  | 
| 12 | 
            -
             | 
| 11 | 
            +
            - Operate on your data to build what you need. 
         | 
| 12 | 
            +
              - Files are taken through an `Extract -> Build -> Transform -> Export` pipeline.
         | 
| 13 | 
            +
            - Extract parts of your file into named ranges to remove junk. 
         | 
| 14 | 
            +
            - Build (Add/Remove/Explode) columns - handy for files that may have JSON as a column value.
         | 
| 15 | 
            +
            - Transform values:
         | 
| 16 | 
            +
              - Define either full or regex match groups replacements.
         | 
| 17 | 
            +
              - Choose from many strategies to replace anonymous data - random number sequences, GUIDs, placeholders, multipliers amongst many others.
         | 
| 18 | 
            +
              - **Existing generated values are stored and recalled** so once a replacement is defined, it is kept around for other columns to use.
         | 
| 19 | 
            +
                - For example, once a replacement **Account Number** is generated, any further use of that number sequence is other columns will be used, keeping data real(ish) and functional in a relational sense.
         | 
| 20 | 
            +
            - Export into one (or many) files, in a selection of chosen formats (CSV at the moment, coming soon with JSON, Parquet).
         | 
| 13 21 |  | 
| 14 22 | 
             
            ## Use Cases
         | 
| 15 23 |  | 
| 16 24 | 
             
            - Strip out personal data from a CSV that may be used for public demo.
         | 
| 25 | 
            +
            - Extract specific parts of a CSV file and junk the rest.
         | 
| 26 | 
            +
            - Explode JSON values into individual columns per key.
         | 
| 17 27 |  | 
| 18 28 | 
             
            ## Usage
         | 
| 19 29 |  | 
    
        data/exe/cure
    CHANGED
    
    | @@ -1,6 +1,9 @@ | |
| 1 1 | 
             
            #!/usr/bin/env ruby
         | 
| 2 2 | 
             
            # frozen_string_literal: true
         | 
| 3 3 |  | 
| 4 | 
            +
            # rubocop:disable Style/MixinUsage
         | 
| 5 | 
            +
            # rubocop:disable Style/MethodCalledOnDoEndBlock
         | 
| 6 | 
            +
             | 
| 4 7 | 
             
            require "cure"
         | 
| 5 8 | 
             
            require "optparse"
         | 
| 6 9 |  | 
| @@ -34,9 +37,10 @@ OptionParser.new do |opts| | |
| 34 37 | 
             
            end.parse!
         | 
| 35 38 |  | 
| 36 39 | 
             
            log_info "Config loaded successfully, initialising environment ..."
         | 
| 37 | 
            -
            main = Cure::Main. | 
| 40 | 
            +
            main = Cure::Main.init_from_file(conf[:template_file_location], conf[:source_file_location], conf[:output_dir])
         | 
| 38 41 |  | 
| 39 42 | 
             
            log_info "... set up complete. Beginning process"
         | 
| 40 | 
            -
            main. | 
| 41 | 
            -
             | 
| 43 | 
            +
            main.run_export
         | 
| 42 44 |  | 
| 45 | 
            +
            # rubocop:enable Style/MixinUsage
         | 
| 46 | 
            +
            # rubocop:enable Style/MethodCalledOnDoEndBlock
         | 
    
        data/lib/cure/config.rb
    CHANGED
    
    | @@ -19,16 +19,30 @@ module Cure | |
| 19 19 | 
             
                  ConfigurationSource.instance.load_config(request_config)
         | 
| 20 20 | 
             
                end
         | 
| 21 21 |  | 
| 22 | 
            +
                # @param [String] source_file_location
         | 
| 23 | 
            +
                # @param [Cure::Template] template
         | 
| 24 | 
            +
                # @param [String] output_dir
         | 
| 22 25 | 
             
                # @return [Config]
         | 
| 23 26 | 
             
                def create_config(source_file_location, template, output_dir)
         | 
| 24 27 | 
             
                  Config.new(source_file_location, template, output_dir)
         | 
| 25 28 | 
             
                end
         | 
| 26 29 |  | 
| 30 | 
            +
                # If we are overloading here as a "data store" and "config store", we
         | 
| 31 | 
            +
                # could break out variables and placeholders into their own singleton.
         | 
| 32 | 
            +
                #
         | 
| 33 | 
            +
                # This should be a kind of instance cache, which loads once per run,
         | 
| 34 | 
            +
                # and junk can be jammed in there?
         | 
| 27 35 | 
             
                class Config
         | 
| 28 | 
            -
                  attr_accessor :source_file_location, : | 
| 36 | 
            +
                  attr_accessor :source_file_location, :output_dir
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  # @return [Cure::Template] template
         | 
| 39 | 
            +
                  attr_accessor :template
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  # @return [Hash] variables
         | 
| 42 | 
            +
                  attr_accessor :variables
         | 
| 29 43 |  | 
| 30 44 | 
             
                  # @param [String] source_file_location
         | 
| 31 | 
            -
                  # @param [ | 
| 45 | 
            +
                  # @param [Cure::Template] template
         | 
| 32 46 | 
             
                  # @param [String] output_dir
         | 
| 33 47 | 
             
                  def initialize(source_file_location, template, output_dir)
         | 
| 34 48 | 
             
                    @source_file_location = source_file_location
         | 
| @@ -37,7 +51,7 @@ module Cure | |
| 37 51 | 
             
                  end
         | 
| 38 52 |  | 
| 39 53 | 
             
                  def placeholders
         | 
| 40 | 
            -
                    @template | 
| 54 | 
            +
                    @template.transformations.placeholders || {}
         | 
| 41 55 | 
             
                  end
         | 
| 42 56 | 
             
                end
         | 
| 43 57 |  | 
| @@ -0,0 +1,72 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "cure/log"
         | 
| 4 | 
            +
            require "cure/config"
         | 
| 5 | 
            +
            require "cure/helpers/file_helpers"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            require "cure/extract/extractor"
         | 
| 8 | 
            +
            require "cure/transformation/transform"
         | 
| 9 | 
            +
            require "cure/export/exporter"
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            require "rcsv"
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            module Cure
         | 
| 14 | 
            +
              # Coordinates the entire process:
         | 
| 15 | 
            +
              # Extract -> Build -> Transform -> Export
         | 
| 16 | 
            +
              class Coordinator
         | 
| 17 | 
            +
                include Configuration
         | 
| 18 | 
            +
                include Log
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def process
         | 
| 21 | 
            +
                  # need to check config is init'd
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  extracted_csv = extract
         | 
| 24 | 
            +
                  built_csv = build(extracted_csv)
         | 
| 25 | 
            +
                  transformed_csv = transform(built_csv)
         | 
| 26 | 
            +
                  export(transformed_csv)
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                private
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                # @return [Cure::Extract::WrappedCSV]
         | 
| 32 | 
            +
                def extract
         | 
| 33 | 
            +
                  log_info "Beginning the extraction process..."
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  extractor = Extract::Extractor.new({})
         | 
| 36 | 
            +
                  result = extractor.extract_from_file(config.source_file_location)
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  log_debug "Setting extracted variables to global conf for access downstream"
         | 
| 39 | 
            +
                  config.variables = result.variables
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  log_info "...extraction complete"
         | 
| 42 | 
            +
                  result
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                # @param [Cure::Extract::WrappedCSV] wrapped_csv
         | 
| 46 | 
            +
                def build(wrapped_csv)
         | 
| 47 | 
            +
                  log_info "Beginning the building process..."
         | 
| 48 | 
            +
                  log_info "... building complete"
         | 
| 49 | 
            +
                  wrapped_csv
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                # @return [Hash<String,Cure::Transformation::TransformResult>]
         | 
| 53 | 
            +
                # @param [Cure::Extract::WrappedCSV] parsed_csv
         | 
| 54 | 
            +
                def transform(parsed_csv)
         | 
| 55 | 
            +
                  log_info "Beginning the transformation process..."
         | 
| 56 | 
            +
                  transformer = Cure::Transformation::Transform.new(config.template.transformations.candidates)
         | 
| 57 | 
            +
                  content = transformer.transform_content(parsed_csv)
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  log_info "...transform complete"
         | 
| 60 | 
            +
                  content
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                # @return [Hash<String,Cure::Transformation::TransformResult>]
         | 
| 64 | 
            +
                def export(transformed_result)
         | 
| 65 | 
            +
                  log_info "Beginning export process..."
         | 
| 66 | 
            +
                  Cure::Export::Exporter.export_result(transformed_result, config.output_dir)
         | 
| 67 | 
            +
                  log_info "... export complete."
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  transformed_result
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
              end
         | 
| 72 | 
            +
            end
         | 
    
        data/lib/cure/export/exporter.rb
    CHANGED
    
    | @@ -1,24 +1,49 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require "cure/log"
         | 
| 4 | 
            -
            require "cure/ | 
| 4 | 
            +
            require "cure/config"
         | 
| 5 | 
            +
            require "cure/helpers/file_helpers"
         | 
| 5 6 |  | 
| 6 7 | 
             
            module Cure
         | 
| 7 8 | 
             
              module Export
         | 
| 8 9 | 
             
                class Exporter
         | 
| 9 | 
            -
                  include  | 
| 10 | 
            +
                  include Helpers::FileHelpers
         | 
| 11 | 
            +
                  include Configuration
         | 
| 10 12 | 
             
                  include Log
         | 
| 11 13 |  | 
| 12 | 
            -
                   | 
| 13 | 
            -
             | 
| 14 | 
            +
                  # @param [Array<Cure::Transform::TransformResult>] result
         | 
| 15 | 
            +
                  def self.export_result(results, output_dir)
         | 
| 16 | 
            +
                    exporter = Exporter.new(output_dir)
         | 
| 17 | 
            +
                    exporter.export_results(results)
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  attr_reader :output_dir
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def initialize(output_dir)
         | 
| 23 | 
            +
                    @output_dir = output_dir
         | 
| 24 | 
            +
                  end
         | 
| 14 25 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 26 | 
            +
                  # @param [Array<Cure::Transform::TransformResult>] result
         | 
| 27 | 
            +
                  def export_results(result)
         | 
| 28 | 
            +
                    export_ranges = config.template.dispatch.named_ranges
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    export_ranges.each do |range|
         | 
| 31 | 
            +
                      named_range = range["named_range"]
         | 
| 32 | 
            +
                      unless result.has_key?(named_range)
         | 
| 33 | 
            +
                        raise "Missing named range - #{range} from candidates [#{result.keys.join(", ")}]"
         | 
| 34 | 
            +
                      end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                      data = result[named_range]
         | 
| 37 | 
            +
                      column_headers = data.column_headers.keys
         | 
| 38 | 
            +
                      export(@output_dir, range["file_name"], data.transformed_rows, column_headers)
         | 
| 39 | 
            +
                    end
         | 
| 17 40 | 
             
                  end
         | 
| 18 41 |  | 
| 19 42 | 
             
                  # @param [Array] rows
         | 
| 20 43 | 
             
                  # @param [Array] columns
         | 
| 21 44 | 
             
                  def export(output_dir, file_name, rows, columns)
         | 
| 45 | 
            +
                    file_name = "#{file_name}-#{Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S%-z")}"
         | 
| 46 | 
            +
             | 
| 22 47 | 
             
                    log_info("Exporting file to [#{output_dir}/#{file_name}] with #{rows.length} rows")
         | 
| 23 48 |  | 
| 24 49 | 
             
                    file_contents = []
         | 
| @@ -37,7 +62,7 @@ module Cure | |
| 37 62 | 
             
                  # @param [String] contents
         | 
| 38 63 | 
             
                  # @param [String] file_extension
         | 
| 39 64 | 
             
                  def write_to_file(file_path, file_name, file_extension, contents)
         | 
| 40 | 
            -
                    file_location = "#{file_path}/#{file_name | 
| 65 | 
            +
                    file_location = "#{file_path}/#{file_name}"
         | 
| 41 66 | 
             
                    clean_dir(file_path)
         | 
| 42 67 |  | 
| 43 68 | 
             
                    with_file(file_location, file_extension) do |file|
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "cure/extract/csv_lookup"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Cure
         | 
| 6 | 
            +
              module Extract
         | 
| 7 | 
            +
                class Builder
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  # @param [Hash] opts
         | 
| 10 | 
            +
                  attr_reader :opts
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  # @param [Hash] opts
         | 
| 13 | 
            +
                  def initialize(opts)
         | 
| 14 | 
            +
                    @opts = opts
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  # @param [Array<Array>] _sheet
         | 
| 18 | 
            +
                  # @param [Hash<String, Integer>] _column_headers
         | 
| 19 | 
            +
                  # @return [Array]
         | 
| 20 | 
            +
                  #
         | 
| 21 | 
            +
                  # This returns changed column headers and sheets
         | 
| 22 | 
            +
                  def handle(_sheet, _column_headers)
         | 
| 23 | 
            +
                    []
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Cure
         | 
| 4 | 
            +
              module Extract
         | 
| 5 | 
            +
                class CsvLookup
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  # @param [String] position - [Ex A1:B1, A1:B1,A2:B2]
         | 
| 8 | 
            +
                  # @return [Array] [column_start_idx, column_end_idx, row_start_idx, row_end_idx]
         | 
| 9 | 
            +
                  def self.array_position_lookup(position)
         | 
| 10 | 
            +
                    return [0, -1, 0, -1] if position.is_a?(Integer) && position == -1 # Whole sheet
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    start, finish, *_excess = position.split(":")
         | 
| 13 | 
            +
                    raise "Invalid format" unless start || finish
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    [
         | 
| 16 | 
            +
                      position_for_letter(start),
         | 
| 17 | 
            +
                      position_for_letter(finish),
         | 
| 18 | 
            +
                      position_for_digit(start),
         | 
| 19 | 
            +
                      position_for_digit(finish)
         | 
| 20 | 
            +
                    ]
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def self.position_for_letter(range)
         | 
| 24 | 
            +
                    range.upcase.scan(/[A-Z]+/).first.ord - 65 # A (65) - 65 = 0 idx
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def self.position_for_digit(range)
         | 
| 28 | 
            +
                    range.upcase.scan(/\d+/).first.to_i - 1
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| @@ -0,0 +1,123 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "cure/log"
         | 
| 4 | 
            +
            require "cure/config"
         | 
| 5 | 
            +
            require "cure/extract/csv_lookup"
         | 
| 6 | 
            +
            require "cure/helpers/file_helpers"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module Cure
         | 
| 9 | 
            +
              module Extract
         | 
| 10 | 
            +
                class Extractor
         | 
| 11 | 
            +
                  include Log
         | 
| 12 | 
            +
                  include Configuration
         | 
| 13 | 
            +
                  include Helpers::FileHelpers
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  # @param [Hash] opts
         | 
| 16 | 
            +
                  attr_reader :opts
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  # @param [Hash] opts
         | 
| 19 | 
            +
                  def initialize(opts)
         | 
| 20 | 
            +
                    @opts = opts
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  # @param [String] csv_file_location
         | 
| 24 | 
            +
                  # @return [WrappedCSV]
         | 
| 25 | 
            +
                  def extract_from_file(csv_file_location)
         | 
| 26 | 
            +
                    file_contents = read_file(csv_file_location)
         | 
| 27 | 
            +
                    extract_from_contents(file_contents)
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  # @param [String] file_contents
         | 
| 31 | 
            +
                  # @return [WrappedCSV]
         | 
| 32 | 
            +
                  def extract_from_contents(file_contents)
         | 
| 33 | 
            +
                    parsed_content = parse_csv(file_contents, header: :none)
         | 
| 34 | 
            +
                    log_info("Parsed CSV into #{parsed_content.content.length} sections.")
         | 
| 35 | 
            +
                    parsed_content
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  # private
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  # @param [String] file_contents
         | 
| 41 | 
            +
                  # @param [Hash] opts
         | 
| 42 | 
            +
                  # @return [WrappedCSV]
         | 
| 43 | 
            +
                  def parse_csv(file_contents, opts={})
         | 
| 44 | 
            +
                    csv_rows = []
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    Rcsv.parse(file_contents, opts) { |row| csv_rows << row }
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    result = WrappedCSV.new
         | 
| 49 | 
            +
                    result.content = extract_named_ranges(csv_rows)
         | 
| 50 | 
            +
                    result.variables = extract_variables(csv_rows)
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    result
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  # @param [Array<Array>] csv_rows
         | 
| 56 | 
            +
                  # @return [Array<Hash>]
         | 
| 57 | 
            +
                  def extract_named_ranges(csv_rows)
         | 
| 58 | 
            +
                    # Use only the NR's that are defined from the candidates list
         | 
| 59 | 
            +
                    candidates = config.template.transformations.candidates
         | 
| 60 | 
            +
                    candidate_nrs = config.template.extraction.required_named_ranges(candidates.map(&:named_range).uniq)
         | 
| 61 | 
            +
                    candidate_nrs.map do |nr|
         | 
| 62 | 
            +
                      {
         | 
| 63 | 
            +
                        "rows" => extract_from_rows(csv_rows, nr["section"]),
         | 
| 64 | 
            +
                        "name" => nr["name"]
         | 
| 65 | 
            +
                      }
         | 
| 66 | 
            +
                    end
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  # @param [Array<Array>] csv_rows
         | 
| 70 | 
            +
                  # @return [Hash]
         | 
| 71 | 
            +
                  def extract_variables(csv_rows)
         | 
| 72 | 
            +
                    config.template.extraction.variables.each_with_object({}) do |variable, hash|
         | 
| 73 | 
            +
                      hash[variable["name"]] = lookup_location(csv_rows, variable["location"])
         | 
| 74 | 
            +
                    end
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  # @param [Array<Array>] rows
         | 
| 78 | 
            +
                  def extract_from_rows(rows, named_range)
         | 
| 79 | 
            +
                    psx = CsvLookup.array_position_lookup(named_range)
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    ret_val = []
         | 
| 82 | 
            +
                    rows.each_with_index do |row, idx|
         | 
| 83 | 
            +
                      # If the position of the end row is -1, we need all,
         | 
| 84 | 
            +
                      # otherwise if its between/equal to start/finish
         | 
| 85 | 
            +
                      ret_val << row[psx[0]..psx[1]] if psx[3] == -1 || (idx >= psx[2] && idx <= psx[3])
         | 
| 86 | 
            +
                    end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                    ret_val
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  # @param [Array<Array>] rows
         | 
| 92 | 
            +
                  # @param [String] variable_location
         | 
| 93 | 
            +
                  def lookup_location(rows, variable_location)
         | 
| 94 | 
            +
                    psx = [CsvLookup.position_for_letter(variable_location),
         | 
| 95 | 
            +
                           CsvLookup.position_for_digit(variable_location)]
         | 
| 96 | 
            +
                    rows[psx[1]][psx[0]]
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  # @param [Integer] row_idx
         | 
| 100 | 
            +
                  # @param [Array] row
         | 
| 101 | 
            +
                  # @param [Array] psx
         | 
| 102 | 
            +
                  # @return [Array, nil]
         | 
| 103 | 
            +
                  def handle_row(row_idx, row, psx)
         | 
| 104 | 
            +
                    return nil unless psx[3] == -1 || (row_idx >= psx[2] && row_idx <= psx[3])
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    row[psx[0]..psx[1]]
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                class WrappedCSV
         | 
| 111 | 
            +
                  # @return [Array<Hash>]
         | 
| 112 | 
            +
                  attr_accessor :content
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                  # @return [Hash]
         | 
| 115 | 
            +
                  attr_accessor :variables
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  def initialize
         | 
| 118 | 
            +
                    @content = []
         | 
| 119 | 
            +
                    @variables = {}
         | 
| 120 | 
            +
                  end
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
              end
         | 
| 123 | 
            +
            end
         | 
| @@ -0,0 +1,51 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Cure
         | 
| 4 | 
            +
              module Generator
         | 
| 5 | 
            +
                class BaseGenerator
         | 
| 6 | 
            +
                  # @return [Hash]
         | 
| 7 | 
            +
                  attr_accessor :options
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def initialize(options={})
         | 
| 10 | 
            +
                    @options = options
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  # @param [Object/Nil] source_value
         | 
| 14 | 
            +
                  # @return [String]
         | 
| 15 | 
            +
                  def generate(source_value=nil)
         | 
| 16 | 
            +
                    translated = _generate(source_value)
         | 
| 17 | 
            +
                    translated = "#{prefix}#{translated}" if prefix
         | 
| 18 | 
            +
                    translated = "#{translated}#{suffix}" if suffix
         | 
| 19 | 
            +
                    translated
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  private
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  # @param [Object/Nil] _source_value
         | 
| 25 | 
            +
                  # @return [String]
         | 
| 26 | 
            +
                  def _generate(_source_value)
         | 
| 27 | 
            +
                    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def prefix(default=nil)
         | 
| 31 | 
            +
                    extract_property("prefix", default)
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def suffix(default=nil)
         | 
| 35 | 
            +
                    extract_property("suffix", default)
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def length(default=nil)
         | 
| 39 | 
            +
                    extract_property("length", default)
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def property_name(default=nil)
         | 
| 43 | 
            +
                    extract_property("name", default)
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  def extract_property(property, default_val)
         | 
| 47 | 
            +
                    @options.fetch(property, default_val)
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
            end
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "cure/generator/base_generator"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Cure
         | 
| 6 | 
            +
              module Generator
         | 
| 7 | 
            +
                class CaseGenerator < BaseGenerator
         | 
| 8 | 
            +
                  private
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  # @param [Object] source_value
         | 
| 11 | 
            +
                  def _generate(source_value)
         | 
| 12 | 
            +
                    result = case_options.fetch("switch").find { |opts| opts["case"] == source_value }&.fetch("return_value", nil)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    return result if result
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    case_options.fetch("else", {}).fetch("return_value", nil)
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  # @return [Hash]
         | 
| 20 | 
            +
                  def case_options
         | 
| 21 | 
            +
                    @case_options ||= extract_property("statement", nil)
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "cure/generator/base_generator"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Cure
         | 
| 6 | 
            +
              module Generator
         | 
| 7 | 
            +
                class CharacterGenerator < BaseGenerator
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def initialize(options=nil)
         | 
| 10 | 
            +
                    super(options)
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  private
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  # @param [Object] source_value
         | 
| 16 | 
            +
                  def _generate(source_value)
         | 
| 17 | 
            +
                    arr = build_options.map(&:to_a).flatten
         | 
| 18 | 
            +
                    (0...length(source_value&.length || 5)).map { arr[rand(arr.length)] }.join
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def build_options
         | 
| 22 | 
            +
                    return [("a".."z"), ("A".."Z"), (0..9)] unless @options.key?("types")
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    type_array = @options["types"]
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    arr = []
         | 
| 27 | 
            +
                    arr << ("a".."z") if type_array.include? "lowercase"
         | 
| 28 | 
            +
                    arr << ("A".."Z") if type_array.include? "uppercase"
         | 
| 29 | 
            +
                    arr << (0..9) if type_array.include? "number"
         | 
| 30 | 
            +
                    arr << ("!".."+") if type_array.include? "symbol"
         | 
| 31 | 
            +
                    arr
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "cure/generator/base_generator"
         | 
| 4 | 
            +
            require "faker"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Cure
         | 
| 7 | 
            +
              module Generator
         | 
| 8 | 
            +
                class FakerGenerator < BaseGenerator
         | 
| 9 | 
            +
                  private
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  # @param [Object] _source_value
         | 
| 12 | 
            +
                  def _generate(_source_value)
         | 
| 13 | 
            +
                    mod_code = extract_property("module", nil)
         | 
| 14 | 
            +
                    mod = Faker.const_get(mod_code)
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    raise "No Faker module found for [#{mod_code}]" unless mod
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    meth_code = extract_property("method", nil)&.to_sym
         | 
| 19 | 
            +
                    raise "No Faker module found for [#{meth_code}]" unless mod.methods.include?(meth_code)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    mod.send(meth_code)
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "cure/generator/base_generator"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Cure
         | 
| 6 | 
            +
              module Generator
         | 
| 7 | 
            +
                class GuidGenerator < BaseGenerator
         | 
| 8 | 
            +
                  private
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  # @param [Object] _source_value
         | 
| 11 | 
            +
                  def _generate(_source_value)
         | 
| 12 | 
            +
                    SecureRandom.uuid.to_s
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "cure/generator/base_generator"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Cure
         | 
| 6 | 
            +
              module Generator
         | 
| 7 | 
            +
                class HexGenerator < BaseGenerator
         | 
| 8 | 
            +
                  private
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  # @param [Object] _source_value
         | 
| 11 | 
            +
                  def _generate(_source_value)
         | 
| 12 | 
            +
                    1.upto(length(rand(0..9))).map { rand(0..15).to_s(16) }.join("")
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
| @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "cure/generator/base_generator"
         | 
| 4 | 
            +
            require "cure/generator/hex_generator"
         | 
| 5 | 
            +
            require "cure/generator/case_generator"
         | 
| 6 | 
            +
            require "cure/generator/character_generator"
         | 
| 7 | 
            +
            require "cure/generator/faker_generator"
         | 
| 8 | 
            +
            require "cure/generator/guid_generator"
         | 
| 9 | 
            +
            require "cure/generator/number_generator"
         | 
| 10 | 
            +
            require "cure/generator/placeholder_generator"
         | 
| 11 | 
            +
            require "cure/generator/redact_generator"
         | 
| 12 | 
            +
            require "cure/generator/variable_generator"
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "cure/generator/base_generator"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Cure
         | 
| 6 | 
            +
              module Generator
         | 
| 7 | 
            +
                class NumberGenerator < BaseGenerator
         | 
| 8 | 
            +
                  private
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  # @param [Object] _source_value
         | 
| 11 | 
            +
                  def _generate(_source_value)
         | 
| 12 | 
            +
                    1.upto(length(rand(0..9))).map { rand(1..9) }.join("").to_i
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         |