nacha 0.2.00 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e7e5a0b7067a5bc2fda825f05bc61f86992b4653056b5800892248b57561952
4
- data.tar.gz: 547452686b682558a049c5c1b1d7985f9b55bec7219b9fc285a3c225c3ee5396
3
+ metadata.gz: c2ae01e0c5a90efda594187387efe2150ff1548affb15c65b50fcb1fbc73dc6f
4
+ data.tar.gz: db181b0d320ab60664c14af3cdcccfc69a6879085bf167c80496303ee9bf6d6a
5
5
  SHA512:
6
- metadata.gz: f9d3b8d3e944540b87e2aa62061682c94786590bc1c1fdda8657a3bdf5e1ed57425a6a9e10cf9a0dcd71716ca01987d609cc8bb821460ff762e822c314734338
7
- data.tar.gz: 21ed379b621b006d716e882d23f0e768269d39938e951bc2add70c3043670bb929117f19fa698ad69b1e6e9b2614b9582af25a3492eecaacbf8597601076b5bb
6
+ metadata.gz: 17564ad39c737c944c7d7b86ed3340fd7ac4aaf0422879051095788b3161b6e487bb892042af646eb760a9a4c84d5a06d13de737fd95f00cddf0daad0e093e46
7
+ data.tar.gz: 358f227a837f75d3f64717b44164e86d35f7c7d52ae95fef851e53e9b06422b71d7e62d975a2b3fa42167ce9e620b52c47c05e3c4fc816ba097b8d7343300710
data/CHANGELOG.md CHANGED
@@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ## [0.2.0] - 2025-08-03
10
+ ## [0.2.1] - 2025-08-03
11
11
 
12
12
  - Better interface to parsed records (Nacha::AchFile)
13
13
 
data/README.md CHANGED
@@ -34,6 +34,12 @@ Or install it yourself as:
34
34
  API may change at any time. Pull requests welcomed
35
35
 
36
36
 
37
+ ```ruby
38
+ ach_file = Nacha.parse('path/to/file.ach')
39
+
40
+ puts ach_file.to_json
41
+ puts ach_file.to_markdown
42
+ ```
37
43
 
