slither 0.99.4 → 0.99.5

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.
@@ -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