sas2yaml 0.0.1
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 +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/sas2yaml +21 -0
- data/lib/sas2yaml.rb +6 -0
- data/lib/sas2yaml/assemble_command.rb +21 -0
- data/lib/sas2yaml/metadata.rb +6 -0
- data/lib/sas2yaml/process_sas.rb +34 -0
- data/lib/sas2yaml/rangifier.rb +24 -0
- data/lib/sas2yaml/sas_processor.rb +221 -0
- data/lib/sas2yaml/sassifier.rb +82 -0
- data/sas2yaml.gemspec +34 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cdbfd522aa2b75913e825ff59ea05de81e35ce2d
|
4
|
+
data.tar.gz: bcb279dd496c036991a20d7aa1c22c359faab60e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 38e7dc54d3467f37b1b7284539d2aa74ee3e3567f05cec458fed07aee69e0b2ecd7ea69d44d450f7859dad6fdda7c0602bde2f5fdb1e128c1016f955d8d1ba47
|
7
|
+
data.tar.gz: e40c5d66386bdedd659a6392e7ba40f6686675aa0723eb6c2264df041dda0f468e531a239243700c78334759c792808873cc0323fe1700796cd4a49c71710442
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Outcomes Insights, Inc.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Sas2Yaml
|
2
|
+
|
3
|
+
This gem installs a CLI program, `sas2yaml` which will (attempt) to read in a SAS input statement and turn it into a [YAML](http://yaml.org/) file with the name of each variable along with its position in a fixed-width file, length, and type (assuming that information is available).
|
4
|
+
|
5
|
+
This gem was developed in order to decipher the fixed-width file structure of SEER Medicare SAS input statement files and works (fairly) well in processing those files. Your mileage almost certainly will vary.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'sas2yaml'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install sas2yaml
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
```
|
26
|
+
sas2yaml file1.sas file2.sas ...
|
27
|
+
```
|
28
|
+
|
29
|
+
For each file listed, a lot of output will hit your screen, the program will hopefully not bomb out, and you'll end up with files named "file1.yml" and "file2.yml" (from the example above).
|
30
|
+
|
31
|
+
## Development
|
32
|
+
|
33
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake false` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
34
|
+
|
35
|
+
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).
|
36
|
+
|
37
|
+
## Thanks
|
38
|
+
- [Outcomes Insights, Inc.](http://outins.com/)
|
39
|
+
- Many thanks for allowing me to release a portion of my work as Open Source Software!
|
40
|
+
- [Ruby](https://www.ruby-lang.org/en/)
|
41
|
+
- For being such an awesome language that it can pretend to be another (and much more awful) language **cough** *SAS* **cough**.
|
42
|
+
|
43
|
+
|
44
|
+
## Contributing
|
45
|
+
|
46
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/sas2yaml.
|
47
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "sas2yaml"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/exe/sas2yaml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "escort"
|
4
|
+
require "bundler/setup"
|
5
|
+
require "sas2yaml"
|
6
|
+
|
7
|
+
Escort::App.create do |app|
|
8
|
+
app.version Sas2Yaml::VERSION
|
9
|
+
app.summary Sas2Yaml::SUMMARY
|
10
|
+
app.description Sas2Yaml::DESCRIPTION
|
11
|
+
|
12
|
+
app.action do |options, arguments|
|
13
|
+
begin
|
14
|
+
Sas2Yaml::AssembleCommand.new(options, arguments).execute
|
15
|
+
rescue
|
16
|
+
puts $!.message
|
17
|
+
puts $!.backtrace.join("\n")
|
18
|
+
raise
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/sas2yaml.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'sas_processor'
|
2
|
+
require_relative 'sassifier'
|
3
|
+
require 'psych'
|
4
|
+
|
5
|
+
module Sas2Yaml
|
6
|
+
class AssembleCommand < ::Escort::ActionCommand::Base
|
7
|
+
def execute
|
8
|
+
arguments.each do |sas_file|
|
9
|
+
puts "Processing #{sas_file}"
|
10
|
+
processed_sas = SasProcessor.new(sas_file).lines.join("\n")
|
11
|
+
sassy_file = File.join(Dir.tmpdir, File.basename(sas_file, '.*') + '.sassy')
|
12
|
+
File.write(sassy_file, processed_sas)
|
13
|
+
puts "Temp at #{sassy_file}"
|
14
|
+
sassy = Sassifier.new(processed_sas)
|
15
|
+
puts "NUM COLUMNS: #{sassy.hash.keys.length}"
|
16
|
+
file = sas_file.gsub(/\..+$/, '.yml')
|
17
|
+
File.write(file, sassy.hash.to_yaml)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Script that drives processing each SAS file to glean the field type
|
2
|
+
# information e.g.
|
3
|
+
# Column position
|
4
|
+
# Length
|
5
|
+
# Format
|
6
|
+
# Data type
|
7
|
+
|
8
|
+
require 'psych'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'tmpdir'
|
11
|
+
|
12
|
+
require_relative '../parsers/sas/sas_processor'
|
13
|
+
require_relative '../parsers/sas/sassifier'
|
14
|
+
|
15
|
+
module ProcessSas
|
16
|
+
extend self
|
17
|
+
|
18
|
+
def run
|
19
|
+
dir = 'ddls'
|
20
|
+
FileUtils.mkdir(dir) unless File.exist?(dir)
|
21
|
+
Dir.glob("sas/*").each do |sas_file|
|
22
|
+
puts "=" * 90
|
23
|
+
puts sas_file
|
24
|
+
puts "=" * 90
|
25
|
+
processed_sas = SasProcessor.new(sas_file).lines.join("\n")
|
26
|
+
sassy = Sassifier.new(processed_sas)
|
27
|
+
puts "NUM COLUMNS: #{sassy.hash.keys.length}"
|
28
|
+
file = File.join(dir, sas_file.gsub(/.+\//, '').gsub(/\..+$/, '.yml'))
|
29
|
+
File.open(file, 'w') { |f| f.puts sassy.hash.to_yaml }
|
30
|
+
|
31
|
+
File.write(File.join(Dir.tmpdir, File.basename(file, '.*') + '.sassy'), processed_sas)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Given an input of something like mon1-mon288
|
2
|
+
# will return %w(mon1 mon2 ... mon288) when #values is called
|
3
|
+
class Rangifier
|
4
|
+
TRAILING_DIGITS_REGEXP = /(\d+)$/
|
5
|
+
def initialize(input)
|
6
|
+
@input = input
|
7
|
+
end
|
8
|
+
|
9
|
+
def values
|
10
|
+
@values ||= get_values
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def get_values
|
15
|
+
parts = @input.sub(/;/, '').strip.split('-').map(&:strip)
|
16
|
+
return [@input] unless parts.first =~ TRAILING_DIGITS_REGEXP
|
17
|
+
puts "Rangifying #@input => #{parts}"
|
18
|
+
first = parts.first.match(TRAILING_DIGITS_REGEXP)[1].to_i
|
19
|
+
last = parts.last.match(TRAILING_DIGITS_REGEXP)[1].to_i
|
20
|
+
prefix = parts.first.sub(TRAILING_DIGITS_REGEXP, '')
|
21
|
+
puts "Ranging #{prefix} from #{first} - #{last}"
|
22
|
+
(first..last).map { |i| "#{prefix}#{i}"}
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
require_relative 'rangifier'
|
2
|
+
|
3
|
+
# Translates SAS code into executable Ruby code
|
4
|
+
#
|
5
|
+
# This is a very gross translation and most code is skipped completely
|
6
|
+
# When I was looking at the SAS code to read in the ALL dataset, I noticed
|
7
|
+
# that I couldn't simply parse out each line and read the field information
|
8
|
+
# directly from the SAS file because sometimes the SAS file used loops
|
9
|
+
# to define the columns for some of the fields
|
10
|
+
#
|
11
|
+
# Also, some of the data type information was assigned to arrays in those
|
12
|
+
# loops.
|
13
|
+
#
|
14
|
+
# BUT I did notice that SAS looks a bit like Ruby and, with a little
|
15
|
+
# pruning and translating, the SAS code could be converted to Ruby
|
16
|
+
#
|
17
|
+
# So this class will parse a SAS file looking for the first line that
|
18
|
+
# starts with INPUT and will parse the file until it hits a line that
|
19
|
+
# says LABELs
|
20
|
+
#
|
21
|
+
# The resulting Ruby code uses instance variables to store all
|
22
|
+
# the information during processing. This is to avoid problems where the
|
23
|
+
# SAS code is using reserved words or method names in Ruby.
|
24
|
+
# The SAS code frequently uses variable names like "begin" and "end" which are
|
25
|
+
# reserved words in Ruby and "inc" and "pos" which are common method names
|
26
|
+
#
|
27
|
+
# See below for information about how certain lines are handled
|
28
|
+
class SasProcessor
|
29
|
+
@@def_regexp = /@[^;]/
|
30
|
+
@@array_regexp = /array/
|
31
|
+
@@label_start_regexp = /^label/i
|
32
|
+
@@comment_regexp =%r{/\*.*?\*/}m
|
33
|
+
@@blank_line_regexp = /^$/
|
34
|
+
@@mode = :skip
|
35
|
+
@@do_regexp = /^do /
|
36
|
+
@@equals_regexp = /=/
|
37
|
+
@@infile_regexp = /^infile\s+/i
|
38
|
+
@@op_regexp = %r{([+-/=*]+)}
|
39
|
+
|
40
|
+
def initialize(file_path)
|
41
|
+
@file_path = file_path
|
42
|
+
end
|
43
|
+
|
44
|
+
# Given blah01-blah02 or (blah01-blah02)
|
45
|
+
# Return ('blah01'..'blah02')
|
46
|
+
def rangify(range)
|
47
|
+
'%w(' + Rangifier.new(strip_parens(range)).values.join(' ') + ')'
|
48
|
+
end
|
49
|
+
|
50
|
+
# Given "(something)"
|
51
|
+
# Return "something"
|
52
|
+
def strip_parens(text)
|
53
|
+
text.gsub(/[()]/, '')
|
54
|
+
end
|
55
|
+
|
56
|
+
# Given what could either be a variable name or a number literal, returns
|
57
|
+
# either an instance variable with the same name, or the number literal
|
58
|
+
# "001" => "1"
|
59
|
+
# "var" => "@var"
|
60
|
+
# "12var34" => "@12var34"
|
61
|
+
def var_or_num(v_or_n)
|
62
|
+
return v_or_n.gsub(/^0+(\d)/,'\1') if /^\d+$/.match(v_or_n)
|
63
|
+
ivarify(v_or_n)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Expect inputs like
|
67
|
+
# cred10 which we just return
|
68
|
+
# or (plan1-plan2) which we return as the range ('plan1'..'plan2')
|
69
|
+
# or some_arr(2) which we return as @some_arr[2]
|
70
|
+
def fix_name(text)
|
71
|
+
return "'#{text}'" unless /\(/.match(text)
|
72
|
+
return rangify(text) if /^\(/.match(text)
|
73
|
+
ivarify(text.gsub(/\(([^)]+)\)/) do |match|
|
74
|
+
'[' + var_or_num($1) + ']'
|
75
|
+
end)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Prepends "@" to the string
|
79
|
+
def ivarify(var_name)
|
80
|
+
'@' + var_name
|
81
|
+
end
|
82
|
+
|
83
|
+
# Definition lines start with "@111" or "input @pos + 2"
|
84
|
+
# But either way, we're interested in everything after the @ sign
|
85
|
+
# for these lines
|
86
|
+
# Examples
|
87
|
+
# @012 bic $char2.
|
88
|
+
# input @inc2 dgn(j) $char5.
|
89
|
+
def translate_def(line)
|
90
|
+
# Strip up through the @
|
91
|
+
line.gsub!(/^.*@/, '')
|
92
|
+
parts = line.split(/\s+/)
|
93
|
+
# Type information is always the last word on the line
|
94
|
+
type = parts.pop
|
95
|
+
# The name is always the second to the last word on the line
|
96
|
+
name = fix_name(parts.pop)
|
97
|
+
|
98
|
+
# The rest of the line could be as simple as a number literal or something like
|
99
|
+
# (pos + 1)
|
100
|
+
# So we put spaces between all the operators and then translate each part
|
101
|
+
rest = blow_out_ops(strip_parens(parts.join(' '))).split(/\s+/).map { |w| var_or_num_or_op(w) }.join(' ')
|
102
|
+
|
103
|
+
# Spit out Ruby code that will tranlate the contents of this definition line
|
104
|
+
"at(#{rest}, #{name}, '#{type}')"
|
105
|
+
end
|
106
|
+
|
107
|
+
# SAS seems to be able to store input definitions into arrays
|
108
|
+
# I don't know the mechanism, but I know how to translate it
|
109
|
+
# We'll take a line like
|
110
|
+
# array dgn(10) $ dgn_cd1-dgn_cd10;
|
111
|
+
def translate_array(line)
|
112
|
+
parts = line.split('$')
|
113
|
+
# Last word has a ';' and then then a range that defines the field names
|
114
|
+
# assigned to this array
|
115
|
+
range = rangify(parts.pop.chop.sub(/^[\s\d]*/, ''))
|
116
|
+
parts = parts.first.split(/\s+/)
|
117
|
+
# We'll use the array's name as it appears in SAS to store the array
|
118
|
+
# of field names
|
119
|
+
# The array name starts as arr(10) so we'll chop off the part in parens
|
120
|
+
puts parts[1]
|
121
|
+
array_name = ivarify(parts[1].gsub(/\(.*/, ''))
|
122
|
+
puts array_name
|
123
|
+
# Spit out Ruby that assigns the array of field names to the array instance variable
|
124
|
+
"#{array_name} = #{range}"
|
125
|
+
end
|
126
|
+
|
127
|
+
# Given do i = 1 to 24;
|
128
|
+
# Return:
|
129
|
+
# (1..24).each do |i|
|
130
|
+
# @i = i
|
131
|
+
def translate_do(line)
|
132
|
+
parts = line.chop.split(/\s+/)
|
133
|
+
lines = []
|
134
|
+
finish = var_or_num(parts.pop)
|
135
|
+
parts.pop # skip to
|
136
|
+
start = var_or_num(parts.pop)
|
137
|
+
parts.pop # skip equals
|
138
|
+
var = parts.pop
|
139
|
+
|
140
|
+
lines << "(#{start} - 1..#{finish} - 1).each do |#{var}|"
|
141
|
+
lines << "\t#{ivarify(var)} = #{var}"
|
142
|
+
lines.join("\n")
|
143
|
+
end
|
144
|
+
|
145
|
+
# Given a variable, number literal, or operator
|
146
|
+
# return the appropriate translation
|
147
|
+
# See var_or_num for variables and numbers
|
148
|
+
# Operators are returned the same as they came in e.g.
|
149
|
+
# "+" => "+"
|
150
|
+
def var_or_num_or_op(vno)
|
151
|
+
return vno if @@op_regexp.match(vno)
|
152
|
+
var_or_num(vno)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Places spaces around all operators so that
|
156
|
+
# we can split on white space and pick up the operators as tokens
|
157
|
+
def blow_out_ops(text)
|
158
|
+
text.gsub(@@op_regexp, ' \1 ')
|
159
|
+
end
|
160
|
+
|
161
|
+
# Given a line that assigns a value to something on the left hand side
|
162
|
+
# We need to do some basic translation of the line e.g.
|
163
|
+
# "inc2=inc2+5;" => "@inc2 = @inc2 + 5"
|
164
|
+
def translate_equals(line)
|
165
|
+
blow_out_ops(line.chop).split(/\s+/).map { |w| var_or_num_or_op(w) }.join(' ')
|
166
|
+
end
|
167
|
+
|
168
|
+
def translate_infile(line)
|
169
|
+
md = /lrecl\s*=\s*(\d+)/.match(line)
|
170
|
+
puts md
|
171
|
+
return "" if md.nil?
|
172
|
+
return "record_len(#{md[1]})"
|
173
|
+
end
|
174
|
+
|
175
|
+
# This method processes the SAS file and translates all relevant lines
|
176
|
+
def lines
|
177
|
+
if @lines.nil?
|
178
|
+
@lines = []
|
179
|
+
@@mode = :skip
|
180
|
+
File.open(@file_path).each_line do |l|
|
181
|
+
# Strip out all comments from each line
|
182
|
+
line = l.chomp.strip.gsub(@@comment_regexp, '')
|
183
|
+
|
184
|
+
# Once we hit a line with "input", start processing
|
185
|
+
@@mode = :process if line.match(/^input/i) || line.match(@@infile_regexp)
|
186
|
+
next if @@mode == :skip
|
187
|
+
|
188
|
+
# @; is ignorable, so make it a blank line
|
189
|
+
line.gsub!(/@;/, '')
|
190
|
+
|
191
|
+
# Skip blank lines
|
192
|
+
next if line.match(@@blank_line_regexp)
|
193
|
+
|
194
|
+
case line
|
195
|
+
when @@def_regexp
|
196
|
+
@lines << translate_def(line.downcase)
|
197
|
+
when @@array_regexp
|
198
|
+
@lines << translate_array(line.downcase)
|
199
|
+
when @@do_regexp
|
200
|
+
@lines << translate_do(line.downcase)
|
201
|
+
when @@infile_regexp
|
202
|
+
@lines << translate_infile(line.downcase)
|
203
|
+
when @@equals_regexp
|
204
|
+
@lines << translate_equals(line.downcase)
|
205
|
+
when @@label_start_regexp
|
206
|
+
# We hit the start of the labels, we can quit processing the file
|
207
|
+
break
|
208
|
+
when /^end;/
|
209
|
+
@lines << line.chop.downcase
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
@lines
|
214
|
+
end
|
215
|
+
|
216
|
+
def save(file_path)
|
217
|
+
File.open(file_path, 'w') do |f|
|
218
|
+
f.puts lines.join("\n")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# Runs the code that we generate by translating a SAS file into Ruby
|
2
|
+
#
|
3
|
+
# This class uses "eval" to get it's job done, so be wary about what code
|
4
|
+
# you feed it
|
5
|
+
class Sassifier
|
6
|
+
def initialize(code)
|
7
|
+
@_code = code
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
puts @_code
|
12
|
+
# eval the code, using the current class's context
|
13
|
+
# so that the code has access to the class's supporting methods
|
14
|
+
eval @_code, binding()
|
15
|
+
check_record_length
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def check_record_length
|
20
|
+
return if @record_length.nil?
|
21
|
+
last_column = @_hash.values.last
|
22
|
+
last_position = last_column[:column] + last_column[:length]
|
23
|
+
return if last_position >= @record_length
|
24
|
+
at(last_position, '_fill', (@record_length - last_position).to_s)
|
25
|
+
@_hash.values.last[:droppable] = true
|
26
|
+
end
|
27
|
+
|
28
|
+
# Hash where we store the field information
|
29
|
+
# Memoized so that we run the code to populate the hash on the
|
30
|
+
# first attempt to access the hash
|
31
|
+
def hash
|
32
|
+
if @_hash.nil?
|
33
|
+
@_hash = {}
|
34
|
+
run
|
35
|
+
end
|
36
|
+
@_hash
|
37
|
+
end
|
38
|
+
|
39
|
+
# Given a SAS-orient type string, return the data type
|
40
|
+
# '10.' => :integer
|
41
|
+
# '15.2' => :decimal
|
42
|
+
# All else seems to be string (so far)
|
43
|
+
def get_type(type_str)
|
44
|
+
return :integer if /^\d+\.$/.match(type_str)
|
45
|
+
return :decimal if /^\d+\.\d+$/.match(type_str)
|
46
|
+
return :string
|
47
|
+
end
|
48
|
+
|
49
|
+
# Given the same type string as described in get_type
|
50
|
+
# Return the length
|
51
|
+
# '10.' => 10
|
52
|
+
# '15.2' => 15
|
53
|
+
# '$char2' => 2
|
54
|
+
def get_length(type_str)
|
55
|
+
type_str.gsub(/^\D+/, '').to_i
|
56
|
+
end
|
57
|
+
|
58
|
+
# Given a column position, a set of names, and a type_str
|
59
|
+
# store information about the field for each name
|
60
|
+
def at(column, names, type_str)
|
61
|
+
length = get_length(type_str)
|
62
|
+
type = get_type(type_str)
|
63
|
+
|
64
|
+
# If we receive a set of names, then we assume that the names represent
|
65
|
+
# individual slices of a contiguous set of columns, all the same length
|
66
|
+
#
|
67
|
+
# So if we have column position of 1 and names %w(name1 name2) and a length of 2
|
68
|
+
# then name1 is column 1, length 2 and name2 is column3 length 2
|
69
|
+
if names.is_a?(Array)
|
70
|
+
names.each do |name|
|
71
|
+
at(column, name, type_str)
|
72
|
+
column += length
|
73
|
+
end
|
74
|
+
else
|
75
|
+
@_hash[names.to_sym] = {column: column, name: names, type: type, length: length, format: type_str}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def record_len(length)
|
80
|
+
@record_length = length
|
81
|
+
end
|
82
|
+
end
|
data/sas2yaml.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sas2yaml/metadata'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "sas2yaml"
|
8
|
+
spec.version = Sas2Yaml::VERSION
|
9
|
+
spec.authors = ["Ryan Duryea"]
|
10
|
+
spec.email = ["aguynamedryan@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = Sas2Yaml::SUMMARY
|
13
|
+
spec.description = Sas2Yaml::DESCRIPTION
|
14
|
+
spec.homepage = "https://github.com/outcomesinsights/sas2yaml"
|
15
|
+
|
16
|
+
spec.licenses = ["MIT"]
|
17
|
+
|
18
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
19
|
+
# delete this section to allow pushing this gem to any host.
|
20
|
+
if spec.respond_to?(:metadata)
|
21
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
22
|
+
else
|
23
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
32
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
33
|
+
spec.add_dependency "escort", "~> 0.4.0"
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sas2yaml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Duryea
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: escort
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.4.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.4.0
|
55
|
+
description: Converts a SAS Input file into a YAML file which details each column
|
56
|
+
along with its type, length, and start column.
|
57
|
+
email:
|
58
|
+
- aguynamedryan@gmail.com
|
59
|
+
executables:
|
60
|
+
- sas2yaml
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- ".gitignore"
|
65
|
+
- ".travis.yml"
|
66
|
+
- Gemfile
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- bin/console
|
71
|
+
- bin/setup
|
72
|
+
- exe/sas2yaml
|
73
|
+
- lib/sas2yaml.rb
|
74
|
+
- lib/sas2yaml/assemble_command.rb
|
75
|
+
- lib/sas2yaml/metadata.rb
|
76
|
+
- lib/sas2yaml/process_sas.rb
|
77
|
+
- lib/sas2yaml/rangifier.rb
|
78
|
+
- lib/sas2yaml/sas_processor.rb
|
79
|
+
- lib/sas2yaml/sassifier.rb
|
80
|
+
- sas2yaml.gemspec
|
81
|
+
homepage: https://github.com/outcomesinsights/sas2yaml
|
82
|
+
licenses:
|
83
|
+
- MIT
|
84
|
+
metadata:
|
85
|
+
allowed_push_host: https://rubygems.org
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 2.4.6
|
103
|
+
signing_key:
|
104
|
+
specification_version: 4
|
105
|
+
summary: Convert SAS Input files into YAML describing layout of the data
|
106
|
+
test_files: []
|
107
|
+
has_rdoc:
|