adt-driver 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +143 -0
- data/.travis.yml +6 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +107 -0
- data/README.md +36 -0
- data/Rakefile +8 -0
- data/adt-driver.gemspec +29 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/adt.rb +21 -0
- data/lib/adt/column.rb +101 -0
- data/lib/adt/column_type.rb +130 -0
- data/lib/adt/header.rb +18 -0
- data/lib/adt/rails.rb +22 -0
- data/lib/adt/record.rb +88 -0
- data/lib/adt/schema.rb +87 -0
- data/lib/adt/table.rb +142 -0
- data/lib/adt/version.rb +5 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -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
|
data/.travis.yml
ADDED
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
|
data/Gemfile.lock
ADDED
@@ -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
|
data/README.md
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
data/adt-driver.gemspec
ADDED
@@ -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
|
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 '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
|
data/bin/setup
ADDED
data/lib/adt.rb
ADDED
@@ -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'
|
data/lib/adt/column.rb
ADDED
@@ -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
|
data/lib/adt/header.rb
ADDED
@@ -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
|
data/lib/adt/rails.rb
ADDED
@@ -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
|
data/lib/adt/record.rb
ADDED
@@ -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
|
data/lib/adt/schema.rb
ADDED
@@ -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
|
data/lib/adt/table.rb
ADDED
@@ -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
|
data/lib/adt/version.rb
ADDED
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: []
|