jetel 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 51f8e1d58f1480832113887664c59bc1fee6f142
4
- data.tar.gz: c144f84c9664e281299bdc9016e81411a5f6c7b3
3
+ metadata.gz: 037ccf5c15decd72caae4f48d65c8d2525483cd2
4
+ data.tar.gz: 4bbf16791f9c6bbca7bfd52528dd2067e81aa56c
5
5
  SHA512:
6
- metadata.gz: 8fde78020fdcadbf6b6eb6fe5a172607c3177f3a2099ee0de095748d7896d1811c0cf8c27ab9cef16fa2c67a244367c689aafa5b8cf681629949b320777b7f5e
7
- data.tar.gz: fecde5a4930c26d987a4ff14b93755eb6401304f52944b3cf2ffb2e81fe82bdaf5d867c38f1ebd741def9c9b9fa7305bcd93541c80ac7ffe64ba541d58da698e
6
+ metadata.gz: 58f2c194d83231c744c26fc28208812ad099e4accb635a84e4fc25f8091556c63043c862cdee054b88042427315994ea7a3ba5d62d81189055869d05d1c03358
7
+ data.tar.gz: e922b1cdea6f34d4abcd45fd20aa2a019c48e9a5ee9562ebfe7ceacbfa4a903c22e6a5a972342f3b82b3965cf4aba5167938e0048087d9e1d27e4c67665377be
data/.idea/vcs.xml CHANGED
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <project version="4">
3
3
  <component name="VcsDirectoryMappings">
4
- <mapping directory="$PROJECT_DIR$" vcs="Git" />
4
+ <mapping directory="" vcs="Git" />
5
5
  </component>
6
6
  </project>
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in untitled.gemspec
4
4
  gemspec
5
+
6
+ # gem "csv2psql", :path => '/Users/tomaskorcak/dev/csv2psql'
data/Gemfile.lock CHANGED
@@ -3,6 +3,7 @@ PATH
3
3
  specs:
4
4
  jetel (0.0.6)
5
5
  activesupport
6
+ csv2psql
6
7
  gli
7
8
  i18n
8
9
  json_pure
@@ -14,6 +15,17 @@ PATH
14
15
  terminal-table
15
16
  zip
16
17
 
18
+ PATH
19
+ remote: /Users/tomaskorcak/dev/csv2psql
20
+ specs:
21
+ csv2psql (0.0.15)
22
+ gli (~> 2.13, >= 2.13.2)
23
+ json_pure (~> 1.8, >= 1.8.3)
24
+ lru (~> 0.1, >= 0.1.0)
25
+ multi_json (~> 1.11, >= 1.11.2)
26
+ rake (~> 10.4, >= 10.4.2)
27
+ terminal-table (~> 1.5, >= 1.5.2)
28
+
17
29
  GEM
18
30
  remote: https://rubygems.org/
19
31
  specs:
@@ -42,6 +54,7 @@ GEM
42
54
  i18n (0.7.0)
43
55
  json (1.8.3)
44
56
  json_pure (1.8.3)
57
+ lru (0.1.0)
45
58
  mime-types (2.6.2)
46
59
  mini_portile (0.6.2)
47
60
  minitest (5.8.2)
@@ -105,6 +118,7 @@ PLATFORMS
105
118
  DEPENDENCIES
106
119
  bundler (~> 1.5)
107
120
  coveralls
121
+ csv2psql!
108
122
  jetel!
109
123
  rake
110
124
  rspec
data/README.md CHANGED
@@ -49,7 +49,15 @@ COMMANDS
49
49
  └── test
