slither 0.99.4 → 0.99.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +22 -0
- data/CHANGELOG.md +26 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +63 -0
- data/LICENSE.txt +21 -0
- data/README.md +95 -0
- data/Rakefile +12 -37
- data/TODO +2 -1
- data/lib/slither/column.rb +96 -76
- data/lib/slither/definition.rb +28 -13
- data/lib/slither/generator.rb +46 -26
- data/lib/slither/parser.rb +118 -53
- data/lib/slither/section.rb +39 -21
- data/lib/slither/version.rb +5 -0
- data/lib/slither.rb +68 -7
- data/sig/slither.rbs +4 -0
- metadata +81 -82
- data/History.txt +0 -16
- data/README.rdoc +0 -100
- data/lib/slither/slither.rb +0 -49
- data/slither.gemspec +0 -0
- data/spec/column_spec.rb +0 -224
- data/spec/definition_spec.rb +0 -85
- data/spec/generator_spec.rb +0 -42
- data/spec/parser_spec.rb +0 -74
- data/spec/section_spec.rb +0 -146
- data/spec/slither_spec.rb +0 -84
- data/spec/spec_helper.rb +0 -4
data/lib/slither/generator.rb
CHANGED
@@ -1,26 +1,46 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
data/lib/slither/parser.rb
CHANGED
@@ -1,53 +1,118 @@
|
|
1
|
-
|
2
|
-
class Parser
|
3
|
-
|
4
|
-
|
5
|
-
@
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
data/lib/slither/section.rb
CHANGED
@@ -1,54 +1,64 @@
|
|
1
|
-
|
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
|
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
|
-
|
76
|
-
|
90
|
+
def unpacker
|
91
|
+
@columns.map { |c| c.unpacker }.join('')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/slither.rb
CHANGED
@@ -1,7 +1,68 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
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
|
-
|
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:
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
16
10
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
25
|
-
|
26
|
-
requirements:
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
27
24
|
- - ">="
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
44
|
-
-
|
45
|
-
-
|
46
|
-
|
47
|
-
-
|
48
|
-
-
|
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/
|
58
|
-
- slither.
|
59
|
-
|
60
|
-
|
61
|
-
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
78
|
-
requirements:
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
79
89
|
- - ">="
|
80
|
-
- !ruby/object:Gem::Version
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
91
|
-
segments:
|
92
|
-
- 0
|
93
|
-
version: "0"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
94
97
|
requirements: []
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|