38
44
  ```ruby
39
45
  ach_string = "101 124000054 1240000540907021214A094101ZIONS FIRST NATIONAL BAZIONS FIRST NATIONAL BA 1"
data/exe/nacha CHANGED
@@ -23,65 +23,38 @@ module Nacha
23
23
  enum: %w[html json md markdown ach]
24
24
  option :md_flavor, default: "common_mark", enum: %w[common_mark github]
25
25
  def parse(file_path = nil)
26
- if file_path.nil? || file_path.empty?
27
- input_file = $stdin
28
- elsif File.exist?(file_path)
29
- input_file = File.open(file_path)
30
- else
31
- puts "Error: File not found at #{file_path}"
32
- exit 1
33
- end
26
+ ach_file = Nacha::AchFile.new(file_path || $stdin)
34
27
 
35
- raw_records = input_file.read
36
- ach_file = Nacha.parse(raw_records)
28
+ ach_file.parse
37
29
 
38
- if ach_file && ach_file.is_a?(Array) && !ach_file.empty?
30
+ if ach_file.records.is_a?(Array) && !ach_file.records.empty?
39
31
  if options[:output_file]
40
32
  File.open(options[:output_file], "w") do |f|
41
- write_output(ach_file, f, input_file)
33
+ write_output(ach_file, f)
42
34
  end
43
35
  else
44
- write_output(ach_file, $stdout, input_file)
36
+ write_output(ach_file, $stdout)
45
37
  end
46
38
  else
47
39
  puts "Could not parse the file or the file was empty."
48
40
  end
49
41
  rescue StandardError => e
50
42
  puts "An error occurred during parsing: #{e.message}"
51
- puts e.backtrace.join("\n")
52
43
  exit 1
53
44
  end
54
45
 
55
46
  private
56
47
 
57
- def write_output(ach_records, io, file)
58
- formatter_options = {
59
- file_name: File.basename(file.path),
60
- file_size: file.respond_to?(:size) ? file.size : nil,
61
- number_of_lines: ach_records.size,
62
- created_at: file.respond_to?(:ctime) ? file.ctime : Time.now,
63
- modified_at: file.respond_to?(:mtime) ? file.mtime : Time.now,
64
- preamble: HTML_PREAMBLE_FILE,
65
- postamble: HTML_POSTAMBLE_FILE
66
- }
67
-
48
+ def write_output(ach_file, io)
68
49
  case options[:format]
69
50
  when 'ach'
70
- output_ach(ach_records, io)
51
+ io.puts ach_file.to_ach
52
+ when 'html'
53
+ io.puts ach_file.to_html
71
54
  when 'md', 'markdown'
72
- formatter_options[:flavor] = options[:md_flavor].to_sym
73
- formatter = Nacha::Formatter::FormatterFactory.get(:markdown, ach_records, formatter_options)
74
- io.puts formatter.format
55
+ io.puts ach_file.to_markdown
75
56
  else
76
- formatter = Nacha::Formatter::FormatterFactory.get(options[:format].to_sym, ach_records,
77
- formatter_options)
78
- io.puts formatter.format
79
- end
80
- end
81
-
82
- def output_ach(ach_records, io)
83
- ach_records.each do |record|
84
- io.puts record.to_ach
57
+ io.puts ach_file.to_json
85
58
  end
86
59
  end
87
60
  end
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri' # Required for URI.regexp
4
+ require 'openssl'
5
+ require 'httparty'
6
+ require 'nacha/formatter'
7
+
8
+ module Nacha
9
+ # Class for handling ACH files, which can be a URL, a file path, or a string of data.
10
+ # encapsulating the records and handling output
11
+ class AchFile
12
+ TEMPLATES_DIR = File.join(Gem::Specification.find_by_name("nacha").gem_dir,
13
+ "templates").freeze
14
+ HTML_PREAMBLE_FILE = File.join(TEMPLATES_DIR, "html_preamble.html")
15
+ HTML_POSTAMBLE_FILE = File.join(TEMPLATES_DIR, "html_postamble.html")
16
+
17
+ attr_reader :raw_input, :input_type, :records, :checksum, :input_source,
18
+ :created_at, :modified_at
19
+
20
+ def initialize(input_string)
21
+ @input_type = classify_input(input_string)
22
+ @records = []
23
+ case @input_type
24
+ when 'url'
25
+ @input_source = input_string
26
+ @raw_input = read_url_contents_httparty(input_string)
27
+ when 'filepath'
28
+ raise ArgumentError, "File not found: #{input_string}" unless File.exist?(input_string)
29
+
30
+ @input_source = input_string
31
+ @raw_input = File.read(input_string)
32
+ when 'string of data'
33
+ @input_source = "string"
34
+ @raw_input = input_string
35
+ else
36
+ if input_string.respond_to?(:read)
37
+ # If input_string is an IO object (like $stdin), read it directly
38
+ @input_source = "stdin"
39
+ @raw_input = input_string.read
40
+ else
41
+ # Otherwise, treat it as a simple string
42
+ @input_source = "string"
43
+ @raw_input = input_string.to_s
44
+ end
45
+ end
46
+ @checksum = OpenSSL::Digest::SHA256.hexdigest(@raw_input) if @raw_input
47
+ end
48
+
49
+ def parse
50
+ @records = Nacha::Parser.new.parse_string(@raw_input)
51
+ self
52
+ end
53
+
54
+ def to_ach
55
+ records.map(&:to_ach).join("\n")
56
+ end
57
+
58
+ def to_json
59
+ formatter(:json).format
60
+ end
61
+
62
+ def to_html
63
+ formatter(:html).format
64
+ end
65
+
66
+ def to_markdown
67
+ formatter(:markdown).format
68
+ end
69
+
70
+ private
71
+
72
+ def formatter_options
73
+ {
74
+ file_name: File.basename(@input_source),
75
+ file_size: @raw_input.size,
76
+ number_of_lines: records.size,
77
+ checksum: @checksum,
78
+ preamble: HTML_PREAMBLE_FILE,
79
+ postamble: HTML_POSTAMBLE_FILE
80
+ }.merge(file_info)
81
+ end
82
+
83
+ def file_info
84
+ return {} unless @input_type == 'filepath' && File.exist?(@input_source)
85
+
86
+ File.open(@input_source,'r') do |file|
87
+ {
88
+ created_at: file.ctime,
89
+ modified_at: file.mtime,
90
+ }
91
+ end
92
+ end
93
+
94
+ def formatter(format)
95
+ Formatter::FormatterFactory.get(format, self, formatter_options)
96
+ end
97
+
98
+ # Reads the content of a URL using HTTParty.
99
+ #
100
+ # @param url [String] The URL to read.
101
+ # @return [String, nil] The content of the URL as a string, or nil if an error occurred.
102
+ def read_url_contents_httparty(url)
103
+ response = HTTParty.get(url, timeout: 60) # Set a timeout
104
+ if response.success?
105
+ response.body
106
+ else
107
+ puts "Error: HTTP request failed with status #{response.code} #{response.message} for #{url}"
108
+ nil
109
+ end
110
+ rescue StandardError, HTTParty::Error => err
111
+ # HTTParty wraps various network and HTTP errors
112
+ puts "Error: An unexpected error occurred while reading #{url} - #{err.message}"
113
+ end
114
+
115
+ # Classifies an input string as a filepath, URL, or string of data.
116
+ # The classification order is: URL -> Filepath -> String of Data (if >= 94 chars) -> Generic String.
117
+ #
118
+ # @param input_arg [String, Object] The input to classify. It will be converted to a string.
119
+ # @return [String] One of 'url', 'filepath', 'string of data', or 'string'.
120
+ def classify_input(input_arg)
121
+ return 'file' if input_arg.respond_to?(:read)
122
+ # Ensure the input is treated as a string for consistent pattern matching.
123
+ # This handles cases where the input might be a number, nil, etc., by converting them to string.
124
+ input = input_arg.to_s
125
+
126
+ # 1. Check for URL
127
+ # URI::DEFAULT_PARSER.make_regexp is the standard way to match URIs according to RFCs.
128
+ # It primarily looks for a scheme (e.g., http://, https://, ftp://, file://).
129
+ # Note: It will typically *not* classify "www.example.com" as a URL without a scheme.
130
+ return 'url' if URI::DEFAULT_PARSER.make_regexp.match?(input)
131
+
132
+ # 2. Check for Filepath
133
+ # This is the most complex part due to the variety of path formats (absolute, relative,
134
+ # Windows, Unix-like) and the ambiguity with general strings.
135
+ is_filepath = false
136
+
137
+ # A. Absolute Path Patterns:
138
+ # - Unix-like absolute: `/path/to/file`
139
+ # - Windows drive letter: `C:\path\to\file` or `C:/path/to/file`
140
+ # - Windows UNC path: `\\server\share\path`
141
+ # - Home directory (Unix-like): `~/path/to/file`
142
+ if input =~ %r{
143
+ ^ # Start of the string
144
+ (?: # Non-capturing group for different absolute path roots
145
+ / | # Unix-like root (e.g., /usr/local)
146
+ [A-Za-z]:[\\/] | # Windows drive letter (e.g., C:\, D:/)
147
+ \\\\(?:[a-zA-Z0-9_.-]+[\\/])+[a-zA-Z0-9_.-]* | # Windows UNC path (e.g., \\server\share\folder or \\server\share)
148
+ ~[\\/] # Unix-like home directory (e.g., ~/documents)
149
+ )
150
+ }x # The 'x' modifier allows whitespace and comments in the regex for readability
151
+ is_filepath = true
152
+ # B. Relative Path Starting with Common Indicators:
153
+ # - Current directory: `./file` or `.\file`
154
+ # - Parent directory: `../file` or `..\file`
155
+ elsif input =~ %r{^\.{1,2}[\\/]}
156
+ is_filepath = true
157
+ # C. Path containing separators AND ending with a common file extension:
158
+ # e.g., "folder/image.jpg", "document.pdf" (if it contains a slash or just "document.pdf")
159
+ # This handles cases like "my_photo.png" or "data/report.xlsx".
160
+ elsif (input.include?('/') || input.include?('\\')) && input =~ /\.[a-zA-Z0-9]{2,5}$/
161
+ is_filepath = true
162
+ # D. Path with separators and multiple components or ending in a separator:
163
+ # e.g., "folder/file", "dir1/dir2/file", "dir/subdir/", "/another_folder/"
164
+ # This tries to identify directory structures.
165
+ elsif (input.include?('/') || input.include?('\\'))
166
+ # Split the path by either / or \ and remove any empty strings that result
167
+ # (e.g., "/a/b" splits to ["", "a", "b"]; rejecting empty ones yields ["a", "b"]).
168
+ segments = input.split(/[\/\\]/).reject(&:empty?)
169
+ if segments.length >= 2 || (segments.length >= 1 && (input.end_with?('/') || input.end_with?('\\')))
170
+ # Classify as filepath if:
171
+ # - It has two or more segments (e.g., "folder/file", "/folder/sub/file").
172
+ # - OR it has at least one segment AND ends with a path separator (e.g., "folder/", "/folder/").
173
+ is_filepath = true
174
+ end
175
+ # E. Simple filename with a common extension, not caught by other rules (e.g., "report.txt")
176
+ # This handles cases like "my_document.docx" which have no directory separators but are filenames.
177
+ elsif input =~ /^[a-zA-Z0-9_.-]+\.[a-zA-Z0-9]{2,5}$/
178
+ # Basic check for "name.ext" pattern, allowing common filename characters.
179
+ is_filepath = true
180
+ end
181
+
182
+ if is_filepath
183
+ return 'filepath'
184
+ end
185
+
186
+ # 3. Check for "string of data"
187
+ # This is the fallback for anything that isn't clearly a URL or a filepath,
188
+ # AND meets the minimum length requirement as per the problem description.
189
+ if input.length >= 94
190
+ return 'string of data'
191
+ end
192
+
193
+ # 4. Default: Just a regular string
194
+ # If it doesn't fit any of the above categories (URL, filepath, long data string),
195
+ # it's considered a generic string (e.g., "Hello World", "short_name", "a_simple_word").
196
+ return 'string'
197
+ end
198
+
199
+ # # --- Test Cases ---
200
+
201
+ # puts "--- URLs ---"
202
+ # puts "http://example.com => #{classify_input("http://example.com")}"
203
+ # puts "https://www.google.com/search?q=ruby+regex => #{classify_input("https://www.google.com/search?q=ruby+regex")}"
204
+ # puts "ftp://user:pass@ftp.example.com/file.txt => #{classify_input("ftp://user:pass@ftp.example.com/file.txt")}"
205
+ # puts "file:///C:/Users/user/Documents/report.pdf => #{classify_input("file:///C:/Users/user/Documents/report.pdf")}"
206
+ # puts "www.example.com => #{classify_input("www.example.com")} (Expected: string, as it lacks a scheme for URI.regexp)"
207
+
208
+ # puts "\n--- Filepaths ---"
209
+ # puts "/usr/local/bin/myscript.sh => #{classify_input("/usr/local/bin/myscript.sh")}"
210
+ # puts "C:\\Program Files\\App\\config.ini => #{classify_input("C:\\Program Files\\App\\config.ini")}"
211
+ # puts "/home/user/documents/report.pdf => #{classify_input("/home/user/documents/report.pdf")}"
212
+ # puts "relative/path/to/file.txt => #{classify_input("relative/path/to/file.txt")}"
213
+ # puts "./current_dir_file.log => #{classify_input("./current_dir_file.log")}"
214
+ # puts "../parent_dir/other_file.csv => #{classify_input("../parent_dir/other_file.csv")}"
215
+ # puts "my_document.docx => #{classify_input("my_document.docx")}"
216
+ # puts "folder/another_folder/image.png => #{classify_input("folder/another_folder/image.png")}"
217
+ # puts "just_a_folder_name/ => #{classify_input("just_a_folder_name/")}"
218
+ # puts "root_dir/ => #{classify_input("root_dir/")}"
219
+ # puts "C:/ => #{classify_input("C:/")}"
220
+ # puts "/ => #{classify_input("/")}"
221
+ # puts "\\\\server\\share\\file.txt => #{classify_input("\\\\server\\share\\file.txt")}"
222
+ # puts "\\\\server\\share\\ => #{classify_input("\\\\server\\share\\")}"
223
+
224
+ # puts "\n--- Strings of Data (>= 94 characters) ---"
225
+ # long_data_string = "This is a very long string that serves as an example of generic data. It must be at least 94 characters long to be classified as 'string of data' by our function. This particular string is precisely 160 characters long. Ruby is fun!"
226
+ # puts "#{long_data_string.slice(0, 50)}... (length #{long_data_string.length}) => #{classify_input(long_data_string)}"
227
+
228
+ # long_data_94_chars = "a" * 94 # A string of exactly 94 'a' characters
229
+ # puts "#{long_data_94_chars.slice(0, 50)}... (length #{long_data_94_chars.length}) => #{classify_input(long_data_94_chars)}"
230
+
231
+ # puts "\n--- Regular Strings (short, ambiguous) ---"
232
+ # puts "Hello World => #{classify_input("Hello World")}"
233
+ # puts "short_string => #{classify_input("short_string")}"
234
+ # puts "a_simple_word => #{classify_input("a_simple_word")}"
235
+ # puts "12345 => #{classify_input("12345")}"
236
+ # puts "" => #{classify_input("")}" # Empty string
237
+ # puts "folder_name_only => #{classify_input("folder_name_only")}" # Not a path without separator or extension
238
+ # puts "not_a_path/but_looks_like_it_if_short_and_not_a_file_with_extension => #{classify_input("not_a_path/but_looks_like_it_if_short_and_not_a_file_with_extension")}" # This is still a filepath because of the / and multi-segments logic. This is correct behavior for a path-like string.
239
+ end
240
+ end
@@ -5,13 +5,17 @@ require 'digest'
5
5
  module Nacha
6
6
  module Formatter
7
7
  class Base
8
- attr_reader :records, :options
8
+ attr_reader :ach_file, :options
9
9
 
10
- def initialize(records, options = {})
11
- @records = records
10
+ def initialize(ach_file, options = {})
11
+ @ach_file = ach_file
12
12
  @options = options
13
13
  end
14
14
 
15
+ def records
16
+ @ach_file.records
17
+ end
18
+
15
19
  def format
16
20
  raise NotImplementedError, 'Subclasses must implement a format method'
17
21
  end
@@ -9,41 +9,11 @@ module Nacha
9
9
  def format
10
10
  output = {
11
11
  file: file_statistics,
12
- records: records.map { |record| record_to_h(record) }
12
+ records: records.map(&:to_h)
13
13
  }
14
14
 
15
15
  JSON.pretty_generate(output)
16
16
  end
17
-
18
- private
19
-
20
- def record_to_h(record)
21
- {
22
- nacha_record_type: record.record_type,
23
- metadata: {
24
- klass: record.class.name,
25
- errors: record.errors,
26
- line_number: record.line_number,
27
- original_input_line: record.original_input_line
28
- }
29
- }.merge(
30
- record.fields.keys.to_h do |key|
31
- [key, field_to_json_output(record.fields[key])]
32
- end
33
- )
34
- end
35
-
36
- def field_to_json_output(field)
37
- if field.json_output
38
- # rubocop:disable GitlabSecurity/PublicSend
39
- field.json_output.reduce(field.raw) do |memo, operation|
40
- memo&.public_send(*operation)
41
- end
42
- # rubocop:enable GitlabSecurity/PublicSend
43
- else
44
- field.to_s
45
- end
46
- end
47
17
  end
48
18
  end
49
19
  end
data/lib/nacha/parser.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'nacha'
4
4
  require 'nacha/parser_context'
5
+ require 'nacha/ach_file'
5
6
 
6
7
  # Nacha Parser - deal with figuring out what record type a line is
7
8
  class Nacha::Parser
@@ -18,9 +19,9 @@ class Nacha::Parser
18
19
  end
19
20
 
20
21
  def parse_file(file)
21
- @context.parser_started_at = Time.now.utc
22
- @context.file_name = file
23
- parse_string(file.read)
22
+ @context.parser_started_at = Time.now.utc
23
+ @context.file_name = file
24
+ parse_string(file.read)
24
25
  end
25
26
 
26
27
  def detect_possible_record_types(line)
@@ -30,6 +31,8 @@ class Nacha::Parser
30
31
  end
31
32
 
32
33
  def parse_string(str)
34
+ return [] unless str.is_a?(String) && !str.empty?
35
+
33
36
  line_num = -1
34
37
  records = []
35
38
  @context.parser_started_at ||= Time.now.utc
data/lib/nacha/version.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Nacha
4
4
  module Version
5
- STRING = '0.2.00'
5
+ STRING = '0.2.1'
6
6
  end
7
7
  VERSION = Version::STRING
8
8
  end
data/lib/nacha.rb CHANGED
@@ -10,6 +10,7 @@
10
10
  # end
11
11
 
12
12
  require 'yaml'
13
+ require 'nacha/ach_file'
13
14
  require 'nacha/version'
14
15
  require 'nacha/aba_number'
15
16
  require 'nacha/ach_date'
@@ -66,18 +67,14 @@ module Nacha
66
67
  types_hash
67
68
  end
68
69
 
69
- # Parses a NACHA file or string into a structured object representation.
70
+ # Parses a NACHA file, string or url into a structured object representation.
70
71
  #
71
- # @param object [String, File, IO] The input to parse, either a string containing
72
+ # @param object [String, File, IO, URL] The input to parse, either a string containing
72
73
  # NACHA data or an IO object (e.g., a File) representing the NACHA file.
73
- # @return [Nacha::Record::Base] The parsed NACHA file object.
74
+ # @return [Nacha::AchFile] The parsed NACHA file object.
74
75
  def parse(object)
75
- parser = Nacha::Parser.new
76
- if object.is_a?(String)
77
- parser.parse_string(object)
78
- else
79
- parser.parse_file(object)
80
- end
76
+ ach_file = AchFile.new(object)
77
+ ach_file.parse
81
78
  end
82
79
 
83
80
  # Converts a given string into a underscored, lowercase record name.
data/nacha.gemspec CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ["lib"]
30
30
  spec.add_dependency 'bigdecimal'
31
+ spec.add_dependency 'httparty'
31
32
 
32
33
  spec.add_development_dependency 'bundler'
33
34
  spec.add_development_dependency 'byebug' if RUBY_ENGINE == 'ruby'
@@ -40,6 +41,7 @@ Gem::Specification.new do |spec|
40
41
  spec.add_development_dependency 'rake'
41
42
  spec.add_development_dependency 'reek'
42
43
  spec.add_development_dependency 'rspec'
44
+ spec.add_development_dependency 'webmock'
43
45
  spec.add_development_dependency 'rubocop'
44
46
  spec.add_development_dependency 'rubocop-performance'
45
47
  spec.add_development_dependency 'rubocop-rspec'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nacha
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.00
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David H. Wilkins
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: httparty
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: bundler
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -163,6 +177,20 @@ dependencies:
163
177
  - - ">="
164
178
  - !ruby/object:Gem::Version
165
179
  version: '0'
180
+ - !ruby/object:Gem::Dependency
181
+ name: webmock
182
+ requirement: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ type: :development
188
+ prerelease: false
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
166
194
  - !ruby/object:Gem::Dependency
167
195
  name: rubocop
168
196
  requirement: !ruby/object:Gem::Requirement
@@ -278,6 +306,7 @@ files:
278
306
  - lib/nacha.rb
279
307
  - lib/nacha/aba_number.rb
280
308
  - lib/nacha/ach_date.rb
309
+ - lib/nacha/ach_file.rb
281
310
  - lib/nacha/field.rb
282
311
  - lib/nacha/formatter.rb
283
312
  - lib/nacha/formatter/base.rb