loi_parser 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3bd1b7478112fcb89c4e2dbe1488e0ba2ad80660a4961bd653fbe7e13808125d
4
+ data.tar.gz: c91e18d7a6eb9aba55ae689bb6e9755998157cbf720d3106713617e245fad7a7
5
+ SHA512:
6
+ metadata.gz: 4ac95c4499e291bcb0e18aea849423c06fe761bf482dc43954358bffb842704fae5f9ce07d671201a44344fcf8caa46d5766d8428587d1e462e6f349d34a5006
7
+ data.tar.gz: a01c90bd3eeb38575f7070e293c6ade131d46ed0b6ad04d55ccc05910589ab0ae263a8f2bbdc9fb32467457412776c7a442ebbb69bb9e9550b06b6e1cf0dd8e0
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ spec/fixtures/202002131019.loi
data/.gitlab-ci.yml ADDED
@@ -0,0 +1,9 @@
1
+ image: ruby:3.0.4
2
+
3
+ before_script:
4
+ - gem install bundler -v 2.2.6
5
+ - bundle install
6
+
7
+ build:
8
+ script:
9
+ - bundle exec rake
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,10 @@
1
+ require:
2
+ - rubocop-rspec
3
+ - rubocop-rake
4
+
5
+ AllCops:
6
+ NewCops: enable
7
+ TargetRubyVersion: 2.4
8
+
9
+ Layout/LineLength:
10
+ Max: 80
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [1.0.0] - 2023-03-13
10
+ ### Added
11
+ - Initial import
12
+
13
+ [Unreleased]: https://gitlab.com/pharmony/loi-parser/compare/v1.0.0...master
14
+ [1.0.0]: https://gitlab.com/hydrana/hydrana/tags/v1.0.0
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in loi-parser.gemspec
6
+ gemspec
7
+
8
+ gem 'rake', '~> 13.0'
9
+ gem 'rspec', '~> 3.0'
10
+ gem 'rubocop', '~> 1.7'
11
+ gem 'rubocop-rake', '~> 0.6', require: false
12
+ gem 'rubocop-rspec', '~> 2.19', require: false
13
+ gem 'rubyzip', '~> 2.3.2'
data/Gemfile.lock ADDED
@@ -0,0 +1,70 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ loi_parser (1.0.0)
5
+ logger
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ diff-lcs (1.5.0)
12
+ json (2.6.3)
13
+ logger (1.5.3)
14
+ parallel (1.22.1)
15
+ parser (3.2.1.1)
16
+ ast (~> 2.4.1)
17
+ rainbow (3.1.1)
18
+ rake (13.0.6)
19
+ regexp_parser (2.7.0)
20
+ rexml (3.2.5)
21
+ rspec (3.12.0)
22
+ rspec-core (~> 3.12.0)
23
+ rspec-expectations (~> 3.12.0)
24
+ rspec-mocks (~> 3.12.0)
25
+ rspec-core (3.12.1)
26
+ rspec-support (~> 3.12.0)
27
+ rspec-expectations (3.12.2)
28
+ diff-lcs (>= 1.2.0, < 2.0)
29
+ rspec-support (~> 3.12.0)
30
+ rspec-mocks (3.12.3)
31
+ diff-lcs (>= 1.2.0, < 2.0)
32
+ rspec-support (~> 3.12.0)
33
+ rspec-support (3.12.0)
34
+ rubocop (1.48.0)
35
+ json (~> 2.3)
36
+ parallel (~> 1.10)
37
+ parser (>= 3.2.0.0)
38
+ rainbow (>= 2.2.2, < 4.0)
39
+ regexp_parser (>= 1.8, < 3.0)
40
+ rexml (>= 3.2.5, < 4.0)
41
+ rubocop-ast (>= 1.26.0, < 2.0)
42
+ ruby-progressbar (~> 1.7)
43
+ unicode-display_width (>= 2.4.0, < 3.0)
44
+ rubocop-ast (1.27.0)
45
+ parser (>= 3.2.1.0)
46
+ rubocop-capybara (2.17.1)
47
+ rubocop (~> 1.41)
48
+ rubocop-rake (0.6.0)
49
+ rubocop (~> 1.0)
50
+ rubocop-rspec (2.19.0)
51
+ rubocop (~> 1.33)
52
+ rubocop-capybara (~> 2.17)
53
+ ruby-progressbar (1.13.0)
54
+ rubyzip (2.3.2)
55
+ unicode-display_width (2.4.2)
56
+
57
+ PLATFORMS
58
+ ruby
59
+
60
+ DEPENDENCIES
61
+ loi_parser!
62
+ rake (~> 13.0)
63
+ rspec (~> 3.0)
64
+ rubocop (~> 1.7)
65
+ rubocop-rake (~> 0.6)
66
+ rubocop-rspec (~> 2.19)
67
+ rubyzip (~> 2.3.2)
68
+
69
+ BUNDLED WITH
70
+ 2.1.4
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Pharmony
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # LOI Parser
2
+
3
+ This gem reads the `.loi` file format, parses its header and bitmap allowing you
4
+ to:
5
+ * retrieve the file's application, date, range, and version
6
+ * check if s serial number is in opposition
7
+ * grab all the serial numbers being marked in opposition
8
+
9
+ So far this gem only support "LOI" application files, not "dLOI" application
10
+ files.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'loi_parser'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ $ bundle install
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install loi_parser
27
+
28
+ ## Usage
29
+
30
+ ```ruby
31
+ > loi = LoiParser::Reader.new('/path/to/your/file.loi')
32
+
33
+ # Check the file header:
34
+ > loi.header
35
+ => {:length=>"0042", :application=>"LOI", :version=>"01", :date=>"20200213", :date_range=>"1019", :list_format=>"BTMP", :bitmap_size=>32000000}
36
+
37
+ # Checking a given serial number being in opposition
38
+ > loi.in_opposition?(99_999_999)
39
+ => true
40
+ > loi.in_opposition?(12_345_678)
41
+ => false
42
+
43
+ # Grabbing all the serial numbers being in opposition
44
+ > loi.serials_in_opposition
45
+ I, [2023-03-08T16:04:19.286193 #317] INFO -- LoiParser: (0%) current_sn: 255999999/256000000 (Elapsed: 0 seconds | Found: 0) ...
46
+ I, [2023-03-08T16:04:25.387373 #317] INFO -- LoiParser: (1%) current_sn: 253440000/256000000 (Elapsed: 6 seconds | Found: 0) ...
47
+ I, [2023-03-08T16:04:31.418921 #317] INFO -- LoiParser: (2%) current_sn: 250880000/256000000 (Elapsed: 12 seconds | Found: 0) ...
48
+ ...
49
+ I, [2023-03-08T11:02:13.052564 #99340] INFO -- LoiParser: (99%) current_sn: 2560000/256000000 ...
50
+ I, [2023-03-08T11:02:18.651785 #99340] INFO -- LoiParser: (100%) current_sn: 0/256000000 ...
51
+ I, [2023-03-08T11:02:18.651856 #99340] INFO -- LoiParser: Gathered 50 serial number(s) in opposition in 578.848939881
52
+ I, [2023-03-08T11:02:18.651881 #99340] INFO -- LoiParser: Serials in opposition: [99999999, ...]
53
+ => [99999999, ...]
54
+ ```
55
+
56
+ ## Development
57
+
58
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
59
+
60
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
61
+
62
+ ## Contributing
63
+
64
+ Bug reports and pull requests are welcome on GitHub at https://gitlab.com/pharmony/loi-parser.
65
+
66
+ ## License
67
+
68
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'loi_parser'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LoiParser
4
+ #
5
+ # Read about the Ruby's String#unpack method:
6
+ # https://ruby-doc.org/3.0.4/String.html#method-i-unpack
7
+ #
8
+ # Read about the Ruby's IO#seek method:
9
+ # https://ruby-doc.org/3.0.4/IO.html#method-i-seek
10
+ #
11
+ # Ruby Bitwise Operators
12
+ # https://medium.com/rubycademy/ruby-bitwise-operators-da57763fa368
13
+ #
14
+ class Reader
15
+ class InvalidLoiFile < StandardError; end
16
+
17
+ attr_reader :header, :path
18
+
19
+ HEADER_COLUMNS = [
20
+ { field: :length, length: 4 },
21
+ { field: :application, length: 20 },
22
+ { field: :version, length: 2 },
23
+ { field: :date, length: 8 },
24
+ { field: :date_range, length: 4 },
25
+ { field: :list_format, length: 4 },
26
+ { field: :bitmap_size, length: 8 }
27
+ ].freeze
28
+
29
+ def initialize(path)
30
+ @path = path
31
+
32
+ open_file!
33
+
34
+ @header = build_header
35
+ @list_start_position = @file.pos
36
+ end
37
+
38
+ def in_opposition?(serial_number)
39
+ unless serial_number.is_a?(Integer)
40
+ raise ArgumentError, 'serial_number must be an Integer'
41
+ end
42
+
43
+ # Reading the bitmap list
44
+ position = serial_number / 8
45
+
46
+ # When position is out of bitmap range, the serial number is in opposition
47
+ return true if position > header[:bitmap_size]
48
+
49
+ # Moves to the serial number position from the bitmap
50
+ @file.seek(position, IO::SEEK_CUR)
51
+
52
+ # Read the byte as binary
53
+ byte = @file.read(1).unpack1('B*')
54
+
55
+ # Checks for opposition
56
+ opposition = serial_in_opposition_from?(serial_number, byte)
57
+
58
+ # Moves back to the initial position
59
+ @file.seek(@list_start_position, IO::SEEK_SET)
60
+
61
+ opposition
62
+ end
63
+
64
+ def serials_in_opposition
65
+ # https://blog.dnsimple.com/2018/03/elapsed-time-with-ruby-the-right-way/
66
+ @starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
67
+
68
+ serial_numbers = find_all_serial_numbers_in_opposition
69
+
70
+ LoiParser.logger.info(
71
+ "Gathered #{serial_numbers.size} serial number(s) " \
72
+ "in opposition in #{elapsed_time_in_minutes} minutes."
73
+ )
74
+ LoiParser.logger.info "Serials in opposition: #{serial_numbers.inspect}"
75
+
76
+ serial_numbers
77
+ end
78
+
79
+ private
80
+
81
+ def serial_in_opposition_from?(serial_number, byte)
82
+ byte.to_i(2) & (1 << (serial_number % 8)) != 0
83
+ end
84
+
85
+ def build_header
86
+ header = read_header_from_file
87
+
88
+ header[:application] = header[:application].strip
89
+ header[:bitmap_size] = header[:bitmap_size].to_i
90
+
91
+ raise InvalidLoiFile unless %w[LOI dLOI].include?(header[:application])
92
+
93
+ header
94
+ end
95
+
96
+ def current_byte_pos
97
+ @file.pos - @list_start_position - 1
98
+ end
99
+
100
+ def elapsed_time_in_minutes
101
+ # https://blog.dnsimple.com/2018/03/elapsed-time-with-ruby-the-right-way/
102
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @starting
103
+
104
+ if elapsed <= 60
105
+ "#{elapsed.to_i} seconds"
106
+ else
107
+ min, sec = elapsed.divmod(60)
108
+
109
+ # https://stackoverflow.com/a/33684387/1996540
110
+ "#{min} minutes #{sec.to_i} seconds"
111
+ end
112
+ end
113
+
114
+ def extract_serial_mumbers_from(byte)
115
+ byte.each_char.with_index(1).map do |bit, index|
116
+ next if bit == '0'
117
+
118
+ ((current_byte_pos * 8) + (8 - index))
119
+ end.compact
120
+ end
121
+
122
+ def find_all_serial_numbers_in_opposition
123
+ serial_numbers = []
124
+
125
+ until (readed = @file.read(1)).nil?
126
+ byte = readed.unpack1('B*')
127
+
128
+ print_percentage(serial_numbers.size)
129
+
130
+ next if byte == '00000000'
131
+
132
+ serial_numbers |= extract_serial_mumbers_from(byte)
133
+ end
134
+
135
+ serial_numbers
136
+ end
137
+
138
+ def open_file!
139
+ unless File.exist?(@path)
140
+ raise "#{self.class} ERROR: #{@path} doesn't exist."
141
+ end
142
+
143
+ @file = File.open(@path)
144
+ end
145
+
146
+ def print_percentage(found)
147
+ percentage = (@file.pos * 100) / header[:bitmap_size]
148
+
149
+ return if percentage == @current_percentage
150
+
151
+ LoiParser.logger.info(
152
+ "(#{percentage}%) pos: #{@file.pos} (Elapsed: " \
153
+ "#{elapsed_time_in_minutes} | Found: #{found}) ..."
154
+ )
155
+
156
+ @current_percentage = percentage
157
+ end
158
+
159
+ def read_header_from_file
160
+ HEADER_COLUMNS.each_with_object({}) do |column, acc|
161
+ acc[column[:field]] = @file.read(column[:length]).unpack1('M')
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LoiParser
4
+ VERSION = '1.0.0'
5
+ end
data/lib/loi_parser.rb ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ require_relative 'loi_parser/reader'
6
+ require_relative 'loi_parser/version'
7
+
8
+ module LoiParser # rubocop:disable Style/Documentation
9
+ class << self
10
+ attr_writer :logger
11
+
12
+ def logger
13
+ @logger ||= Logger.new($stdout).tap do |log|
14
+ log.progname = name
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/loi_parser/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'loi_parser'
7
+ spec.version = LoiParser::VERSION
8
+ spec.authors = ['Pharmony']
9
+ spec.email = ['dev@pharmony.eu']
10
+
11
+ spec.summary = 'LOI (Liste d’Opposition Incrémentale) file reader'
12
+ spec.description = 'This gem parses the `.loi` file format and allows ' \
13
+ 'to check if a given National Insurance Number card ' \
14
+ "serial number is marked as 'in opposition', or to " \
15
+ 'extract all the serial numbers in opposition.'
16
+ spec.homepage = 'https://gitlab.com/pharmony/loi-parser'
17
+ spec.license = 'MIT'
18
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
19
+
20
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
21
+ spec.metadata['rubygems_mfa_required'] = 'true'
22
+
23
+ spec.metadata['homepage_uri'] = spec.homepage
24
+ spec.metadata['source_code_uri'] = spec.homepage
25
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/-/blob/master/CHANGELOG.md"
26
+
27
+ # Specify which files should be added to the gem when it is released.
28
+ # The `git ls-files -z` loads the files in the RubyGem that have been added
29
+ # into git.
30
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
31
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:spec)/}) }
32
+ end
33
+ spec.bindir = 'exe'
34
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
35
+ spec.require_paths = ['lib']
36
+
37
+ spec.add_runtime_dependency 'logger'
38
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: loi_parser
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Pharmony
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-03-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logger
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: This gem parses the `.loi` file format and allows to check if a given
28
+ National Insurance Number card serial number is marked as 'in opposition', or to
29
+ extract all the serial numbers in opposition.
30
+ email:
31
+ - dev@pharmony.eu
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".gitignore"
37
+ - ".gitlab-ci.yml"
38
+ - ".rspec"
39
+ - ".rubocop.yml"
40
+ - CHANGELOG.md
41
+ - Gemfile
42
+ - Gemfile.lock
43
+ - LICENSE.txt
44
+ - README.md
45
+ - Rakefile
46
+ - bin/console
47
+ - bin/setup
48
+ - lib/loi_parser.rb
49
+ - lib/loi_parser/reader.rb
50
+ - lib/loi_parser/version.rb
51
+ - loi_parser.gemspec
52
+ homepage: https://gitlab.com/pharmony/loi-parser
53
+ licenses:
54
+ - MIT
55
+ metadata:
56
+ allowed_push_host: https://rubygems.org
57
+ rubygems_mfa_required: 'true'
58
+ homepage_uri: https://gitlab.com/pharmony/loi-parser
59
+ source_code_uri: https://gitlab.com/pharmony/loi-parser
60
+ changelog_uri: https://gitlab.com/pharmony/loi-parser/-/blob/master/CHANGELOG.md
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 2.4.0
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubygems_version: 3.1.4
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: LOI (Liste d’Opposition Incrémentale) file reader
80
+ test_files: []