50
50
  ```
51
51
 
52
- ## Executables
52
+ ## Examples
53
+
54
+ **Plays nicely with [csv2psql](https://github.com/korczis/csv2psql)**
55
+
56
+ ```
57
+ $ csv2psql convert -t --drop-table --create-table -t afrinic tmp/Ip/afrinic/transformed/delegated-afrinic-latest | psql -h 127.0.0.1 -U jetel
58
+
59
+ $ csv2psql convert -t --drop-table --create-table -t apnic tmp/Ip/apnic/transformed/delegated-apnic-latest | psql -h 127.0.0.1 -U jetel
60
+ ```
53
61
 
54
62
  ### Rake
55
63
 
data/bin/jetel CHANGED
@@ -1,5 +1,3 @@
1
1
  #! /usr/bin/env ruby
2
2
 
3
- require_relative '../lib/jetel'
4
-
5
3
  require_relative '../lib/jetel/cli/cli'
data/jetel.gemspec CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.require_paths = ['lib']
21
21
 
22
22
  spec.add_dependency 'activesupport'
23
+ spec.add_dependency 'csv2psql', '~> 0.0.16'
23
24
  spec.add_dependency 'gli'
24
25
  spec.add_dependency 'i18n'
25
26
  spec.add_dependency 'json_pure'
data/lib/jetel/cli/cli.rb CHANGED
@@ -1,5 +1,3 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require 'pathname'
4
-
5
3
  require_relative 'app'
@@ -2,10 +2,6 @@
2
2
 
3
3
  require 'pp'
4
4
 
5
- require_relative '../../version'
6
-
7
- require_relative '../shared'
8
-
9
5
  require_relative '../../config/config'
10
6
 
11
7
  desc 'Show config'
@@ -2,10 +2,6 @@
2
2
 
3
3
  require 'terminal-table'
4
4
 
5
- require_relative '../../version'
6
-
7
- require_relative '../shared'
8
-
9
5
  require_relative '../../modules/modules'
10
6
 
11
7
  MODULES = Jetel::Modules.modules
@@ -14,7 +10,14 @@ MODULES_ACTIONS = {
14
10
  download: nil,
15
11
  extract: nil,
16
12
  transform: nil,
17
- load: nil
13
+ load: {
14
+ params: [{
15
+ desc: 'Column type',
16
+ default_value: nil,
17
+ arg_name: 'column-name=column-type',
18
+ flag: [:column_type]
19
+ }]
20
+ }
18
21
  }
19
22
 
20
23
  # Gets module name
@@ -32,9 +35,21 @@ end
32
35
  # @param action_command [String] Nested command name
33
36
  # @param action_desc [String] Nested command action description
34
37
  # @return [Object] Return value
35
- def register_module_action(c, _m, action_command, action_desc, &block)
36
- c.desc(action_desc)
37
- c.command(action_command) do |cmd|
38
+ def register_module_action(c, modul, name, spec, &block)
39
+ module_name = modul[:name]
40
+ action_description = spec && spec[:description] || "#{name} #{module_name}"
41
+
42
+ params = spec && spec[:params] || []
43
+
44
+
45
+ c.desc(action_description)
46
+ c.command(name) do |cmd|
47
+ params.each do |param|
48
+ param.each do |name, val|
49
+ cmd.send name, val
50
+ end
51
+ end
52
+
38
53
  cmd.action(&block)
39
54
  end
40
55
  end
@@ -45,15 +60,12 @@ def register_module(m)
45
60
  desc "Module #{module_name}"
46
61
  command(m[:name], m[:class_name]) do |c|
47
62
  module_instance = m[:klass].new
48
- module_name = m[:name]
49
63
 
50
- MODULES_ACTIONS.each do |k, v|
51
- next unless module_instance.respond_to?(k)
64
+ MODULES_ACTIONS.each do |name, spec|
65
+ next unless module_instance.respond_to?(name)
52
66
 
53
- action_name = k
54
- action_description = v || "#{action_name} #{module_name}"
55
- register_module_action(c, m, action_name, action_description) do |global_options, options, args|
56
- module_instance.send(k, global_options, options, args)
67
+ register_module_action(c, m, name, spec) do |global_options, options, args|
68
+ module_instance.send(name, global_options, options, args)
57
69
  end
58
70
  end
59
71
  end
@@ -2,16 +2,19 @@
2
2
 
3
3
  require_relative '../config/config'
4
4
 
5
+ require_relative '../helpers/helpers'
5
6
  require_relative '../loaders/loaders'
6
7
 
8
+ require 'erb'
7
9
  require 'i18n'
10
+ require 'ostruct'
8
11
 
9
12
  module Jetel
10
13
  module Helper
11
14
  class << self
12
15
  def target_dir(modul, dir, source)
13
- klass = I18n.transliterate(modul.class.name.split('::').last).gsub(/[^0-9a-z_\-]/i, '_')
14
- source_name = I18n.transliterate(source[:name]).gsub(/[^0-9a-z_\-]/i, '_')
16
+ klass = modul.class.name.split('::').last
17
+ source_name = Helper.sanitize(source[:name])
15
18
  File.join(dir || Config[:DATA_DIRECTORY], klass, source_name)
16
19
  end
17
20
 
@@ -24,6 +27,19 @@ module Jetel
24
27
 
25
28
  res[:klass].new(uri)
26
29
  end
30
+
31
+ def erb(template, vars)
32
+ ERB.new(template).result(OpenStruct.new(vars).instance_eval { binding })
33
+ end
34
+
35
+ def erb_template(file, vars)
36
+ template = File.open(file, 'r').read
37
+ ERB.new(template).result(OpenStruct.new(vars).instance_eval { binding })
38
+ end
39
+
40
+ def sanitize(str)
41
+ I18n.transliterate(str).gsub(/[^0-9a-z_\-]/i, '_')
42
+ end
27
43
  end
28
44
  end
29
45
  end
@@ -1,6 +1,10 @@
1
1
  require_relative '../loader'
2
2
 
3
+ require_relative '../../helpers/helpers'
4
+
3
5
  require 'pg'
6
+ require 'csv2psql/convert/convert'
7
+ require 'csv2psql/analyzer/analyzer'
4
8
 
5
9
  module Jetel
6
10
  module Loaders
@@ -31,6 +35,83 @@ module Jetel
31
35
 
32
36
  @client = PG.connect(opts)
33
37
  end
38
+
39
+ def load(modul, source, file, opts)
40
+ super
41
+
42
+ convert_opts = {
43
+ :l => 1_000,
44
+ :skip => 0,
45
+ :header => true
46
+ }
47
+
48
+ schema_list = Csv2Psql::Convert.generate_schema([file], convert_opts)
49
+ _file_name, schema = schema_list.first
50
+
51
+ return nil if schema.nil?
52
+
53
+ analyzer = Csv2Psql::Analyzer.new
54
+ column_types = (opts['column_type'] && opts['column_type'].split(/[;,]/)) || []
55
+ column_types.each do |ct|
56
+ name, type = ct.split('=')
57
+
58
+ columns = schema[:columns] || []
59
+ column = columns.find do |k, v|
60
+ k.downcase == name
61
+ end
62
+
63
+ analyzer_type = analyzer.analyzers.find do |spec|
64
+ spec[:class].name.split('::').last.downcase == type.downcase
65
+ end
66
+
67
+ type_val = analyzer_type ? analyzer_type[:class].const_get(:TYPE) : type
68
+
69
+ if column
70
+ columns[column[0]] = {
71
+ type: type_val.to_sym,
72
+ null: true
73
+ }
74
+ end
75
+ end
76
+
77
+ ctx = {
78
+ :ctx => {
79
+ :table => Helper.sanitize(source[:name]).downcase,
80
+ :columns => schema[:columns],
81
+ :source => source,
82
+ :module => modul,
83
+ :file => File.absolute_path(modul.transformed_file(source, opts))
84
+ }
85
+ }
86
+
87
+ sql = Helper.erb_template(File.expand_path('../sql/schema.sql.erb', __FILE__), ctx)
88
+ sql.gsub!("\n\n", "\n")
89
+ puts sql
90
+ @client.exec(sql)
91
+
92
+ sql = Helper.erb_template(File.expand_path('../sql/copy.sql.erb', __FILE__), ctx)
93
+ sql.gsub!("\n\n", "\n")
94
+ puts sql
95
+ @client.exec(sql)
96
+
97
+ file = File.open(ctx[:ctx][:file], 'r')
98
+ while !file.eof?
99
+ # Add row to copy data
100
+ @client.put_copy_data(file.readline)
101
+ end
102
+
103
+ # We are done adding copy data
104
+ @client.put_copy_end
105
+
106
+ # Display any error messages
107
+ while res = @client.get_result
108
+ if e_message = res.error_message
109
+ p e_message
110
+ end
111
+ end
112
+
113
+ sql
114
+ end
34
115
  end
35
116
  end
36
117
  end
@@ -0,0 +1,6 @@
1
+ COPY "<%= ctx[:table] %>"
2
+ FROM STDIN
3
+ WITH DELIMITER ','
4
+ CSV HEADER
5
+ ;
6
+
@@ -0,0 +1,12 @@
1
+ DROP TABLE IF EXISTS "<%= ctx[:table] %>";
2
+
3
+ CREATE TABLE "<%= ctx[:table] %>"
4
+ (
5
+ <% ctx[:columns].each_with_index do |item, index| %>
6
+ <%= item %> TEXT<%= ", " if index < ctx[:columns].length - 1%>
7
+ <% end %>
8
+ )
9
+ WITH (
10
+ OIDS=FALSE
11
+ );
12
+
@@ -0,0 +1,2 @@
1
+ DROP TABLE IF EXISTS "<%= ctx[:table] %>";
2
+
@@ -0,0 +1,2 @@
1
+ -- Table: "<%= ctx[:table] + "\n" %>"
2
+
@@ -0,0 +1,12 @@
1
+ DROP TABLE IF EXISTS "<%= ctx[:table] %>";
2
+
3
+ CREATE TABLE "<%= ctx[:table] %>"
4
+ (
5
+ <% ctx[:columns].each_with_index do |item, index| %>
6
+ "<%= Csv2Psql::Generator.sanitize_header(item[0]) %>" <%= item[1][:type].upcase %><% if !item[1][:null] %> NOT NULL<% end %><%= ', ' if index < ctx[:columns].length - 1%>
7
+ <% end %>
8
+ )
9
+ WITH (
10
+ OIDS=FALSE
11
+ );
12
+
@@ -0,0 +1,2 @@
1
+ TRUNCATE "<%= ctx[:table] %>";
2
+
@@ -10,41 +10,45 @@ require_relative '../../modules/module'
10
10
  module Jetel
11
11
  module Modules
12
12
  class Ip < Module
13
- SOURCES = [
14
- {
15
- name: 'afrinic',
16
- url: 'ftp://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-latest'
17
- },
18
- {
19
- name: 'apnic',
20
- url: 'ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-latest'
21
- },
22
- {
23
- name: 'arin',
24
- url: 'ftp://ftp.arin.net/pub/stats/arin/delegated-arin-latest'
25
- },
26
- {
27
- name: 'lacnic',
28
- url: 'ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-latest'
29
- },
30
- {
31
- name: 'ripencc',
32
- url: 'ftp://ftp.ripe.net/ripe/stats/delegated-ripencc-latest'
33
- },
34
- {
35
- name: 'iana',
36
- url: 'ftp://ftp.apnic.net/pub/stats/iana/delegated-iana-latest'
37
- }
38
- ]
13
+ class << self
14
+ def sources
15
+ [
16
+ {
17
+ name: 'afrinic',
18
+ url: 'ftp://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-latest'
19
+ },
20
+ {
21
+ name: 'apnic',
22
+ url: 'ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-latest'
23
+ },
24
+ {
25
+ name: 'arin',
26
+ url: 'ftp://ftp.arin.net/pub/stats/arin/delegated-arin-latest'
27
+ },
28
+ {
29
+ name: 'lacnic',
30
+ url: 'ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-latest'
31
+ },
32
+ {
33
+ name: 'ripencc',
34
+ url: 'ftp://ftp.ripe.net/ripe/stats/delegated-ripencc-latest'
35
+ },
36
+ {
37
+ name: 'iana',
38
+ url: 'ftp://ftp.apnic.net/pub/stats/iana/delegated-iana-latest'
39
+ }
40
+ ]
41
+ end
42
+ end
39
43
 
40
44
  def download(global_options, options, args)
41
- SOURCES.pmap do |source|
45
+ self.class.sources.pmap do |source|
42
46
  download_source(source, global_options.merge(options))
43
47
  end
44
48
  end
45
49
 
46
50
  def extract(global_options, options, args)
47
- SOURCES.pmap do |source|
51
+ self.class.sources.pmap do |source|
48
52
  downloaded_file = downloaded_file(source, global_options.merge(options))
49
53
  dest_dir = extract_dir(source, global_options.merge(options))
50
54
 
@@ -56,7 +60,7 @@ module Jetel
56
60
  end
57
61
 
58
62
  def transform(global_options, options, args)
59
- SOURCES.pmap do |source|
63
+ self.class.sources.pmap do |source|
60
64
  opts = global_options.merge(options)
61
65
 
62
66
  extracted_file = extracted_file(source, opts)
@@ -89,18 +93,6 @@ module Jetel
89
93
  end
90
94
  end
91
95
  end
92
-
93
- def load(global_options, options, args)
94
- SOURCES.map do |source|
95
- opts = global_options.merge(options)
96
-
97
- transformed_file = transformed_file(source, opts)
98
-
99
- loader = Helper.get_loader(opts['data_loader'])
100
-
101
- loader.load(self, source, transformed_file, opts)
102
- end
103
- end
104
96
  end
105
97
  end
106
98
  end
@@ -10,23 +10,27 @@ require_relative '../../modules/module'
10
10
  module Jetel
11
11
  module Modules
12
12
  class Iso3166 < Module
13
- SOURCES = [
14
- {
15
- name: 'iso366',
16
- filename_extracted: 'IP2LOCATION-ISO3166-2.CSV',
17
- filename_transformed: 'IP2LOCATION-ISO3166-2.CSV',
18
- url: 'http://www.ip2location.com/downloads/Hi3sL9bnXfe/IP2LOCATION-ISO3166-2.ZIP'
19
- }
20
- ]
13
+ class << self
14
+ def sources
15
+ [
16
+ {
17
+ name: 'iso366',
18
+ filename_extracted: 'IP2LOCATION-ISO3166-2.CSV',
19
+ filename_transformed: 'IP2LOCATION-ISO3166-2.CSV',
20
+ url: 'http://www.ip2location.com/downloads/Hi3sL9bnXfe/IP2LOCATION-ISO3166-2.ZIP'
21
+ }
22
+ ]
23
+ end
24
+ end
21
25
 
22
26
  def download(global_options, options, args)
23
- SOURCES.pmap do |source|
27
+ self.class.sources.pmap do |source|
24
28
  download_source(source, global_options.merge(options))
25
29
  end
26
30
  end
27
31
 
28
32
  def extract(global_options, options, args)
29
- SOURCES.pmap do |source|
33
+ self.class.sources.pmap do |source|
30
34
  downloaded_file = downloaded_file(source, global_options.merge(options))
31
35
  dest_dir = extract_dir(source, global_options.merge(options))
32
36
 
@@ -46,7 +50,7 @@ module Jetel
46
50
  end
47
51
 
48
52
  def transform(global_options, options, args)
49
- SOURCES.pmap do |source|
53
+ self.class.sources.pmap do |source|
50
54
  opts = global_options.merge(options)
51
55
 
52
56
  extracted_file = extracted_file(source, opts)
@@ -74,9 +78,6 @@ module Jetel
74
78
  end
75
79
  end
76
80
  end
77
-
78
- def load(global_options, options, args)
79
- end
80
81
  end
81
82
  end
82
83
  end
@@ -10,8 +10,8 @@ module Jetel
10
10
 
11
11
  class << self
12
12
  def target_dir(modul, source, dir, *path)
13
- klass = I18n.transliterate(modul.class.name.split('::').last).gsub(/[^0-9a-z_\-]/i, '_')
14
- source_name = I18n.transliterate(source[:name]).gsub(/[^0-9a-z_\-]/i, '_')
13
+ klass = modul.class.name.split('::').last
14
+ source_name = Helper.sanitize(source[:name])
15
15
  File.join(dir.kind_of?(String) ? dir : dir['download_dir'] || Config[:DATA_DIRECTORY], klass, source_name, path)
16
16
  end
17
17
 
@@ -75,6 +75,26 @@ module Jetel
75
75
  def transformed_file(source, opts)
76
76
  Module.transformed_file(self, source, opts)
77
77
  end
78
+
79
+ def load(global_options, options, args)
80
+ sources = self.class.sources
81
+ if args.length > 0
82
+ args = args.map(&:downcase)
83
+ sources = sources.select do |source|
84
+ args.index(source[:name].downcase)
85
+ end
86
+ end
87
+
88
+ sources.pmap(8) do |source|
89
+ opts = global_options.merge(options)
90
+
91
+ transformed_file = transformed_file(source, opts)
92
+
93
+ loader = Helper.get_loader(opts['data_loader'])
94
+
95
+ loader.load(self, source, transformed_file, opts)
96
+ end
97
+ end
78
98
  end
79
99
  end
80
100
  end