dsx-dml 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +9 -0
- data/dsx-dml.gemspec +1 -1
- data/lib/dsx/dml.rb +52 -30
- data/lib/dsx/dml/version.rb +1 -1
- data/lib/dsx/operation.rb +170 -0
- data/lib/dsx/writer.rb +94 -0
- data/spec/dsx/dml_spec.rb +88 -39
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1c0267af52f6079d2061168946dfbfedf3fdb5d5
|
4
|
+
data.tar.gz: b667fcf06038eba5823c6d5c3fb27c226f4448ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 30f256ae7964fb6409af0741443c40f567e6db6aa54672d297e851159aa65190f8976cb1869e5e8196c05ed8d044bf1ef2772c6793f63f943864a0448a218d13
|
7
|
+
data.tar.gz: 0f1c0aae389746b2d12f1eb7af8d761ca8c8c4a67df21924a8decbb369b60cdbc3913d54a0efd94d467b983498c7c26bbffe3b36452b268e1bf2e500bbddf8d1
|
data/Rakefile
CHANGED
data/dsx-dml.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
-
spec.add_development_dependency "rake"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.1"
|
23
23
|
spec.add_development_dependency "rspec", "~> 2.14"
|
24
24
|
|
25
25
|
spec.add_runtime_dependency "net-sftp", "~> 2.1"
|
data/lib/dsx/dml.rb
CHANGED
@@ -1,21 +1,24 @@
|
|
1
1
|
require 'dsx/dml/version'
|
2
|
-
require 'net/sftp'
|
3
|
-
require 'securerandom'
|
4
|
-
require 'tmpdir'
|
5
|
-
require 'tempfile'
|
6
2
|
|
7
3
|
module Dsx
|
8
4
|
module Dml
|
9
5
|
|
10
6
|
class Zone
|
11
7
|
|
12
|
-
def initialize(location_num, udf_field_num)
|
8
|
+
def initialize(location_num, udf_field_num, writer = false)
|
13
9
|
@location_num = location_num
|
14
10
|
@udf_field_num = udf_field_num
|
11
|
+
if(writer)
|
12
|
+
@writer = writer
|
13
|
+
end
|
15
14
|
end
|
16
15
|
|
17
16
|
def use(id_num)
|
18
|
-
|
17
|
+
if(@writer)
|
18
|
+
return Changeset.new(self, id_num)
|
19
|
+
else
|
20
|
+
return Changeset.new(self, id_num, @writer)
|
21
|
+
end
|
19
22
|
end
|
20
23
|
|
21
24
|
def location_num
|
@@ -31,19 +34,26 @@ module Dsx
|
|
31
34
|
|
32
35
|
class Changeset
|
33
36
|
|
34
|
-
def initialize(zone, id_num)
|
37
|
+
def initialize(zone, id_num, writer = false)
|
35
38
|
@id_num = id_num
|
36
39
|
@zone = zone
|
37
40
|
@stack = []
|
41
|
+
if(writer)
|
42
|
+
@writer = writer
|
43
|
+
end
|
38
44
|
start_command
|
39
45
|
end
|
40
46
|
|
41
47
|
def table(table_name)
|
42
48
|
case table_name
|
43
|
-
when
|
44
|
-
return
|
49
|
+
when :Cards
|
50
|
+
return CardsTable.new(self, table_name)
|
51
|
+
when :Names
|
52
|
+
return NamesTable.new(self, table_name)
|
53
|
+
when 'Cards'
|
54
|
+
return CardsTable.new(self, table_name)
|
45
55
|
when 'Names'
|
46
|
-
return
|
56
|
+
return NamesTable.new(self, table_name)
|
47
57
|
else
|
48
58
|
return Table.new(self, table_name)
|
49
59
|
end
|
@@ -54,26 +64,34 @@ module Dsx
|
|
54
64
|
def to_string
|
55
65
|
@stack.join("\r")
|
56
66
|
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
to_string
|
70
|
+
end
|
71
|
+
|
72
|
+
def writer=(writer)
|
73
|
+
@writer = writer
|
74
|
+
end
|
57
75
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
76
|
+
def writer
|
77
|
+
@writer
|
78
|
+
end
|
79
|
+
|
80
|
+
def net_sftp(writer = false)
|
81
|
+
if (writer)
|
82
|
+
writer.net_sftp(to_string)
|
83
|
+
elsif (@writer)
|
84
|
+
@writer.net_sftp(to_string)
|
65
85
|
end
|
66
86
|
return self
|
67
87
|
end
|
68
88
|
|
69
89
|
# Alternative to Net::SFTP.
|
70
|
-
def curl(
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
result = `curl -s -S -u #{params[:user]}:#{params[:password]} -T #{file.path} -w "okay\n" sftp://#{params[:address]}/`
|
76
|
-
file.unlink()
|
90
|
+
def curl(writer = false)
|
91
|
+
if (writer)
|
92
|
+
writer.curl(to_string)
|
93
|
+
elsif (@writer)
|
94
|
+
@writer.curl(to_string)
|
77
95
|
end
|
78
96
|
return self
|
79
97
|
end
|
@@ -118,21 +136,25 @@ module Dsx
|
|
118
136
|
def curl(params)
|
119
137
|
@changeset.curl(params)
|
120
138
|
end
|
139
|
+
|
140
|
+
def queue(data)
|
141
|
+
fields(data)
|
142
|
+
end
|
121
143
|
|
122
|
-
def write(data)
|
144
|
+
def write(data={})
|
123
145
|
fields(data)
|
124
146
|
@changeset.stack << 'W'
|
125
147
|
return self
|
126
148
|
end
|
127
149
|
|
128
|
-
def update(data)
|
150
|
+
def update(data={})
|
129
151
|
fields(data)
|
130
152
|
@changeset.stack << 'U'
|
131
153
|
return self
|
132
154
|
end
|
133
155
|
|
134
156
|
def delete
|
135
|
-
fields(data)
|
157
|
+
fields(data={})
|
136
158
|
@changeset.stack << 'D'
|
137
159
|
return self
|
138
160
|
end
|
@@ -155,7 +177,7 @@ module Dsx
|
|
155
177
|
end
|
156
178
|
|
157
179
|
|
158
|
-
class
|
180
|
+
class CardsTable < Table
|
159
181
|
|
160
182
|
def relace_code(code)
|
161
183
|
single('ReplaceCode', code)
|
@@ -182,7 +204,7 @@ module Dsx
|
|
182
204
|
end
|
183
205
|
|
184
206
|
def clear_all_acl
|
185
|
-
blank('
|
207
|
+
blank('ClearAcl')
|
186
208
|
end
|
187
209
|
|
188
210
|
def clear_all_tmp_acl
|
@@ -196,7 +218,7 @@ module Dsx
|
|
196
218
|
end
|
197
219
|
|
198
220
|
|
199
|
-
class
|
221
|
+
class NamesTable < Table
|
200
222
|
|
201
223
|
def dc
|
202
224
|
@changeset.stack << 'DC'
|
data/lib/dsx/dml/version.rb
CHANGED
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'dsx/dml/version'
|
2
|
+
require 'dsx/dml'
|
3
|
+
require 'dsx/writer'
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
module Dsx
|
8
|
+
module Dml
|
9
|
+
|
10
|
+
class Operation
|
11
|
+
|
12
|
+
def initialize(details, writer = false)
|
13
|
+
initialize_details(details)
|
14
|
+
initialize_command(writer)
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def last_type=(type)
|
19
|
+
@type = type
|
20
|
+
end
|
21
|
+
|
22
|
+
def last_type
|
23
|
+
@type
|
24
|
+
end
|
25
|
+
|
26
|
+
def reset_cards
|
27
|
+
table = @changeset.table(:Names)
|
28
|
+
table.dc
|
29
|
+
table.write
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def access_levels
|
34
|
+
return AccessLevelOp.new(self, @changeset)
|
35
|
+
end
|
36
|
+
|
37
|
+
def access_levels=(args=false)
|
38
|
+
return self
|
39
|
+
end
|
40
|
+
|
41
|
+
def linking_level=(level)
|
42
|
+
table = @changeset.table(:Cards)
|
43
|
+
table.write({Code: @identifier, Loc: @location_num, OLL: level})
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def tmp_access_levels
|
48
|
+
return TmpAccessLevelOp.new(self, @changeset)
|
49
|
+
end
|
50
|
+
|
51
|
+
def identifier
|
52
|
+
@identifier
|
53
|
+
end
|
54
|
+
|
55
|
+
def changeset
|
56
|
+
@changeset
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_s
|
60
|
+
@changeset.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def initialize_command(writer=false)
|
66
|
+
@zone = (writer) ? Zone.new(@location_num, @udf_num, writer) : Zone.new(@location_num, @udf_num)
|
67
|
+
changeset = @zone.use(@identifier)
|
68
|
+
changeset.table(:Names).write(FName: @first_name,
|
69
|
+
LName: @last_name,
|
70
|
+
Company: @company)
|
71
|
+
changeset.table(:UDF).write(UdfNum: @udf_num,
|
72
|
+
UdfText: @identifier)
|
73
|
+
@changeset = changeset
|
74
|
+
end
|
75
|
+
|
76
|
+
def initialize_details(details)
|
77
|
+
if( details.key?(:location_num) &&
|
78
|
+
details.key?(:udf_num) &&
|
79
|
+
details.key?(:first_name) &&
|
80
|
+
details.key?(:last_name) &&
|
81
|
+
details.key?(:company)&&
|
82
|
+
details.key?(:identifier) )
|
83
|
+
@location_num = details[:location_num]
|
84
|
+
@udf_num = details[:udf_num]
|
85
|
+
@first_name = details[:first_name]
|
86
|
+
@last_name = details[:last_name]
|
87
|
+
@company = details[:company]
|
88
|
+
@identifier = details[:identifier]
|
89
|
+
else
|
90
|
+
raise "Operation arguments missing"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
class OpTemplate
|
97
|
+
def initialize(op, changeset)
|
98
|
+
@changeset = changeset
|
99
|
+
@op = op
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
class CardRelatedOp < OpTemplate
|
105
|
+
def initialize(op, changeset)
|
106
|
+
super(op, changeset)
|
107
|
+
@table = @changeset.table(:Cards)
|
108
|
+
@table.queue(Code: op.identifier)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
class AccessLevelOp < CardRelatedOp
|
114
|
+
|
115
|
+
def + (values)
|
116
|
+
values.each do |level|
|
117
|
+
@table.add_acl(level)
|
118
|
+
end
|
119
|
+
@table.write
|
120
|
+
@op
|
121
|
+
end
|
122
|
+
|
123
|
+
def - (values)
|
124
|
+
values.each do |level|
|
125
|
+
@table.del_acl(level)
|
126
|
+
end
|
127
|
+
@table.write
|
128
|
+
@op
|
129
|
+
end
|
130
|
+
|
131
|
+
def reset
|
132
|
+
@table.clear_all_acl
|
133
|
+
@table.write
|
134
|
+
@op
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
class TmpAccessLevelOp < CardRelatedOp
|
141
|
+
|
142
|
+
def + (values)
|
143
|
+
values.each do |level|
|
144
|
+
@table.add_tmp_acl(level)
|
145
|
+
end
|
146
|
+
@table.write
|
147
|
+
@op
|
148
|
+
end
|
149
|
+
|
150
|
+
def - (values)
|
151
|
+
values.each do |level|
|
152
|
+
@table.del_tmp_acl(level)
|
153
|
+
end
|
154
|
+
@table.write
|
155
|
+
@op
|
156
|
+
end
|
157
|
+
|
158
|
+
def reset
|
159
|
+
@table.clear_all_tmp_acl
|
160
|
+
@table.write
|
161
|
+
@op
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
data/lib/dsx/writer.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'dsx/dml/version'
|
2
|
+
require 'net/sftp'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'tmpdir'
|
5
|
+
require 'tempfile'
|
6
|
+
|
7
|
+
|
8
|
+
# @ Todo: any encrypt/decrypt functions
|
9
|
+
|
10
|
+
module Dsx
|
11
|
+
module Dml
|
12
|
+
|
13
|
+
class Writer
|
14
|
+
|
15
|
+
def initialize(params)
|
16
|
+
if(params.key?(:address) && params.key?(:user) && params.key?(:password))
|
17
|
+
@address = params[:address]
|
18
|
+
@user = params[:user]
|
19
|
+
@password = encrypt(params[:password])
|
20
|
+
@doc_label = (params.key? :doc_label) ? params[:doc_label] : 'rb_dsx_dml'
|
21
|
+
|
22
|
+
# @key = SecureRandom::uuid()
|
23
|
+
# @salt = SecureRandom::uuid()
|
24
|
+
# @vector = SecureRandom::uuid()
|
25
|
+
|
26
|
+
else
|
27
|
+
raise 'Incomplete DSX credentials'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# params: {address: server_address, user: user, password: password}
|
32
|
+
def net_sftp(string_or_object)
|
33
|
+
data = format(string_or_object)
|
34
|
+
Net::SFTP::start(@address, @user, :password => @password) do |sftp|
|
35
|
+
sftp.file.open("/^Imp_#{@doc_label}_#{Time.now.utc.to_i}_#{SecureRandom::hex(4)}.txt", 'w') do |file|
|
36
|
+
file.write data
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
dynamic_return(string_or_object)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Alternative to Net::SFTP.
|
44
|
+
def curl(string_or_object)
|
45
|
+
data = format(string_or_object)
|
46
|
+
Dir.mktmpdir do |tmpdir|
|
47
|
+
file = Tempfile.new(["^Imp_#{@doc_label}_#{Time.now.utc.to_i}_", '.txt'], tmpdir)
|
48
|
+
file.write data
|
49
|
+
file.close()
|
50
|
+
result = `curl -s -S -u #{@user}:#{@password} -T #{file.path} -w "okay\n" sftp://#{@address}/`
|
51
|
+
file.unlink()
|
52
|
+
end
|
53
|
+
|
54
|
+
dynamic_return(string_or_object)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def dynamic_return (string_or_object)
|
60
|
+
if (string_or_object.is_a? String)
|
61
|
+
return this
|
62
|
+
else
|
63
|
+
return string_or_object
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def format(string_or_object)
|
68
|
+
if(string_or_object.respond_to?(:to_s))
|
69
|
+
return string_or_object.to_s()
|
70
|
+
elsif(string_or_object.respond_to?(:to_string))
|
71
|
+
return string_or_object.to_string()
|
72
|
+
else
|
73
|
+
# Really most things will respond to to_s. Might need special casing, though.
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def password
|
80
|
+
decrypt(@password)
|
81
|
+
end
|
82
|
+
|
83
|
+
def encrypt(value)
|
84
|
+
return value
|
85
|
+
end
|
86
|
+
|
87
|
+
def decrypt(value)
|
88
|
+
return value
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
data/spec/dsx/dml_spec.rb
CHANGED
@@ -1,45 +1,94 @@
|
|
1
1
|
require 'dsx/dml'
|
2
|
+
require 'dsx/writer'
|
3
|
+
require 'dsx/operation'
|
2
4
|
require 'securerandom'
|
3
5
|
|
6
|
+
def get_card_number
|
7
|
+
rand(10000000000..99999999999)
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_name
|
11
|
+
'test_' + SecureRandom::hex(4)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_credentials
|
15
|
+
{address: '127.0.0.1', user: 'teset' , password: 'testing_password'}
|
16
|
+
end
|
17
|
+
|
4
18
|
describe Dsx::Dml do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
19
|
+
describe "Creating a changset string" do
|
20
|
+
|
21
|
+
name = get_name()
|
22
|
+
number = get_card_number()
|
23
|
+
zone = Dsx::Dml::Zone.new(130, 2)
|
24
|
+
|
25
|
+
changeset = zone.use(number)
|
26
|
+
|
27
|
+
changeset.table('Names').write({
|
28
|
+
FName: name,
|
29
|
+
LName: name + "_L",
|
30
|
+
Company: 'Bio5',
|
31
|
+
Visitor: '',
|
32
|
+
Trace: '',
|
33
|
+
Notes: ''
|
34
|
+
}).table('UDF').write({
|
35
|
+
UdfNum: 2,
|
36
|
+
UdfText: number
|
37
|
+
}).table('Cards').write({
|
38
|
+
Code: number,
|
39
|
+
PIN: 1234,
|
40
|
+
StartDate: '09/15/2013',
|
41
|
+
StopDate: '04/11/2023',
|
42
|
+
NumUses: 9999
|
43
|
+
})
|
44
|
+
|
45
|
+
changeset_string = changeset.to_string
|
46
|
+
|
47
|
+
it "Should use DML compatible newlines" do
|
48
|
+
changeset_string.should include("\r")
|
49
|
+
changeset_string.should_not include("\n")
|
50
|
+
end
|
51
|
+
|
52
|
+
it "Should have some properly formatted fields and values" do
|
53
|
+
changeset_string.should include("^#{name}^^^")
|
54
|
+
changeset_string.should include("^#{number}^^^")
|
55
|
+
changeset_string.should include("\rF UdfNum ^2^^^\r")
|
56
|
+
end
|
57
|
+
|
58
|
+
# writer = Dsx::Dml::Writer.new(test_credentials)
|
59
|
+
# writer.net_sftp(changeset)
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "Using an operation" do
|
64
|
+
name = get_name()
|
65
|
+
op = Dsx::Dml::Operation.new(location_num: 130,
|
66
|
+
udf_num: 2,
|
67
|
+
first_name: name,
|
68
|
+
last_name: name + '_L',
|
69
|
+
company: 'Bio5',
|
70
|
+
identifier: get_card_number())
|
71
|
+
op.access_levels += ['BIO5 Test Level 1', 'BIO5 Test Level 2']
|
72
|
+
op.access_levels -= ['Master Access Level']
|
73
|
+
op.linking_level = 1
|
74
|
+
|
75
|
+
op_string = op.to_s
|
76
|
+
|
77
|
+
it "Should use DML compatible newlines" do
|
78
|
+
op_string.should include("\r")
|
79
|
+
op_string.should_not include("\n")
|
80
|
+
end
|
81
|
+
|
82
|
+
it "Should have access level commands" do
|
83
|
+
op_string.should include("\rF AddAcl ^BIO5 Test Level 1^^^\r")
|
84
|
+
op_string.should include("\rF AddAcl ^BIO5 Test Level 2^^^\r")
|
85
|
+
op_string.should include("\rF DelAcl ^Master Access Level^^^\r")
|
86
|
+
op_string.should include("\rF Loc ^130^^^\r")
|
87
|
+
op_string.should include("\rF OLL ^1^^^\r")
|
44
88
|
end
|
89
|
+
|
90
|
+
# writer = Dsx::Dml::Writer.new(test_credentials)
|
91
|
+
# writer.curl(op.changeset)
|
92
|
+
|
93
|
+
end
|
45
94
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dsx-dml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- dgsan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-09-
|
11
|
+
date: 2013-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -28,16 +28,16 @@ dependencies:
|
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ~>
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '10.1'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ~>
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '10.1'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -83,6 +83,8 @@ files:
|
|
83
83
|
- dsx-dml.gemspec
|
84
84
|
- lib/dsx/dml.rb
|
85
85
|
- lib/dsx/dml/version.rb
|
86
|
+
- lib/dsx/operation.rb
|
87
|
+
- lib/dsx/writer.rb
|
86
88
|
- spec/dsx/dml_spec.rb
|
87
89
|
homepage: https://github.com/dgsan/dsx-dml
|
88
90
|
licenses:
|