slither 0.99.4 → 0.99.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,26 +1,46 @@
1
- class Slither
2
- class Generator
3
-
4
- def initialize(definition)
5
- @definition = definition
6
- end
7
-
8
- def generate(data)
9
- @builder = []
10
- @definition.sections.each do |section|
11
- content = data[section.name]
12
- if content
13
- content = [content] unless content.is_a?(Array)
14
- raise(Slither::RequiredSectionEmptyError, "Required section '#{section.name}' was empty.") if content.empty?
15
- content.each do |row|
16
- @builder << section.format(row)
17
- end
18
- else
19
- raise(Slither::RequiredSectionEmptyError, "Required section '#{section.name}' was empty.") unless section.optional
20
- end
21
- end
22
- @builder.join("\n")
23
- end
24
-
25
- end
26
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Slither
4
+ class Generator
5
+ def initialize(definition)
6
+ @definition = definition
7
+ end
8
+
9
+ def generate(data)
10
+ @builder = []
11
+
12
+ sections.each do |section|
13
+ content = data[section.name]
14
+
15
+ if content
16
+ content = [content] unless content.is_a?(Array)
17
+
18
+ raise_required_section_empty(section) if content.empty?
19
+
20
+ content.each do |row|
21
+ builder << section.format(row)
22
+ end
23
+ else
24
+ raise_required_section_empty(section) unless section.optional
25
+ end
26
+ end
27
+
28
+ builder.join("\n")
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :definition, :builder
34
+
35
+ def sections
36
+ definition.sections
37
+ end
38
+
39
+ def raise_required_section_empty(section)
40
+ raise(
41
+ Slither::RequiredSectionEmptyError,
42
+ "Required section '#{section.name}' was empty."
43
+ )
44
+ end
45
+ end
46
+ end
@@ -1,53 +1,118 @@
1
- class Slither
2
- class Parser
3
-
4
- def initialize(definition, file)
5
- @definition = definition
6
- @file = file
7
- # This may be used in the future for non-linear or repeating sections
8
- @mode = :linear
9
- end
10
-
11
- def parse()
12
- @parsed = {}
13
- @content = read_file
14
- unless @content.empty?
15
- @definition.sections.each do |section|
16
- rows = fill_content(section)
17
- raise(Slither::RequiredSectionNotFoundError, "Required section '#{section.name}' was not found.") unless rows > 0 || section.optional
18
- end
19
- end
20
- @parsed
21
- end
22
-
23
- private
24
-
25
- def read_file
26
- content = []
27
- File.open(@file, 'r') do |f|
28
- while (line = f.gets) do
29
- content << line
30
- end
31
- end
32
- content
33
- end
34
-
35
- def fill_content(section)
36
- matches = 0
37
- loop do
38
- line = @content.first
39
- break unless section.match(line)
40
- add_to_section(section, line)
41
- matches += 1
42
- @content.shift
43
- end
44
- matches
45
- end
46
-
47
- def add_to_section(section, line)
48
- @parsed[section.name] = [] unless @parsed[section.name]
49
- @parsed[section.name] << section.parse(line)
50
- end
51
-
52
- end
53
- end
1
+ module Slither
2
+ class Parser
3
+ def initialize(definition, file_io)
4
+ @definition = definition
5
+ @file = file_io
6
+ # This may be used in the future for non-linear or repeating sections
7
+ @mode = :linear
8
+ end
9
+
10
+ def parse
11
+ parsed = {}
12
+
13
+ @file.each_line do |line|
14
+ line&.chomp!
15
+
16
+ next if line.empty?
17
+
18
+ @definition.sections.each do |section|
19
+ if section.match(line)
20
+ validate_length(line, section)
21
+ parsed = fill_content(line, section, parsed)
22
+ end
23
+ end
24
+ end
25
+
26
+ @definition.sections.each do |section|
27
+ unless parsed[section.name] || section.optional
28
+ raise(Slither::RequiredSectionNotFoundError,
29
+ "Required section '#{section.name}' was not found.")
30
+ end
31
+ end
32
+ parsed
33
+ end
34
+
35
+ def parse_by_bytes
36
+ parsed = {}
37
+
38
+ all_section_lengths = @definition.sections.map(&:length)
39
+ byte_length = all_section_lengths.max
40
+ all_section_lengths.each do |bytes|
41
+ next unless bytes != byte_length
42
+
43
+ raise(
44
+ Slither::SectionsNotSameLengthError, "All sections must have the same number of bytes for parse by bytes"
45
+ )
46
+ end
47
+
48
+ while (record = @file.read(byte_length))
49
+
50
+ unless remove_newlines! && byte_length == record.length
51
+ parsed_line = parse_for_error_message(record)
52
+ raise(Slither::LineWrongSizeError, "Line wrong size: No newline at #{byte_length} bytes. #{parsed_line}")
53
+ end
54
+
55
+ record.force_encoding(@file.external_encoding)
56
+
57
+ @definition.sections.each do |section|
58
+ parsed = fill_content(record, section, parsed) if section.match(record)
59
+ end
60
+ end
61
+
62
+ @definition.sections.each do |section|
63
+ unless parsed[section.name] || section.optional
64
+ raise(Slither::RequiredSectionNotFoundError,
65
+ "Required section '#{section.name}' was not found.")
66
+ end
67
+ end
68
+ parsed
69
+ end
70
+
71
+ private
72
+
73
+ def fill_content(line, section, parsed)
74
+ parsed[section.name] ||= []
75
+ parsed[section.name] << section.parse(line)
76
+ parsed
77
+ end
78
+
79
+ def validate_length(line, section)
80
+ return unless line.length != section.length
81
+
82
+ parsed_line = parse_for_error_message(line)
83
+ raise Slither::LineWrongSizeError,
84
+ "Line wrong size: (#{line.length} when it should be #{section.length}. #{parsed_line})"
85
+ end
86
+
87
+ def remove_newlines!
88
+ return true if @file.eof?
89
+
90
+ b = @file.getbyte
91
+
92
+ if b == 10 || (b == 13 && @file.getbyte == 10)
93
+ true
94
+ else
95
+ @file.ungetbyte b
96
+ false
97
+ end
98
+ end
99
+
100
+ def newline?(char_code)
101
+ # \n or LF -> 10
102
+ # \r or CR -> 13
103
+ [10, 13].any? { |code| char_code == code }
104
+ end
105
+
106
+ def parse_for_error_message(line)
107
+ parsed = ""
108
+
109
+ line.force_encoding(@file.external_encoding)
110
+
111
+ @definition.sections.each do |section|
112
+ parsed = section.parse_when_problem(line) if section.match(line)
113
+ end
114
+
115
+ parsed
116
+ end
117
+ end
118
+ end
@@ -1,54 +1,64 @@
1
- class Slither
1
+ module Slither
2
2
  class Section
