sycsvpro 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sycsvpro (0.1.3)
4
+ sycsvpro (0.1.4)
5
5
  gli (= 2.9.0)
6
6
  timeleap (~> 0.0.1)
7
7
 
data/README.md CHANGED
@@ -16,6 +16,7 @@ Processing of csv files. *sycsvpro* offers following functions
16
16
  * create or edit a Ruby script
17
17
  * list scripts available optionally with methods (since version 0.0.7)
18
18
  * execute a Ruby script file that operates a csv file
19
+ * create a table from a source file with dynamically create columns (since version 0.1.4)
19
20
 
20
21
  To get help type
21
22
 
@@ -308,6 +309,26 @@ command: `sycsvpro execute script.rb method infile param1 param2`
308
309
  * Add `clean_up` to *Dsl* that takes files to be deleted after the script has
309
310
  run: `clean_up(%w{file1 file2})`
310
311
 
312
+ Version 0.1.4
313
+ -------------
314
+ * A new Table class is available with following features
315
+ * Create dynamic headline columns based on source table data
316
+ * Associate values to multi keys
317
+ * Create values based on arithmetic operations of source table data
318
+ Example
319
+ `sycsvpro -f in.csv -o out.csv table -h "c4,c5,c0=~/\\.(\\d{4})/" \
320
+ -k "c4,c5" \
321
+ -c "c0=~/\\.(\\d{4})/:+n1"`
322
+ h:: the header is created from the source table header of column 4 and 5.
323
+ Another header column is created dynamicall based on the year part of a
324
+ date in column 0
325
+ k:: the key is based on source table of column 4 and 5
326
+ c:: the column operation is in the form HeaderName:Operation. In this case the
327
+ HeaderName is dynamically determined based on column 0 and added the value
328
+ of column 1 to this column that is associated to the key
329
+
330
+ c4, n4, d4 are string, number and date values respectively
331
+
311
332
  Installation
312
333
  ============
