loi_parser 1.0.0

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 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: []