3
3
  attr_accessor :definition, :optional
4
- attr_reader :name, :columns, :options
5
-
4
+ attr_reader :name, :columns, :options, :length
5
+
6
6
  RESERVED_NAMES = [:spacer]
7
-
7
+
8
8
  def initialize(name, options = {})
9
9
  @name = name
10
10
  @options = options
11
11
  @columns = []
12
12
  @trap = options[:trap]
13
13
  @optional = options[:optional] || false
14
+ @length = 0
14
15
  end
15
-
16
+
16
17
  def column(name, length, options = {})
17
18
  raise(Slither::DuplicateColumnNameError, "You have already defined a column named '#{name}'.") if @columns.map do |c|
18
19
  RESERVED_NAMES.include?(c.name) ? nil : c.name
19
20
  end.flatten.include?(name)
20
21
  col = Column.new(name, length, @options.merge(options))
21
22
  @columns << col
23
+ @length += length
22
24
  col
23
25
  end
24
-
26
+
25
27
  def spacer(length)
26
28
  column(:spacer, length)
27
29
  end
28
-
30
+
29
31
  def trap(&block)
30
32
  @trap = block
31
33
  end
32
-
34
+
33
35
  def template(name)
34
36
  template = @definition.templates[name]
35
37
  raise ArgumentError, "Template #{name} not found as a known template." unless template
36
- @columns = @columns + template.columns
38
+ @columns += template.columns
39
+ @length += template.length
37
40
  # Section options should trump template options
38
41
  @options = template.options.merge(@options)
39
42
  end
40
-
43
+
44
+ # Format a data Hash using columns width.
45
+ # - Data - hash, based on columns definitions content.
46
+ # Ex: Having the next 2 columns .column(:id, 5) && .column(:name, 10)
47
+ # we pass the data hash data = { id: 3, name: "Ryan" }
48
+ # the result is the content of the hash based on the columns width:
49
+ # format(data)
50
+ # => " 3 Ryan"
41
51
  def format(data)