313
334
  [![Gem Version](https://badge.fury.io/rb/sycsvpro.png)](http://badge.fury.io/rb/sycsvpro)
data/bin/sycsvpro CHANGED
@@ -303,6 +303,51 @@ command :aggregate do |c|
303
303
 
304
304
  end
305
305
 
306
+ desc 'Creates a table from a source file'
307
+
308
+ command :table do |c|
309
+
310
+ c.desc 'Rows to consider'
311
+ c.arg_name '1,2,10-30,45-EOF,REGEXP'
312
+ c.flag [:r, :row], :must_match => row_regex
313
+
314
+ c.desc 'Header can be defined by Words (Year), references to source header (c1) and dynamically created header values (c1+c2,c0=~/\\.(\\d{4})/)'
315
+ c.arg_name "COL_A,c6,c2+c4,c0=~/\\.(\\d{4})/"
316
+ c.flag [:h, :header]
317
+
318
+ c.desc 'Key to that the other columns are associated to. A key can be created dynamically'
319
+ c.arg_name "c0=~/\\.(\\d{4})/,c6"
320
+ c.flag [:k, :key]
321
+
322
+ c.desc 'Columns to be associated to the key. Columns are identified by the column name. The operation to create the column value is separated by a colon (:) from the column name'
323
+ c.arg_name "c0=~/\\.(\\d{4})/:+n1,Value:+n2"
324
+ c.flag [:c, :col]
325
+
326
+ c.desc 'Format of date values'
327
+ c.arg_name '%d.%m.%Y|%m/%d/%Y|...'
328
+ c.flag [:df]
329
+
330
+ c.desc 'Format of number values'
331
+ c.arg_name 'DE|EN'
332
+ c.default_value 'EN'
333
+ c.flag [:nf]
334
+
335
+ c.action do |global_options,options,args|
336
+ print "Table..."
337
+ table = Sycsvpro::Table.new(infile: global_options[:f],
338
+ outfile: global_options[:o],
339
+ df: options[:df],
340
+ nf: options[:nf],
341
+ rows: options[:r],
342
+ header: options[:h],
343
+ key: options[:k],
344
+ cols: options[:c])
345
+ table.execute
346
+ puts "done"
347
+ end
348
+
349
+ end
350
+
306
351
  desc 'Sort rows based on column values'
307
352
  command :sort do |c|
308
353
  c.desc 'Rows to consider'
@@ -21,12 +21,56 @@ module Sycsvpro
21
21
  end
22
22
  end
23
23
 
24
+ def method_missing(id, *args, &block)
25
+ return @row_cols[$1.to_i] if id =~ /^c(\d+)$/
26
+ super
27
+ end
28
+
24
29
  # Returns the header
25
- def process(line)
30
+ def process(line, values = true)
26
31
  return "" if @header_cols.empty?
27
- @header_cols[0] = unstring(line).split(';')
28
- @header_cols.flatten.join(';')
32
+ header_patterns = {}
33
+ @row_cols = unstring(line).split(';')
34
+ if @header_cols[0] == '*'
35
+ @header_cols[0] = @row_cols
36
+ else
37
+ @header_cols.each_with_index do |h,i|
38
+ if h =~ /^c\d+(?:[=~]{,2}).*$/
39
+ if col = eval(h)
40
+ last_eval = $1
41
+ unless @header_cols.index(last_eval) || @header_cols.index(col)
42
+ if values
43
+ @header_cols[i] = (h =~ /^c\d+=~/) ? last_eval : col
44
+ header_patterns[i+1] = h if h =~ /^c\d+[=~+-]{1,2}/
45
+ else
46
+ @header_cols[i] = col if h =~ /^c\d+$/
47
+ end
48
+ end
49
+ end
50
+ else
51
+ @header_cols[i] = h
52
+ end
53
+ end
54
+ end
55
+ header_patterns.each { |i,h| @header_cols.insert(i,h) }
56
+ to_s
57
+ end
58
+
59
+ # Returns @header_cols without pattern
60
+ def clear_header_cols
61
+ @header_cols.flatten.select { |col| col !~ /^c\d+[=~+]{1,2}/ }
29
62
  end
63
+
64
+ # Returns the index of the column
65
+ def column_of(value)
66
+ clear_header_cols.index(value)
67
+ end
68
+
69
+ # Returns the header
70
+ def to_s
71
+ clear_header_cols.join(';')
72
+ end
73
+
30
74
  end
31
75
 
32
76
  end
@@ -0,0 +1,174 @@
1
+ require_relative 'row_filter'
2
+ require_relative 'header'
3
+ require_relative 'dsl'
4
+ require 'date'
5
+
6
+ module Sycsvpro
7
+
8
+ class Table
9
+
10
+ include Dsl
11
+
12
+ # infile contains the data that is operated on
13
+ attr_reader :infile
14
+ # outfile is the file where the result is written to
15
+ attr_reader :outfile
16
+ # filter that is used for rows
17
+ attr_reader :row_filter
18
+ # date format for date operations
19
+ attr_reader :date_format
20
+ # header of the outfile
21
+ attr_reader :header
22
+ # rows of the created table
23
+ attr_reader :rows
24
+
25
+ # Creates a new Table. Options expects :infile, :outfile, :rows and
26
+ # :columns. Optionally a header can be provided. The header can be
27
+ # supplemented with additional column names that are generated due to a
28
+ # arithmetic operation that creates new columns
29
+ # :call-seq:
30
+ # Sycsvpro::Table.new(infile: "in.csv",
31
+ # outfile: "out.csv",
32
+ # df: "%d.%m.%Y",
33
+ # rows: "1,2,BEGINn3>20END",
34
+ # header: "Year,c6,c1",
35
+ # key: "c0=~/\\.(\\d{4})/,c6",
36
+ # cols: "Value:+n1,c2+c3:+n1",
37
+ # nf: "DE").execute
38
+ def initialize(options = {})
39
+ @infile = options[:infile]
40
+ @outfile = options[:outfile]
41
+ @date_format = options[:df] || "%Y-%m-%d"
42
+ @row_filter = RowFilter.new(options[:rows], df: options[:df])
43
+ @header = Header.new(options[:header])
44
+ @keys = options[:key].split(',')
45
+ @cols = options[:cols].split(',')
46
+ @number_format = options[:nf] || 'EN'
47
+ @rows = {}
48
+ end
49
+
50
+ # Retrieves the values from a row as the result of a arithmetic operation
51
+ # with #eval
52
+ def method_missing(id, *args, &block)
53
+ return @columns[$1.to_i] if id =~ /c(\d+)/
54
+ return to_number(@columns[$1.to_i]) if id =~ /n(\d+)/
55
+ return to_date(@columns[$1.to_i]) if id =~ /d(\d+)/
56
+ super
57
+ end
58
+
59
+ # Executes the table and writes the result to the _outfile_
60
+ def execute
61
+ create_table_data
62
+ write_to_file
63
+ end
64
+
65
+ # Create the table
66
+ def create_table_data
67
+ processed_header = false
68
+
69
+ File.open(infile).each_with_index do |line, index|
70
+ line = line.chomp
71
+
72
+ next if line.empty?
73
+
74
+ line = unstring(line).chomp
75
+
76
+ header.process line, processed_header
77
+
78
+ unless processed_header
79
+ processed_header = true
80
+ next
81
+ end
82
+
83
+ next if row_filter.process(line, row: index).nil?
84
+
85
+ @columns = line.split(';')
86
+
87
+ create_row(create_key, line)
88
+ end
89
+
90
+ end
91
+
92
+ # Write table to _outfile_
93
+ def write_to_file
94
+ File.open(outfile, 'w') do |out|
95
+ out.puts header.to_s
96
+ rows.each do |key, row|
97
+ line = [] << row[:key]
98
+ header.clear_header_cols.each_with_index do |col, index|
99
+ next if index < row[:key].size
100
+ line << row[:cols][col]
101
+ end
102
+ out.puts line.flatten.join(';')
103
+ end
104
+ end
105
+ end
106
+
107
+ # Creates a key from the provided key pattern
108
+ def create_key
109
+ key = []
110
+ @keys.each { |k| key << evaluate(k, "") }
111
+ key
112
+ end
113
+
114
+ # Creates a table row based on the column pattern
115
+ # Examples of column patterns
116
+ # * Value:+n1 Adds content of column 1 to Value column
117
+ # * Value:+n1,c2+c3:+n1 Creates a dynamic column and adds column 1 value
118
+ # * c0=~/\\.(\\d{4})/:+n1 Creates dynamic column from regex and adds
119
+ # column 1 value
120
+ def create_row(key, line)
121
+ row = rows[key] || rows[key] = { key: key, cols: Hash.new(0) }
122
+ @cols.each do |col|
123
+ column, formula = col.split(':')
124
+ column = evaluate(column) if column =~ /^c\d+[=~+]/
125
+ row[:cols][column] = eval("#{row[:cols][column]}#{formula}")
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ # Casts a string to an integer or float depending whether the value has a
132
+ # decimal point
133
+ def to_number(value)
134
+ value = convert_to_en(value)
135
+ return value.to_i unless value =~ /\./
136
+ return value.to_f if value =~ /\./
137
+ end
138
+
139
+ # Casts a string to a date
140
+ def to_date(value)
141
+ if value.nil? or value.strip.empty?
142
+ nil
143
+ else
144
+ Date.strptime(value, date_format)
145
+ end
146
+ end
147
+
148
+ # Localize the number to EN
149
+ def convert_to_en(value)
150
+ if @number_format == 'DE'
151
+ value.gsub('.', '_').gsub(',', '.')
152
+ else
153
+ value
154
+ end
155
+ end
156
+
157
+ # Evaluate a formula
158
+ # Example invokation
159
+ # evaluate("n1+n2", 0)
160
+ # evaluate("c1+c2", "failed")
161
+ # evaluate("c0=~/\\.(\\d{4})/", "0")
162
+ def evaluate(formula, fail_result = 0)
163
+ if value = eval(formula)
164
+ last_match = $1
165
+ (formula =~ /^c\d+=~/) ? last_match : value
166
+ else
167
+ fail_result
168
+ end
169
+ end
170
+
171
+ end
172
+
173
+ end
174
+
@@ -1,5 +1,5 @@
1
1
  # Operating csv files
2
2
  module Sycsvpro
3
3
  # Version number of sycsvpro
4
- VERSION = '0.1.3'
4
+ VERSION = '0.1.4'
5
5
  end
data/lib/sycsvpro.rb CHANGED
@@ -13,3 +13,4 @@ require 'sycsvpro/script_list.rb'
13
13
  require 'sycsvpro/inserter.rb'
14
14
  require 'sycsvpro/sorter.rb'
15
15
  require 'sycsvpro/aggregator.rb'
16
+ require 'sycsvpro/table.rb'
@@ -0,0 +1,79 @@
1
+ require 'sycsvpro/header'
2
+
3
+ module Sycsvpro
4
+
5
+ describe Header do
6
+
7
+ it "should create a header from '*,A,B'" do
8
+ header = Header.new("*,A,B")
9
+
10
+ header.process("a;b;c").should eq 'a;b;c;A;B'
11
+ end
12
+
13
+ it "should create a header form 'A,c6,c1'" do
14
+ header = Header.new("A,c6,c1")
15
+
16
+ header.process("a0;a1;a2;a3;a4;a5;a6").should eq "A;a6;a1"
17
+ header.process("x0;x1;x2;x3;x4;x5;x6").should eq "A;a6;a1"
18
+ end
19
+
20
+ it "should not create columns on arithmetic operation" do
21
+ header = Header.new("A,c1,c2+c3")
22
+
23
+ header.process("h0;h1;h2;h3;h4;h5", false).should eq "A;h1"
24
+ header.process("a0;a1;a2;a3;a4;a5").should eq "A;h1;a2a3"
25
+ header.process("b0;b1;b2;b3;b4;b5").should eq "A;h1;a2a3;b2b3"
26
+ header.process("c0;a1;a2;c3;a4;a5").should eq "A;h1;a2a3;b2b3;a2c3"
27
+
28
+ end
29
+
30
+ it "should create a header from 'A,c1,c2+c3'" do
31
+ header = Header.new("A,c1,c2+c3")
32
+
33
+ header.process("a0;a1;a2;a3;a4;a5").should eq "A;a1;a2a3"
34
+ header.process("b0;b1;b2;b3;b4;b5").should eq "A;a1;a2a3;b2b3"
35
+ header.process("c0;a1;a2;c3;a4;a5").should eq "A;a1;a2a3;b2b3;a2c3"
36
+ end
37
+
38
+ it "should create a header form 'A,c1,c2+'-'+c3'" do
39
+ header = Header.new("A,c1,c2+'-'+c3")
40
+
41
+ header.process("a0;a1;a2;a3;a4;a5").should eq "A;a1;a2-a3"
42
+ header.process("b0;b1;b2;b3;b4;b5").should eq "A;a1;a2-a3;b2-b3"
43
+ header.process("c0;a1;a2;a3;c4;c5").should eq "A;a1;a2-a3;b2-b3"
44
+ end
45
+
46
+ it "should create a header from 'c4,A,c0=~/\.(\d{4})/,c1,B'" do
47
+ header = Header.new("c4,A,c0=~/\\.(\\d{4})/,c1,B")
48
+
49
+ header.process("a0;a1;a2;a3;a4;a5").should eq "a4;A;a1;B"
50
+ header.process("1.1.2012;b1;b2;b3;b4;b5").should eq "a4;A;2012;a1;B"
51
+ header.process("3.4.2013;c1;c2;c3;c4;c5").should eq "a4;A;2012;2013;a1;B"
52
+ header.process("5.5.2012;d1;d2;d3;d4;d5").should eq "a4;A;2012;2013;a1;B"
53
+ end
54
+
55
+ it "should return the header" do
56
+ header = Header.new("c4,A,c0=~/\\.(\\d{4})/,c1,B")
57
+
58
+ header.process("a0;a1;a2;a3;a4;a5").should eq "a4;A;a1;B"
59
+ header.process("1.1.2012;b1;b2;b3;b4;b5").should eq "a4;A;2012;a1;B"
60
+ header.process("3.4.2013;c1;c2;c3;c4;c5").should eq "a4;A;2012;2013;a1;B"
61
+ header.process("5.5.2012;d1;d2;d3;d4;d5").should eq "a4;A;2012;2013;a1;B"
62
+
63
+ header.to_s.should eq "a4;A;2012;2013;a1;B"
64
+ end
65
+
66
+ it "should return the index of the coloum" do
67
+ header = Header.new("c4,A,c0=~/\\.(\\d{4})/,c1,B")
68
+
69
+ header.process("a0;a1;a2;a3;a4;a5").should eq "a4;A;a1;B"
70
+ header.column_of("a1").should eq 2
71
+ header.process("1.1.2012;b1;b2;b3;b4;b5").should eq "a4;A;2012;a1;B"
72
+ header.column_of("a1").should eq 3
73
+ header.process("3.4.2013;c1;c2;c3;c4;c5").should eq "a4;A;2012;2013;a1;B"
74
+ header.column_of("B").should eq 5
75
+ end
76
+
77
+ end
78
+
79
+ end
@@ -0,0 +1,65 @@
1
+ require 'sycsvpro/table'
2
+
3
+ module Sycsvpro
4
+
5
+ describe Table do
6
+ before do
7
+ @in_file = File.join(File.dirname(__FILE__), "files/table.csv")
8
+ @out_file = File.join(File.dirname(__FILE__), "files/out.csv")
9
+ end
10
+
11
+ it "should create headings from String and column values" do
12
+ Sycsvpro::Table.new(infile: @in_file,
13
+ outfile: @out_file,
14
+ header: "Year,c6,c1",
15
+ key: "c0=~/\\.(\\d{4})/,c6",
16
+ cols: "Value:+n1").execute
17
+
18
+ result = [ "Year;Country;Value",
19
+ "2013;AT;53.7",
20
+ "2014;DE;21.0",
21
+ "2014;AT;20.5" ]
22
+
23
+ File.open(@out_file).each_with_index do |line, index|
24
+ line.chomp.should eq result[index]
25
+ end
26
+ end
27
+
28
+ it "should create headings from operation" do
29
+ Sycsvpro::Table.new(infile: @in_file,
30
+ outfile: @out_file,
31
+ header: "Year,c6,c1,c2+c3",
32
+ key: "c0=~/\\.(\\d{4})/,c6",
33
+ cols: "Value:+n1,c2+c3:+n1").execute
34
+
35
+ result = [ "Year;Country;Value;A1;B2;B4",
36
+ "2013;AT;53.7;20.5;0;33.2",
37
+ "2014;DE;21.0;0;21.0;0",
38
+ "2014;AT;20.5;20.5;0;0" ]
39
+
40
+ File.open(@out_file).each_with_index do |line, index|
41
+ line.chomp.should eq result[index]
42
+ end
43
+
44
+ end
45
+
46
+ it "should create key from operation" do
47
+ Sycsvpro::Table.new(infile: @in_file,
48
+ outfile: @out_file,
49
+ header: "c4,c5,c0=~/\\.(\\d{4})/",
50
+ key: "c4,c5",
51
+ cols: "c0=~/\\.(\\d{4})/:+n1").execute
52
+
53
+ result = [ "Customer Name;Customer-ID;2013;2014",
54
+ "Hank;133;20.5;20.5",
55
+ "Hans;234;0;21.0",
56
+ "Jack;432;33.2;0" ]
57
+
58
+ File.open(@out_file).each_with_index do |line, index|
59
+ line.chomp.should eq result[index]
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sycsvpro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-06-21 00:00:00.000000000 Z
12
+ date: 2014-06-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -148,6 +148,7 @@ files:
148
148
  - lib/sycsvpro/script_creator.rb
149
149
  - lib/sycsvpro/script_list.rb
150
150
  - lib/sycsvpro/sorter.rb
151
+ - lib/sycsvpro/table.rb
151
152
  - lib/sycsvpro/version.rb
152
153
  - spec/sycsvpro/aggregator_spec.rb
153
154
  - spec/sycsvpro/allocator_spec.rb
@@ -161,12 +162,14 @@ files:
161
162
  - spec/sycsvpro/files/profile.rb
162
163
  - spec/sycsvpro/files/script.rb
163
164
  - spec/sycsvpro/files/unsert.ins
165
+ - spec/sycsvpro/header_spec.rb
164
166
  - spec/sycsvpro/inserter_spec.rb
165
167
  - spec/sycsvpro/mapper_spec.rb
166
168
  - spec/sycsvpro/profiler_spec.rb
167
169
  - spec/sycsvpro/row_filter_spec.rb
168
170
  - spec/sycsvpro/script_list_spec.rb
169
171
  - spec/sycsvpro/sorter_spec.rb
172
+ - spec/sycsvpro/table_spec.rb
170
173
  - sycsvpro.gemspec
171
174
  - sycsvpro.rdoc
172
175
  homepage: https://github.com/sugaryourcoffee/syc-svpro