jetel 0.0.6 → 0.0.7

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