sycsvpro 0.1.3 → 0.1.4

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.
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