42
52
  # raise( ColumnMismatchError,
43
53
  # "The '#{@name}' section has #{@columns.size} column(s) defined, but there are #{data.size} column(s) provided in the data."
44
54
  # ) unless @columns.size == data.size
45
- row = ''
55
+ row = ''
46
56
  @columns.each do |column|
47
57
  row += column.format(data[column.name])
48
58
  end
49
59
  row
50
60
  end
51
-
61
+
52
62
  def parse(line)
53
63
  line_data = line.unpack(unpacker)
54
64
  row = {}
@@ -57,20 +67,28 @@ class Slither
57
67
  end
58
68
  row
59
69
  end
60
-
70
+
71
+ def parse_when_problem(line)
72
+ line_data = line.unpack(@columns.map { |c| "a#{c.length}" }.join(''))
73
+ row = ''
74
+ @columns.each_with_index do |c, i|
75
+ row << "\n'#{c.name}':'#{line_data[i]}'" unless RESERVED_NAMES.include?(c.name)
76
+ end
77
+ row
78
+ end
79
+
61
80
  def match(raw_line)
62
81
  raw_line.nil? ? false : @trap.call(raw_line)
63
82
  end
64
-
83
+
65
84
  def method_missing(method, *args)
66
85
  column(method, *args)
67
86
  end
68
-
87
+
69
88
  private
70
-
71
- def unpacker
72
- @columns.map { |c| c.unpacker }.join('')
73
- end
74
89
 
75
- end
76
- end
90
+ def unpacker
91
+ @columns.map { |c| c.unpacker }.join('')
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slither
4
+ VERSION = "0.99.5"
5
+ end
data/lib/slither.rb CHANGED
@@ -1,7 +1,68 @@
1
- $: << File.dirname(__FILE__)
2
- require 'slither/slither'
3
- require 'slither/definition'
4
- require 'slither/section'
5
- require 'slither/column'
6
- require 'slither/parser'
7
- require 'slither/generator'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'slither/column'
4
+ require_relative 'slither/definition'
5
+ require_relative 'slither/generator'
6
+ require_relative 'slither/parser'
7
+ require_relative 'slither/section'
8
+ require_relative "slither/version"
9
+
10
+ module Slither
11
+ class Error < StandardError; end
12
+ # Your code goes here...
13
+
14
+ class DuplicateColumnNameError < StandardError; end
15
+ class RequiredSectionNotFoundError < StandardError; end
16
+ class RequiredSectionEmptyError < StandardError; end
17
+ class FormattedStringExceedsLengthError < StandardError; end
18
+ class ColumnMismatchError < StandardError; end
19
+ class LineWrongSizeError < StandardError; end
20
+ class SectionsNotSameLengthError < StandardError; end
21
+
22
+
23
+ def self.define(name, options = {}, &block)
24
+ definition = Definition.new(options)
25
+ yield(definition)
26
+ definitions[name] = definition
27
+ definition
28
+ end
29
+
30
+ # Generate a File from Data.
31
+ def self.generate(definition_name, data)
32
+ definition = definition(definition_name)
33
+ raise ArgumentError, "Definition name '#{name}' was not found." unless definition
34
+ generator = Generator.new(definition)
35
+ generator.generate(data)
36
+ end
37
+
38
+ # Writes the File
39
+ def self.write(filename, definition_name, data)
40
+ File.open(filename, 'w') do |f|
41
+ f.write generate(definition_name, data)
42
+ end
43
+ end
44
+
45
+ def self.parse(filename, definition_name)
46
+ raise ArgumentError, "File #{filename} does not exist." unless File.exists?(filename)
47
+
48
+ file_io = File.open(filename, 'r')
49
+ parseIo(file_io, definition_name)
50
+ end
51
+
52
+ def self.parseIo(io, definition_name)
53
+ definition = definition(definition_name)
54
+ raise ArgumentError, "Definition name '#{definition_name}' was not found." unless definition
55
+ parser = Parser.new(definition, io)
56
+ definition.options[:by_bytes] ? parser.parse_by_bytes : parser.parse
57
+ end
58
+
59
+ private
60
+
61
+ def self.definitions
62
+ @@definitions ||= {}
63
+ end
64
+
65
+ def self.definition(name)
66
+ definitions[name]
67
+ end
68
+ end
data/sig/slither.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Slither
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata CHANGED
@@ -1,51 +1,71 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: slither
3
- version: !ruby/object:Gem::Version
4
- hash: 411
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 99
9
- - 4
10
- version: 0.99.4
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.99.5
11
5
  platform: ruby
