adt-driver 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b733d56e30d0ac25c4c388185f2103520fc2e0d6681ffca13507004b9b7e6f3d
4
+ data.tar.gz: 80195deaa7aec3560808ecc4e10e9b90897822da9dce6911b1327f44de52cb43
5
+ SHA512:
6
+ metadata.gz: 0607be88df8a7902d6bc348d5daff6195c3c0a70ab15ab133d7b26fca61350d4266d393189642c24653bb772b0320405ec5a048ce04d30a39c5f85e874a106b3
7
+ data.tar.gz: 57ac5faeb9c95777898bb07463c7e7afea7d5802f830ca2dc5afff88a25fa38007d197019c2d17587d12a2040f15dba5da4e534c760cf4d5601ab86105a29581
@@ -0,0 +1,11 @@
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
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,143 @@
1
+ ---
2
+ Layout/CaseIndentation:
3
+ Enabled: false
4
+ Layout/ElseAlignment:
5
+ Enabled: false
6
+ Layout/EmptyLinesAroundAttributeAccessor:
7
+ Enabled: true
8
+ Layout/EmptyLinesAroundBlockBody:
9
+ Enabled: false
10
+ Layout/EndAlignment:
11
+ Enabled: true
12
+ EnforcedStyleAlignWith: start_of_line
13
+ Layout/FirstArrayElementIndentation:
14
+ EnforcedStyle: consistent
15
+ Layout/FirstHashElementIndentation:
16
+ EnforcedStyle: consistent
17
+ Layout/LineLength:
18
+ Enabled: false
19
+ Layout/MultilineHashBraceLayout:
20
+ Enabled: true
21
+ EnforcedStyle: symmetrical
22
+ Layout/MultilineMethodCallIndentation:
23
+ EnforcedStyle: indented
24
+ Layout/MultilineOperationIndentation:
25
+ EnforcedStyle: indented
26
+ Layout/ParameterAlignment:
27
+ Enabled: true
28
+ EnforcedStyle: with_fixed_indentation
29
+ Layout/SpaceAroundMethodCallOperator:
30
+ Enabled: true
31
+ Layout/SpaceInLambdaLiteral:
32
+ EnforcedStyle: require_space
33
+ Layout/SpaceInsideHashLiteralBraces:
34
+ EnforcedStyle: no_space
35
+ Layout/TrailingWhitespace:
36
+ Enabled: false
37
+ Lint/AmbiguousBlockAssociation:
38
+ Enabled: false
39
+ Lint/AssignmentInCondition:
40
+ Enabled: false
41
+ Lint/DeprecatedOpenSSLConstant:
42
+ Enabled: false
43
+ Lint/MixedRegexpCaptureTypes:
44
+ Enabled: false
45
+ Lint/RaiseException:
46
+ Enabled: false
47
+ Lint/RedundantSplatExpansion:
48
+ Enabled: false
49
+ Lint/StructNewOverride:
50
+ Enabled: false
51
+ Metrics/AbcSize:
52
+ Enabled: false
53
+ Metrics/BlockLength:
54
+ CountComments: false
55
+ Enabled: true
56
+ Exclude:
57
+ - spec/**/*_spec.rb
58
+ Max: 100
59
+ Metrics/ClassLength:
60
+ Enabled: false
61
+ Metrics/CyclomaticComplexity:
62
+ Max: 10
63
+ Metrics/MethodLength:
64
+ Enabled: false
65
+ Metrics/ModuleLength:
66
+ Enabled: false
67
+ Metrics/PerceivedComplexity:
68
+ Max: 10
69
+ Naming/FileName:
70
+ Enabled: false
71
+ Naming/MethodParameterName:
72
+ Enabled: false
73
+ RSpec/AnyInstance:
74
+ Enabled: false
75
+ RSpec/DescribeClass:
76
+ Exclude:
77
+ - spec/requests/**/*_spec.rb
78
+ RSpec/DescribeMethod:
79
+ Enabled: false
80
+ RSpec/DescribedClass:
81
+ Enabled: false
82
+ RSpec/ExampleLength:
83
+ Exclude:
84
+ - spec/workers/**/*_spec.rb
85
+ Max: 25
86
+ RSpec/ImplicitSubject:
87
+ Enabled: false
88
+ RSpec/LetSetup:
89
+ Enabled: false
90
+ RSpec/MessageSpies:
91
+ Enabled: false
92
+ RSpec/MultipleExpectations:
93
+ Enabled: false
94
+ RSpec/NestedGroups:
95
+ Max: 5
96
+ RSpec/NotToNot:
97
+ EnforcedStyle: to_not
98
+ Security/YAMLLoad:
99
+ Exclude:
100
+ - app/models/entity.rb
101
+ Style/Alias:
102
+ Enabled: false
103
+ Style/AsciiComments:
104
+ Enabled: false
105
+ Style/ClassAndModuleChildren:
106
+ Enabled: false
107
+ Style/Documentation:
108
+ Enabled: false
109
+ Style/EmptyMethod:
110
+ EnforcedStyle: expanded
111
+ Style/ExponentialNotation:
112
+ Enabled: true
113
+ Style/FormatStringToken:
114
+ Enabled: false
115
+ Style/FrozenStringLiteralComment:
116
+ Enabled: false
117
+ Style/HashEachMethods:
118
+ Enabled: true
119
+ Style/HashTransformKeys:
120
+ Enabled: true
121
+ Style/HashTransformValues:
122
+ Enabled: true
123
+ Style/Lambda:
124
+ Enabled: false
125
+ Style/NumericPredicate:
126
+ Enabled: false
127
+ Style/PerlBackrefs:
128
+ Enabled: false
129
+ Style/RedundantRegexpCharacterClass:
130
+ Enabled: true
131
+ Style/RedundantRegexpEscape:
132
+ Enabled: true
133
+ Style/RescueModifier:
134
+ Enabled: false
135
+ Style/SafeNavigation:
136
+ Enabled: false
137
+ Style/SlicingWithRange:
138
+ Enabled: true
139
+ Style/SymbolArray:
140
+ EnforcedStyle: brackets
141
+ require:
142
+ - rubocop-rspec
143
+ - rubocop-performance
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.0
6
+ before_install: gem install bundler -v 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ gemspec
2
+ source 'https://rubygems.org'
3
+
4
+ group :development, :test do
5
+ gem 'awesome_print'
6
+ gem 'byebug'
7
+ gem 'guard'
8
+ gem 'guard-rspec'
9
+ gem 'irb'
10
+ gem 'rake'
11
+ gem 'rspec'
12
+ gem 'rubocop'
13
+ gem 'rubocop-performance'
14
+ gem 'rubocop-rspec'
15
+ end
@@ -0,0 +1,107 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ adt-driver (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.1)
10
+ awesome_print (1.8.0)
11
+ byebug (11.1.3)
12
+ coderay (1.1.3)
13
+ diff-lcs (1.3)
14
+ ffi (1.13.1)
15
+ formatador (0.2.5)
16
+ guard (2.16.2)
17
+ formatador (>= 0.2.4)
18
+ listen (>= 2.7, < 4.0)
19
+ lumberjack (>= 1.0.12, < 2.0)
20
+ nenv (~> 0.1)
21
+ notiffany (~> 0.0)
22
+ pry (>= 0.9.12)
23
+ shellany (~> 0.0)
24
+ thor (>= 0.18.1)
25
+ guard-compat (1.2.1)
26
+ guard-rspec (4.7.3)
27
+ guard (~> 2.1)
28
+ guard-compat (~> 1.1)
29
+ rspec (>= 2.99.0, < 4.0)
30
+ io-console (0.5.6)
31
+ irb (1.2.4)
32
+ reline (>= 0.0.1)
33
+ listen (3.2.1)
34
+ rb-fsevent (~> 0.10, >= 0.10.3)
35
+ rb-inotify (~> 0.9, >= 0.9.10)
36
+ lumberjack (1.2.5)
37
+ method_source (1.0.0)
38
+ nenv (0.3.0)
39
+ notiffany (0.1.3)
40
+ nenv (~> 0.1)
41
+ shellany (~> 0.0)
42
+ parallel (1.19.1)
43
+ parser (2.7.1.3)
44
+ ast (~> 2.4.0)
45
+ pry (0.13.1)
46
+ coderay (~> 1.1)
47
+ method_source (~> 1.0)
48
+ rainbow (3.0.0)
49
+ rake (12.3.3)
50
+ rb-fsevent (0.10.4)
51
+ rb-inotify (0.10.1)
52
+ ffi (~> 1.0)
53
+ regexp_parser (1.7.1)
54
+ reline (0.1.4)
55
+ io-console (~> 0.5)
56
+ rexml (3.2.4)
57
+ rspec (3.9.0)
58
+ rspec-core (~> 3.9.0)
59
+ rspec-expectations (~> 3.9.0)
60
+ rspec-mocks (~> 3.9.0)
61
+ rspec-core (3.9.2)
62
+ rspec-support (~> 3.9.3)
63
+ rspec-expectations (3.9.2)
64
+ diff-lcs (>= 1.2.0, < 2.0)
65
+ rspec-support (~> 3.9.0)
66
+ rspec-mocks (3.9.1)
67
+ diff-lcs (>= 1.2.0, < 2.0)
68
+ rspec-support (~> 3.9.0)
69
+ rspec-support (3.9.3)
70
+ rubocop (0.85.1)
71
+ parallel (~> 1.10)
72
+ parser (>= 2.7.0.1)
73
+ rainbow (>= 2.2.2, < 4.0)
74
+ regexp_parser (>= 1.7)
75
+ rexml
76
+ rubocop-ast (>= 0.0.3)
77
+ ruby-progressbar (~> 1.7)
78
+ unicode-display_width (>= 1.4.0, < 2.0)
79
+ rubocop-ast (0.0.3)
80
+ parser (>= 2.7.0.1)
81
+ rubocop-performance (1.6.1)
82
+ rubocop (>= 0.71.0)
83
+ rubocop-rspec (1.40.0)
84
+ rubocop (>= 0.68.1)
85
+ ruby-progressbar (1.10.1)
86
+ shellany (0.0.1)
87
+ thor (1.0.1)
88
+ unicode-display_width (1.7.0)
89
+
90
+ PLATFORMS
91
+ ruby
92
+
93
+ DEPENDENCIES
94
+ adt-driver!
95
+ awesome_print
96
+ byebug
97
+ guard
98
+ guard-rspec
99
+ irb
100
+ rake
101
+ rspec
102
+ rubocop
103
+ rubocop-performance
104
+ rubocop-rspec
105
+
106
+ BUNDLED WITH
107
+ 2.1.4
@@ -0,0 +1,36 @@
1
+ # Adt::Driver
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/adt/driver`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'adt-driver'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install adt-driver
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ 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.
30
+
31
+ 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/adt-driver.
36
+
@@ -0,0 +1,8 @@
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
+ task default: :spec
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/adt/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'adt-driver'
7
+ spec.version = ADT::VERSION
8
+ spec.authors = ['Sven Clement']
9
+ spec.email = ['clement@clement-weyer.lu']
10
+
11
+ spec.summary = 'ADT is a small Ruby library for reading Advantage Database Server ADT files'
12
+ spec.homepage = 'https://github.com/svnee/adt-driver'
13
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
14
+
15
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/svnee/adt-driver'
19
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+ end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'adt'
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 'pry'
15
+ Pry.start
@@ -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,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require 'date'
5
+ require 'json'
6
+ require 'forwardable'
7
+ require 'stringio'
8
+
9
+ module ADT
10
+ class Error < StandardError; end
11
+ # Your code goes here...
12
+ end
13
+
14
+ require 'adt/version'
15
+ require 'adt/schema'
16
+ require 'adt/rails'
17
+ require 'adt/table'
18
+ require 'adt/header'
19
+ require 'adt/column_type'
20
+ require 'adt/column'
21
+ require 'adt/record'
@@ -0,0 +1,101 @@
1
+ module ADT
2
+ class Column
3
+ extend Forwardable
4
+
5
+ class LengthError < ADT::Error; end
6
+ class NameError < ADT::Error; end
7
+
8
+ attr_reader :name, :type, :length
9
+
10
+ def_delegator :type_cast_class, :type_cast
11
+ # in use for SAGE BOB50
12
+ # [1, 3, 4, 6, 10, 11, 12, 14, 15, 17]
13
+ TYPE_CAST_CLASS = {
14
+ 1 => ADT::ColumnType::Boolean,
15
+ 3 => ADT::ColumnType::Date,
16
+ 4 => ADT::ColumnType::String,
17
+ 6 => ADT::ColumnType::Binary,
18
+ 10 => ADT::ColumnType::Double,
19
+ 11 => ADT::ColumnType::Integer,
20
+ 12 => ADT::ColumnType::ShortInteger,
21
+ 14 => ADT::ColumnType::DateTime,
22
+ 15 => ADT::ColumnType::Integer, # In reality an "autoinc" but we don't deal with that
23
+ 17 => ADT::ColumnType::CurDouble
24
+ }.freeze
25
+ TYPE_CAST_CLASS.default = ADT::ColumnType::String
26
+ TYPE_CAST_CLASS.freeze
27
+
28
+ # Initialize a new ADT::Column
29
+ #
30
+ # @param [String] name
31
+ # @param [String] type
32
+ # @param [Integer] length
33
+ def initialize(name, type, length)
34
+ @name = clean(name)
35
+ @type = type
36
+ @length = length
37
+
38
+ validate_length
39
+ validate_name
40
+ end
41
+
42
+ # Return the flag to decode the data
43
+ #
44
+ # @param [Integer] length
45
+ def flag(length = 0)
46
+ flag = type_cast_class.flag
47
+ return flag + length.to_s if flag.eql? 'A'
48
+
49
+ flag
50
+ end
51
+
52
+ # Returns a Hash with :name, :type, :length, and :decimal keys
53
+ #
54
+ # @return [Hash]
55
+ def to_hash
56
+ {name: name, type: type, length: length}
57
+ end
58
+
59
+ def downcase
60
+ name.downcase
61
+ end
62
+
63
+ # Underscored name
64
+ #
65
+ # This is the column name converted to underscore format.
66
+ # For example, MyColumn will be returned as my_column.
67
+ #
68
+ # @return [String]
69
+ def underscored_name
70
+ @underscored_name ||= begin
71
+ name.gsub(/::/, '/')
72
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
73
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
74
+ .tr('-', '_')
75
+ .downcase
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def clean(value) # :nodoc:
82
+ truncated_value = value.strip.partition("\x00").first
83
+ truncated_value.gsub(/[^\x20-\x7E]/, '')
84
+ end
85
+
86
+ def validate_length # :nodoc:
87
+ raise LengthError, 'field length must be 0 or greater' if length < 0
88
+ end
89
+
90
+ def validate_name # :nodoc:
91
+ raise NameError, 'column name cannot be empty' if @name.empty?
92
+ end
93
+
94
+ def type_cast_class # :nodoc:
95
+ @type_cast_class ||= begin
96
+ klass = @length == 0 ? ColumnType::Nil : TYPE_CAST_CLASS[type]
97
+ klass.new
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,130 @@
1
+ module ADT
2
+ module ColumnType
3
+ class MemoryError < ADT::Error; end
4
+
5
+ class Base; end
6
+
7
+ class Nil < Base
8
+ def type_cast(_value, _table)
9
+ nil
10
+ end
11
+
12
+ def flag
13
+ ''
14
+ end
15
+ end
16
+
17
+ class Boolean < Base
18
+ def type_cast(value, _table)
19
+ value.strip.match?(/^(y|t)$/i)
20
+ end
21
+
22
+ def flag
23
+ ''
24
+ end
25
+ end
26
+
27
+ class Binary < Base
28
+ def type_cast(value, table)
29
+ raise MemoryError, '.adm file is missing' unless table.memory?
30
+
31
+ offset, length = value.unpack('I*')
32
+ table.memory.seek(offset * 8)
33
+ table.memory.read(length)
34
+ end
35
+
36
+ def flag
37
+ ''
38
+ end
39
+ end
40
+
41
+ class Double < Base
42
+ def type_cast(value, _table)
43
+ value.unpack1('D') <= 0.001 ? 0.0 : value.unpack1('D')
44
+ end
45
+
46
+ def flag
47
+ 'D'
48
+ end
49
+ end
50
+
51
+ class Integer < Base
52
+ def type_cast(value, _table)
53
+ return nil if value.strip.empty?
54
+
55
+ value.unpack('L').dig(0).to_i == 2_147_483_648 ? nil : value.unpack('L').dig(0).to_i
56
+ end
57
+
58
+ def flag
59
+ 'i'
60
+ end
61
+ end
62
+
63
+ class ShortInteger < Base
64
+ def type_cast(value, _table)
65
+ return nil if value.strip.empty?
66
+
67
+ value.unpack('S_').dig(0).to_i == 2_147_483_648 ? nil : value.unpack('S_').dig(0).to_i
68
+ end
69
+
70
+ def flag
71
+ 'i'
72
+ end
73
+ end
74
+
75
+ class Date < Base
76
+ def type_cast(value, _table)
77
+ int = value.unpack('L').dig(0).to_i
78
+ int == 0 ? nil : ::Date.jd(int)
79
+ rescue StandardError
80
+ nil
81
+ end
82
+
83
+ def flag
84
+ '?'
85
+ end
86
+ end
87
+
88
+ class DateTime < Base
89
+ def type_cast(value, _table)
90
+ val = value.unpack('L L')
91
+ date = ::Date.jd(val.dig(0))
92
+ ::Time.at(date.to_time + val.dig(1) / 1000).to_datetime
93
+ end
94
+
95
+ def flag
96
+ '?'
97
+ end
98
+ end
99
+
100
+ class CurDouble < Base
101
+ def type_cast(value, _table)
102
+ value.unpack('D').dig(0) < 0.001 ? nil : value.unpack('D').dig(0)
103
+ end
104
+
105
+ def flag
106
+ 'D'
107
+ end
108
+ end
109
+
110
+ class String < Base
111
+ def type_cast(value, _table)
112
+ value = value.strip
113
+ value.force_encoding('UTF-8').encode('UTF-8', undef: :replace, invalid: :replace)
114
+ end
115
+
116
+ def flag
117
+ 'A'
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ # TYPES = {17 => 'curdouble', 4 => 'character', 10 => 'double', 11 => 'integer', 12 => 'short', 20 => 'cicharacter', 3 => 'date', 13 => 'time', 14 => 'timestamp', 15 => 'autoinc'}
124
+ # FLAGS = {'character' => 'A', 'double' => 'D', 'integer' => 'i', 'short' => 'S', 'cicharacter' => 'A', 'date' => '?', 'time' => '?', 'timestamp' => '?', 'autoinc' => 'I'}
125
+ # in use for SAGE BOB50
126
+ # [1, 3, 4, 6, 10, 11, 12, 14, 15, 17]
127
+ # 6 => Binary
128
+ # 10 => Double
129
+ # 12 => Shortint
130
+ # 15 => autoinc
@@ -0,0 +1,18 @@
1
+ module ADT
2
+ class Header
3
+ attr_reader :record_count
4
+ attr_reader :data_offset
5
+ attr_reader :record_length
6
+ attr_reader :column_count
7
+
8
+ def initialize(data)
9
+ @data = data
10
+ @record_count, @data_offset, @record_length = unpack_header
11
+ @column_count = (@data_offset - 400) / 200
12
+ end
13
+
14
+ def unpack_header
15
+ @data.unpack('@24 I x4 I I')
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ module ADT
2
+ # The Model module is mixin for the Table class
3
+ # It provides generators for imported tables
4
+ module Rails
5
+ def model
6
+ "class #{camelize(name)} < ApplicationRecord
7
+ self.table_name = '#{name}'
8
+ end"
9
+ end
10
+
11
+ private
12
+
13
+ def camelize(string, uppercase_first_letter = true)
14
+ string = if uppercase_first_letter
15
+ string.sub(/^[a-z\d]*/, &:capitalize)
16
+ else
17
+ string.sub(/^(?:(?=\b|[A-Z_])|\w)/, &:downcase)
18
+ end
19
+ string.gsub(%r{(?:_|(/))([a-z\d]*)}) { "#{$1}#{$2.capitalize}" }.gsub('/', '::')
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,88 @@
1
+ module ADT
2
+ # An instance of ADT::Record represents a row in the ADT file
3
+ class Record
4
+ # Initialize a new ADT::Record
5
+ #
6
+ # @data [String, StringIO] data
7
+ # @columns [Column]
8
+ def initialize(data, columns, table)
9
+ @data = ::StringIO.new(data)
10
+ @data.seek(4) # We don't know what the first 4 bytes are
11
+ @table = table
12
+ @columns = columns
13
+ end
14
+
15
+ # Record attributes
16
+ #
17
+ # @return [Hash]
18
+ def attributes
19
+ @attributes ||= Hash[attribute_map]
20
+ end
21
+
22
+ # Maps a row to an array of values
23
+ #
24
+ # @return [Array]
25
+ def to_a
26
+ @columns.map { |column| attributes[column.name] }
27
+ end
28
+
29
+ # Do all search parameters match?
30
+ #
31
+ # @param [Hash] options
32
+ # @return [Boolean]
33
+ def match?(options)
34
+ options.all? { |key, value| self[key] == value }
35
+ end
36
+
37
+ # Reads attributes by column name
38
+ #
39
+ # @param [String, Symbol] key
40
+ def [](name)
41
+ key = name.to_s
42
+ if attributes.key?(key)
43
+ attributes[key]
44
+ elsif index = underscored_column_names.index(key)
45
+ attributes[@columns[index].name]
46
+ end
47
+ end
48
+
49
+ # Equality
50
+ #
51
+ # @param [ADT::Record] other
52
+ # @return [Boolean]
53
+ def ==(other)
54
+ other.respond_to?(:attributes) && other.attributes == attributes
55
+ end
56
+
57
+ private
58
+
59
+ def attribute_map # :nodoc:
60
+ @columns.map { |column| [column.name, init_attribute(column)] }
61
+ end
62
+
63
+ def get_data(column) # :nodoc:
64
+ @data.read(column.length)
65
+ end
66
+
67
+ def init_attribute(column) # :nodoc:
68
+ value = get_data(column)
69
+ column.type_cast(value, @table)
70
+ end
71
+
72
+ def method_missing(method, *args) # :nodoc:
73
+ if (index = underscored_column_names.index(method.to_s))
74
+ attributes[@columns[index].name]
75
+ else
76
+ super
77
+ end
78
+ end
79
+
80
+ def respond_to_missing?(method, *) # :nodoc:
81
+ underscored_column_names.include?(method.to_s) || super
82
+ end
83
+
84
+ def underscored_column_names # :nodoc:
85
+ @underscored_column_names ||= @columns.map(&:underscored_name)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,87 @@
1
+ module ADT
2
+ # The Schema module is mixin for the Table class
3
+ module Schema
4
+ FORMATS = [:activerecord, :json].freeze
5
+
6
+ OTHER_DATA_TYPES = {
7
+ 1 => ':boolean',
8
+ 3 => ':date',
9
+ 6 => ':text',
10
+ 10 => ':decimal, precision: 6',
11
+ 11 => ':integer',
12
+ 12 => ':integer',
13
+ 14 => ':datetime',
14
+ 15 => ':integer', # In reality an "autoinc" but we don't deal with that
15
+ 17 => ':decimal, precision: 6'
16
+ }.freeze
17
+
18
+ # Generate an ActiveRecord::Schema
19
+ #
20
+ # ADT data types are converted to generic types as follows:
21
+ # - Number columns with no decimals are converted to :integer
22
+ # - Number columns with decimals are converted to :decimal
23
+ # - Date columns are converted to :date
24
+ # - DateTime columns are converted to :datetime
25
+ # - Logical columns are converted to :boolean
26
+ # - Binary columns are converted to :text
27
+ # - Character columns are converted to :string and the :limit option is set
28
+ # to the length of the character column
29
+ #
30
+ # Example:
31
+ # create_table "mydata" do |t|
32
+ # t.column :name, :string, :limit => 30
33
+ # t.column :last_update, :datetime
34
+ # t.column :is_active, :boolean
35
+ # t.column :age, :integer
36
+ # t.column :notes, :text
37
+ # end
38
+ #
39
+ # @param [Symbol] format Valid options are :activerecord and :json
40
+ # @return [String]
41
+ def schema(format = :activerecord, table_only = false)
42
+ schema_method_name = schema_name(format)
43
+ send(schema_method_name, table_only)
44
+ rescue NameError
45
+ raise ArgumentError, ":#{format} is not a valid schema. Valid schemas are: #{FORMATS.join(', ')}."
46
+ end
47
+
48
+ def schema_name(format) # :nodoc:
49
+ "#{format}_schema"
50
+ end
51
+
52
+ def activerecord_schema(_table_only = false) # :nodoc:
53
+ s = "ActiveRecord::Schema.define do\n"
54
+ s << " create_table \"#{name}\" #{'{id: false}' if columns.map(&:downcase).include?('id')} do |t|\n"
55
+ columns.each do |column|
56
+ s << " t.column #{activerecord_schema_definition(column)}"
57
+ end
58
+ s << " end\nend"
59
+ s
60
+ end
61
+
62
+ def json_schema(_table_only = false) # :nodoc:
63
+ columns.map(&:to_hash).to_json
64
+ end
65
+
66
+ # ActiveRecord schema definition
67
+ #
68
+ # @param [DBF::Column]
69
+ # @return [String]
70
+ def activerecord_schema_definition(column)
71
+ "\"#{column.underscored_name}\", #{schema_data_type(column)}\n"
72
+ end
73
+
74
+ def schema_data_type(column) # :nodoc:
75
+ case column.type
76
+ when *%w[1 3 6 10 11 12 14 15 17]
77
+ OTHER_DATA_TYPES[column.type]
78
+ else
79
+ string_data_format(column)
80
+ end
81
+ end
82
+
83
+ def string_data_format(column)
84
+ ":string, :limit => #{column.length}"
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,142 @@
1
+ module ADT
2
+ class FileNotFoundError < StandardError
3
+ end
4
+
5
+ # ADT::Table is the primary interface to a single ADT file and provides
6
+ # methods for enumerating and searching the records.
7
+ class Table
8
+ extend Forwardable
9
+ include ::ADT::Schema
10
+ include ::ADT::Rails
11
+
12
+ ADT_HEADER_SIZE = 400
13
+
14
+ def_delegator :header, :record_count
15
+ def_delegator :header, :data_offset
16
+ def_delegator :header, :record_length
17
+ def_delegator :header, :column_count
18
+
19
+ attr_reader :memory
20
+
21
+ # Opens a ADT::Table
22
+ # Examples:
23
+ # # working with a file stored on the filesystem
24
+ # table = ADT::Table.new 'data.adt'
25
+ #
26
+ # @param [String] data Path to the adt file
27
+ def initialize(data)
28
+ @data = open_data(data)
29
+ @memory = open_data(data.gsub('.adt', '.adm')) if File.exist?(data.gsub('.adt', '.adm'))
30
+ yield self if block_given?
31
+ end
32
+
33
+ # Closes the table
34
+ #
35
+ # @return [TrueClass, FalseClass]
36
+ def close
37
+ @data.close
38
+ end
39
+
40
+ # @return [TrueClass, FalseClass]
41
+ def memory?
42
+ !@memory.nil?
43
+ end
44
+
45
+ # @return [TrueClass, FalseClass]
46
+ def closed?
47
+ @data.closed?
48
+ end
49
+
50
+ # Retrieve a record by index number.
51
+ # The record will be nil if it has been deleted, but not yet pruned from
52
+ # the database.
53
+ #
54
+ # @param [Integer] index
55
+ # @return [ADT::Record, NilClass]
56
+ def record(index)
57
+ seek_to_record(index)
58
+ return nil if deleted_record?
59
+
60
+ ADT::Record.new(@data.read(record_length), columns, self)
61
+ end
62
+
63
+ # All columns
64
+ #
65
+ # @return [Array]
66
+ def columns
67
+ @columns ||= build_columns
68
+ end
69
+
70
+ # Column names
71
+ #
72
+ # @return [String]
73
+ def column_names
74
+ columns.map(&:name)
75
+ end
76
+
77
+ # @return [String]
78
+ def filename
79
+ return unless @data.respond_to?(:path)
80
+
81
+ File.basename(@data.path)
82
+ end
83
+
84
+ attr_reader :data
85
+
86
+ def name
87
+ filename.gsub('.adt', '')
88
+ end
89
+
90
+ private
91
+
92
+ def build_columns # :nodoc:
93
+ safe_seek do
94
+ # skip past header to get to column information
95
+ @data.seek(ADT_HEADER_SIZE)
96
+ # column names are the first 128 bytes and column info takes up the last 72 bytes.
97
+ # byte 130 contains a 16-bit column type
98
+ # byte 136 contains a 16-bit length field
99
+ @columns = []
100
+ column_count.times do
101
+ name, type, length = @data.read(200).unpack('A128 x S x4 S')
102
+ @columns << ADT::Column.new(name.strip, type, length) if length > 0
103
+ end
104
+ # Reset the column count in case any were skipped
105
+ @column_count = @columns.size
106
+
107
+ @columns
108
+ end
109
+ end
110
+
111
+ def deleted_record? # :nodoc:
112
+ flag = @data.read(1)
113
+ flag ? flag.unpack1('a') == '*' : true
114
+ end
115
+
116
+ def header # :nodoc:
117
+ @header ||= safe_seek do
118
+ @data.seek(0)
119
+ Header.new(@data.read(ADT_HEADER_SIZE))
120
+ end
121
+ end
122
+
123
+ def open_data(data) # :nodoc:
124
+ File.open(data, 'rb')
125
+ rescue Errno::ENOENT
126
+ raise ADT::FileNotFoundError, "file not found: #{data}"
127
+ end
128
+
129
+ def safe_seek # :nodoc:
130
+ original_pos = @data.pos
131
+ yield.tap { @data.seek(original_pos) }
132
+ end
133
+
134
+ def seek(offset) # :nodoc:
135
+ @data.seek(data_offset + offset)
136
+ end
137
+
138
+ def seek_to_record(index) # :nodoc:
139
+ seek(index * record_length)
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ADT
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: adt-driver
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sven Clement
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-06-13 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - clement@clement-weyer.lu
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rspec"
22
+ - ".rubocop.yml"
23
+ - ".travis.yml"
24
+ - Gemfile
25
+ - Gemfile.lock
26
+ - README.md
27
+ - Rakefile
28
+ - adt-driver.gemspec
29
+ - bin/console
30
+ - bin/setup
31
+ - lib/adt.rb
32
+ - lib/adt/column.rb
33
+ - lib/adt/column_type.rb
34
+ - lib/adt/header.rb
35
+ - lib/adt/rails.rb
36
+ - lib/adt/record.rb
37
+ - lib/adt/schema.rb
38
+ - lib/adt/table.rb
39
+ - lib/adt/version.rb
40
+ homepage: https://github.com/svnee/adt-driver
41
+ licenses: []
42
+ metadata:
43
+ homepage_uri: https://github.com/svnee/adt-driver
44
+ source_code_uri: https://github.com/svnee/adt-driver
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 2.3.0
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.1.2
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: ADT is a small Ruby library for reading Advantage Database Server ADT files
64
+ test_files: []