12
- authors:
6
+ authors:
13
7
  - Ryan Wood
14
- autorequire:
15
- bindir: bin
8
+ autorequire:
9
+ bindir: exe
16
10
  cert_chain: []
17
-
18
- date: 2010-10-07 00:00:00 -04:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- name: bones
11
+ date: 2023-08-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pry
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
23
21
  prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
27
24
  - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 25
30
- segments:
31
- - 2
32
- - 5
33
- - 1
34
- version: 2.5.1
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
35
34
  type: :development
36
- version_requirements: *id001
37
- description: A simple, clean DSL for describing, writing, and parsing fixed-width text files.
38
- email: ryan.wood@gmail.com
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: '2.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: '2.6'
55
+ description:
56
+ email:
39
57
  executables: []
40
-
41
58
  extensions: []
42
-
43
- extra_rdoc_files:
44
- - History.txt
45
- - README.rdoc
46
- files:
47
- - History.txt
48
- - README.rdoc
59
+ extra_rdoc_files: []
60
+ files:
61
+ - ".rspec"
62
+ - ".rubocop.yml"
63
+ - CHANGELOG.md
64
+ - CODE_OF_CONDUCT.md
65
+ - Gemfile
66
+ - Gemfile.lock
67
+ - LICENSE.txt
68
+ - README.md
49
69
  - Rakefile
50
70
  - TODO
51
71
  - lib/slither.rb
@@ -54,51 +74,30 @@ files:
54
74
  - lib/slither/generator.rb
55
75
  - lib/slither/parser.rb
56
76
  - lib/slither/section.rb
57
- - lib/slither/slither.rb
58
- - slither.gemspec
59
- - spec/column_spec.rb
60
- - spec/definition_spec.rb
61
- - spec/generator_spec.rb
62
- - spec/parser_spec.rb
63
- - spec/section_spec.rb
64
- - spec/slither_spec.rb
65
- - spec/spec_helper.rb
66
- has_rdoc: true
67
- homepage: http://github.com/ryanwood/slither
68
- licenses: []
69
-
70
- post_install_message:
71
- rdoc_options:
72
- - --main
73
- - README.rdoc
74
- require_paths:
77
+ - lib/slither/version.rb
78
+ - sig/slither.rbs
79
+ homepage: https://github.com/JorgeGarciaxyz/slither
80
+ licenses:
81
+ - MIT
82
+ metadata: {}
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
75
86
  - lib
76
- required_ruby_version: !ruby/object:Gem::Requirement
77
- none: false
78
- requirements:
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
79
89
  - - ">="
80
- - !ruby/object:Gem::Version
81
- hash: 3
82
- segments:
83
- - 0
84
- version: "0"
85
- required_rubygems_version: !ruby/object:Gem::Requirement
86
- none: false
87
- requirements:
90
+ - !ruby/object:Gem::Version
91
+ version: 3.0.0
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
88
94
  - - ">="
89
- - !ruby/object:Gem::Version
90
- hash: 3
91
- segments:
92
- - 0
93
- version: "0"
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
94
97
  requirements: []
95
-
96
- rubyforge_project: !binary |
97
- AA==
98
-
99
- rubygems_version: 1.3.7
100
- signing_key:
101
- specification_version: 2
102
- summary: A simple, clean DSL for describing, writing, and parsing fixed-width text files
98
+ rubygems_version: 3.3.7
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: A simple, clean DSL for describing, writing, and parsing fixed-width text
102
+ files.
103
103
  test_files: []
104
-
data/History.txt DELETED
@@ -1,16 +0,0 @@
1
-
2
- == 0.99.2 / 2009-04-28
3
-
4
- * Added better support for float formatting
5
- * Added the money_with_implied_decimal type
6
-
7
- == 0.99.1 / 2009-04-22
8
-
9
- * Make the missing method build a column i.e. body.record_type 1
10
- * Prevent duplicate column names
11
- * Better error messages
12
- * Implement custom padding (spaces (default), zero)
13
-
14
- == 0.99.0 / 2009-04-14
15
-
16
- * Initial Release