ckan_cli 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +57 -0
- data/Rakefile +6 -0
- data/bin/setup +8 -0
- data/ckan_cli.gemspec +41 -0
- data/exe/ckancli.rb +18 -0
- data/lib/ckancli/cli.rb +43 -0
- data/lib/ckancli/command.rb +121 -0
- data/lib/ckancli/commands/upload.rb +445 -0
- data/lib/ckancli/version.rb +3 -0
- data/lib/ckancli.rb +6 -0
- data/lib/ckanclient/ckanclient.rb +129 -0
- metadata +252 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0d5686c7f8bd8d3fe87ac9fc4de600667ecd455fa773e383f8af10410d237dab
|
4
|
+
data.tar.gz: 0203fd33391a22b27eefcabc8f663b75080df194673800383fe2e563f448096f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9f2b823b83f3a9c9570fe45db2b797012e9b32d77f2db22b52d63db1bc1f7dfc1d2ffd3aa89ca6d019995b07f25f8dfaf7f43f3ffdf0b814cc409cfb018b584c
|
7
|
+
data.tar.gz: 9597fcf8f12824676a93c23b92496e8e282ff847ac3b41657915873e024b969f8b276d3aa17cfa2d5a4a91682e5a8eeb27f4e20064beb64b7a29db8500072005
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 datagovlvlv
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# [CKAN CLI](https://github.com/datagovlv/ckan_cli)
|
2
|
+
|
3
|
+
> CKAN on your command line. Use your terminal to validate and publish resources to CKAN. Works with CKAN API.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'ckan_cli'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install ckan_cli
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Run it:
|
24
|
+
|
25
|
+
```shell
|
26
|
+
$ ckancli
|
27
|
+
```
|
28
|
+
|
29
|
+
For instance, to publish to CKAN all CSV files in folder do
|
30
|
+
|
31
|
+
```shell
|
32
|
+
$ ckancli upload -d C:\my_csv_collection\ -c config.json -r resource.json
|
33
|
+
```
|
34
|
+
|
35
|
+
To see help do
|
36
|
+
|
37
|
+
```shell
|
38
|
+
$ ckancli help
|
39
|
+
```
|
40
|
+
|
41
|
+
## Development
|
42
|
+
|
43
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
44
|
+
|
45
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
46
|
+
|
47
|
+
## Contributing
|
48
|
+
|
49
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/datagovlv/ckan_cli. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
50
|
+
|
51
|
+
## Code of Conduct
|
52
|
+
|
53
|
+
Everyone interacting in the CKAN CLI project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/datagovlv/ckan_cli/blob/master/CODE_OF_CONDUCT.md).
|
54
|
+
|
55
|
+
## Copyright
|
56
|
+
|
57
|
+
Copyright (c) 2019 datagovlv. See [MIT License](LICENSE.txt) for further details.
|
data/Rakefile
ADDED
data/bin/setup
ADDED
data/ckan_cli.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "ckancli/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ckan_cli"
|
8
|
+
spec.license = "MIT"
|
9
|
+
spec.version = Ckancli::VERSION
|
10
|
+
spec.authors = ["datagovlv"]
|
11
|
+
spec.email = ["dati@varam.gov.lv"]
|
12
|
+
spec.summary = "CKAN command line interface."
|
13
|
+
spec.description = "CKAN CLI with built-in CSV file validation and CKAN API integration."
|
14
|
+
spec.homepage = "https://github.com/datagovlv/ckan_cli"
|
15
|
+
|
16
|
+
spec.files = Dir['lib/**/*']
|
17
|
+
spec.files += Dir['bin/*']
|
18
|
+
spec.files += Dir['exe/*']
|
19
|
+
spec.files += Dir['ckan_cli.gemspec', 'README.md', 'LICENSE.txt', 'Rakefile']
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.required_ruby_version = '>= 2.0.0'
|
25
|
+
|
26
|
+
spec.add_dependency "tty-color", "~> 0.4"
|
27
|
+
spec.add_dependency "tty-logger", "~> 0.1.0"
|
28
|
+
spec.add_dependency "tty-platform", "~> 0.2.0"
|
29
|
+
spec.add_dependency "tty-progressbar", "~> 0.16.0"
|
30
|
+
spec.add_dependency "tty-spinner", "~> 0.9.0"
|
31
|
+
spec.add_dependency "tty-which", "~> 0.4"
|
32
|
+
spec.add_dependency "pastel", "~> 0.7.2"
|
33
|
+
spec.add_dependency "thor", "~> 0.20.0"
|
34
|
+
spec.add_dependency "csvlint", "~> 0.4.0"
|
35
|
+
spec.add_dependency "rest-client", "~> 2.0.2"
|
36
|
+
spec.add_dependency "mail", "~> 2.7.1"
|
37
|
+
|
38
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
39
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
40
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
41
|
+
end
|
data/exe/ckancli.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
lib_path = File.expand_path('../lib', __dir__)
|
5
|
+
$:.unshift(lib_path) if !$:.include?(lib_path)
|
6
|
+
require 'ckancli/cli'
|
7
|
+
|
8
|
+
Signal.trap('INT') do
|
9
|
+
warn("\n#{caller.join("\n")}: interrupted")
|
10
|
+
exit(1)
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
Ckancli::CLI.start
|
15
|
+
rescue Ckancli::CLI::Error => err
|
16
|
+
puts "ERROR: #{err.message}"
|
17
|
+
exit 1
|
18
|
+
end
|
data/lib/ckancli/cli.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module Ckancli
|
6
|
+
# Handle the application command line parsing
|
7
|
+
# and the dispatch to various command objects
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
class CLI < Thor
|
11
|
+
class_option :dir, type: :string, required: true, desc: 'Path to CSV files. Full path to directory or URL.', aliases: '-d'
|
12
|
+
class_option :resourceconfig, type: :string, required: true, desc: 'CKAN resource metadata file (JSON). Absolute or relative path.', aliases: '-r'
|
13
|
+
class_option :configuration, type: :string, required: true, desc: 'Global configuration file (JSON). Absolute or relative path.', aliases: '-c'
|
14
|
+
class_option :validationschema, type: :string, required: false, desc: 'Validation schema (JSON). Absolute or relative path.', aliases: '-v'
|
15
|
+
class_option :datasetconfig, type: :string, required: false, desc: 'CKAN dataset metadata file (JSON). Absolute or relative path.', aliases: '-p'
|
16
|
+
class_option :updatemodified, type: :boolean, required: false, desc: 'Update modified date of resource.', aliases: '-m'
|
17
|
+
class_option :ignoreextension, type: :boolean, require: false, desc: 'Ignore file extensions (process all files not only CSV).', aliases: '-i'
|
18
|
+
class_option :overwrite, type: :boolean, require: false, desc: 'Overwrite resource if existing is found by identifier.', aliases: '-w'
|
19
|
+
|
20
|
+
# Error raised by this runner
|
21
|
+
Error = Class.new(StandardError)
|
22
|
+
|
23
|
+
# desc 'version', 'CKAN CLI version'
|
24
|
+
# def version
|
25
|
+
# require_relative 'version'
|
26
|
+
# puts "v#{Ckancli::VERSION}"
|
27
|
+
# end
|
28
|
+
# map %w(--version -v) => :version
|
29
|
+
|
30
|
+
desc 'upload', 'Processes CSV files and uploads to CKAN API. '
|
31
|
+
method_option :help, aliases: '-h', type: :boolean,
|
32
|
+
desc: 'Display usage information'
|
33
|
+
def upload(*)
|
34
|
+
if options[:help]
|
35
|
+
invoke :help, ['upload']
|
36
|
+
else
|
37
|
+
require_relative 'commands/upload'
|
38
|
+
Ckancli::Commands::Upload.new(options).execute
|
39
|
+
end
|
40
|
+
end
|
41
|
+
default_task :help
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Ckancli
|
6
|
+
class Command
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :command, :run
|
10
|
+
|
11
|
+
# Execute this command
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
def execute(*)
|
15
|
+
raise(
|
16
|
+
NotImplementedError,
|
17
|
+
"#{self.class}##{__method__} must be implemented"
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
# The external commands runner
|
22
|
+
#
|
23
|
+
# @see http://www.rubydoc.info/gems/tty-command
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def command(**options)
|
27
|
+
require 'tty-command'
|
28
|
+
TTY::Command.new(options)
|
29
|
+
end
|
30
|
+
|
31
|
+
# The cursor movement
|
32
|
+
#
|
33
|
+
# @see http://www.rubydoc.info/gems/tty-cursor
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
def cursor
|
37
|
+
require 'tty-cursor'
|
38
|
+
TTY::Cursor
|
39
|
+
end
|
40
|
+
|
41
|
+
# Open a file or text in the user's preferred editor
|
42
|
+
#
|
43
|
+
# @see http://www.rubydoc.info/gems/tty-editor
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def editor
|
47
|
+
require 'tty-editor'
|
48
|
+
TTY::Editor
|
49
|
+
end
|
50
|
+
|
51
|
+
# File manipulation utility methods
|
52
|
+
#
|
53
|
+
# @see http://www.rubydoc.info/gems/tty-file
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
def generator
|
57
|
+
require 'tty-file'
|
58
|
+
TTY::File
|
59
|
+
end
|
60
|
+
|
61
|
+
# Terminal output paging
|
62
|
+
#
|
63
|
+
# @see http://www.rubydoc.info/gems/tty-pager
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
def pager(**options)
|
67
|
+
require 'tty-pager'
|
68
|
+
TTY::Pager.new(options)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Terminal platform and OS properties
|
72
|
+
#
|
73
|
+
# @see http://www.rubydoc.info/gems/tty-pager
|
74
|
+
#
|
75
|
+
# @api public
|
76
|
+
def platform
|
77
|
+
require 'tty-platform'
|
78
|
+
TTY::Platform.new
|
79
|
+
end
|
80
|
+
|
81
|
+
# The interactive prompt
|
82
|
+
#
|
83
|
+
# @see http://www.rubydoc.info/gems/tty-prompt
|
84
|
+
#
|
85
|
+
# @api public
|
86
|
+
def prompt(**options)
|
87
|
+
require 'tty-prompt'
|
88
|
+
TTY::Prompt.new(options)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Get terminal screen properties
|
92
|
+
#
|
93
|
+
# @see http://www.rubydoc.info/gems/tty-screen
|
94
|
+
#
|
95
|
+
# @api public
|
96
|
+
def screen
|
97
|
+
require 'tty-screen'
|
98
|
+
TTY::Screen
|
99
|
+
end
|
100
|
+
|
101
|
+
# The unix which utility
|
102
|
+
#
|
103
|
+
# @see http://www.rubydoc.info/gems/tty-which
|
104
|
+
#
|
105
|
+
# @api public
|
106
|
+
def which(*args)
|
107
|
+
require 'tty-which'
|
108
|
+
TTY::Which.which(*args)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Check if executable exists
|
112
|
+
#
|
113
|
+
# @see http://www.rubydoc.info/gems/tty-which
|
114
|
+
#
|
115
|
+
# @api public
|
116
|
+
def exec_exist?(*args)
|
117
|
+
require 'tty-which'
|
118
|
+
TTY::Which.exist?(*args)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,445 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'colorize'
|
4
|
+
require 'pathname'
|
5
|
+
require 'csvlint'
|
6
|
+
require 'json'
|
7
|
+
require 'tty-logger'
|
8
|
+
require 'tty-spinner'
|
9
|
+
require 'net/smtp'
|
10
|
+
require 'mail'
|
11
|
+
require 'open-uri'
|
12
|
+
require 'uri'
|
13
|
+
|
14
|
+
require_relative '../command'
|
15
|
+
require_relative '../../ckanclient/ckanclient'
|
16
|
+
|
17
|
+
module Ckancli
|
18
|
+
module Commands
|
19
|
+
class Upload < Ckancli::Command
|
20
|
+
def initialize(options)
|
21
|
+
@options = options
|
22
|
+
|
23
|
+
@dir = nil
|
24
|
+
@resources = []
|
25
|
+
@http_resource = false
|
26
|
+
@schema = nil
|
27
|
+
@resource_config = nil
|
28
|
+
@dataset_config = nil
|
29
|
+
@api_config = nil
|
30
|
+
@logger = nil
|
31
|
+
@ckan_client = nil
|
32
|
+
@update_modified = false
|
33
|
+
@mail_server = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute(input: $stdin, output: $stdout)
|
37
|
+
begin
|
38
|
+
prepare_options(@options)
|
39
|
+
|
40
|
+
if !@schema.nil?
|
41
|
+
validate()
|
42
|
+
else
|
43
|
+
puts ""
|
44
|
+
show_info "No schema - skipping validation"
|
45
|
+
end
|
46
|
+
|
47
|
+
upload()
|
48
|
+
|
49
|
+
if !@dataset_config.nil?
|
50
|
+
update_dataset()
|
51
|
+
else
|
52
|
+
puts ""
|
53
|
+
show_info "No dataset configuration - skipping update"
|
54
|
+
end
|
55
|
+
|
56
|
+
@resources.each do |res|
|
57
|
+
if !res[:log_file].nil?
|
58
|
+
res[:log_file].close
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if !@mail_server.nil? && !@mail_receivers.nil?
|
63
|
+
mail_notify()
|
64
|
+
end
|
65
|
+
rescue StandardError => e
|
66
|
+
return_error "Unexpected error", e
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def prepare_options(options)
|
72
|
+
@http_resource = true if options[:dir] =~ /^http(s)?/
|
73
|
+
|
74
|
+
if @http_resource
|
75
|
+
@dir = Dir.pwd
|
76
|
+
else
|
77
|
+
@dir = File.directory?(options[:dir]) ? options[:dir] : (File.file?(options[:dir]) ? File.dirname(options[:dir]) : nil)
|
78
|
+
end
|
79
|
+
|
80
|
+
if @dir.nil?
|
81
|
+
return_error "Invalid directory - #{options[:dir]}"
|
82
|
+
end
|
83
|
+
|
84
|
+
begin
|
85
|
+
@logger = TTY::Logger.new do |config|
|
86
|
+
config.metadata = [:date, :time]
|
87
|
+
config.handlers = [
|
88
|
+
[:stream, output: File.open(File.join(@dir, "output.log"), "a")]
|
89
|
+
]
|
90
|
+
end
|
91
|
+
rescue StandartError => e
|
92
|
+
return_error "Could not initialize logger", e
|
93
|
+
end
|
94
|
+
|
95
|
+
begin
|
96
|
+
if @http_resource
|
97
|
+
file_path = File.join(@dir, File.basename(URI.parse(options[:dir]).path))
|
98
|
+
|
99
|
+
File.open(file_path, "wb") do |file|
|
100
|
+
file.write open(options[:dir]).read
|
101
|
+
end
|
102
|
+
|
103
|
+
@resources.push(file_path)
|
104
|
+
else
|
105
|
+
if File.directory?(options[:dir])
|
106
|
+
@resources = Dir.entries(options[:dir]).select{ |f| File.file?(File.join(options[:dir], f)) && (options[:ignoreextension] || File.extname(f).downcase == ".csv") }.map { |f| File.join(options[:dir], f) }
|
107
|
+
else
|
108
|
+
@resources.push(options[:dir])
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
@resources = @resources.map{ |r|
|
113
|
+
{
|
114
|
+
:path => r,
|
115
|
+
:valid => true,
|
116
|
+
:summary => nil,
|
117
|
+
:log_file => File.open(File.join(@dir, "#{File.basename(r)}.log"), "w"),
|
118
|
+
:log => nil
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
@resources.each do |res|
|
123
|
+
res[:log] = TTY::Logger.new do |config|
|
124
|
+
config.metadata = [:date, :time]
|
125
|
+
config.handlers = [
|
126
|
+
[:stream, output: res[:log_file]]
|
127
|
+
]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
rescue Errno::ENOENT => e
|
131
|
+
return_error "Could not load resources", e
|
132
|
+
rescue StandardError => e
|
133
|
+
return_error "Could not load resources", e
|
134
|
+
end
|
135
|
+
|
136
|
+
if options[:validationschema]
|
137
|
+
@schema = (Pathname.new(options[:validationschema])).absolute? ? options[:validationschema] : File.join(@dir, options[:validationschema])
|
138
|
+
end
|
139
|
+
|
140
|
+
if options[:resourceconfig]
|
141
|
+
begin
|
142
|
+
@resource_config = JSON.parse(File.read((Pathname.new(options[:resourceconfig])).absolute? ? options[:resourceconfig] : File.join(@dir, options[:resourceconfig])))
|
143
|
+
rescue Errno::ENOENT => e
|
144
|
+
return_error "Could not load resource configuration", e
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
if options[:datasetconfig]
|
149
|
+
begin
|
150
|
+
@dataset_config = JSON.parse(File.read((Pathname.new(options[:datasetconfig])).absolute? ? options[:datasetconfig] : File.join(@dir, options[:datasetconfig])))
|
151
|
+
rescue Errno::ENOENT => e
|
152
|
+
return_error "Could not load dataset configuration", e
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
if options[:configuration]
|
157
|
+
begin
|
158
|
+
@api_config = JSON.parse(File.read((Pathname.new(options[:configuration])).absolute? ? options[:configuration] : File.join(@dir, options[:configuration])))
|
159
|
+
rescue Errno::ENOENT => e
|
160
|
+
return_error "Could not load configuration", e
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
if !@api_config.nil?
|
165
|
+
@ckan_client = CkanClient::Client.new(@api_config["ckan_api"]["url"], @api_config["ckan_api"]["api_key"])
|
166
|
+
@mail_server = @api_config["email_server"]
|
167
|
+
@mail_receivers = @api_config["notification_receiver"]
|
168
|
+
end
|
169
|
+
|
170
|
+
@update_modified = options[:updatemodified]
|
171
|
+
@overwrite = options[:overwrite]
|
172
|
+
end
|
173
|
+
|
174
|
+
def validate()
|
175
|
+
puts ""
|
176
|
+
show_info "Starting validations..."
|
177
|
+
@resources.each do |res|
|
178
|
+
task("Validating resource: #{File.basename(res[:path])}") do
|
179
|
+
ok = false
|
180
|
+
msg = nil
|
181
|
+
exception = nil
|
182
|
+
|
183
|
+
begin
|
184
|
+
res[:log].info "Validating resource..."
|
185
|
+
|
186
|
+
res[:valid] = false
|
187
|
+
|
188
|
+
source = read_resource(res[:path])
|
189
|
+
schema = read_schema(@schema)
|
190
|
+
|
191
|
+
if schema.class == Csvlint::Schema && schema.description == "malformed"
|
192
|
+
msg = "Invalid schema - malformed JSON"
|
193
|
+
else
|
194
|
+
validator = Csvlint::Validator.new(source, {}, schema)
|
195
|
+
|
196
|
+
if validator.errors.length > 0 || validator.warnings.length > 0
|
197
|
+
msg = "#{validator.errors.length} errors, #{validator.warnings.length} warnings"
|
198
|
+
res[:summary] = {
|
199
|
+
:errors => validator.errors.map { |v| format_validation(v, schema) },
|
200
|
+
:warnings => validator.warnings.map { |v| format_validation(v, schema) }
|
201
|
+
}
|
202
|
+
|
203
|
+
res[:log].error res[:summary].to_json
|
204
|
+
end
|
205
|
+
|
206
|
+
ok = validator.valid?
|
207
|
+
res[:valid] = ok
|
208
|
+
end
|
209
|
+
rescue Errno::ENOENT, OpenURI::HTTPError => e
|
210
|
+
msg = "Could not load file - #{e.message}"
|
211
|
+
rescue Csvlint::Csvw::MetadataError => e
|
212
|
+
msg = "Invalid schema metadata - #{e.message}"
|
213
|
+
rescue StandardError => e
|
214
|
+
msg = "Could not validate resource"
|
215
|
+
exception = e
|
216
|
+
end
|
217
|
+
|
218
|
+
if !exception.nil?
|
219
|
+
res[:log].error msg, exception
|
220
|
+
elsif !msg.nil?
|
221
|
+
res[:log].info msg
|
222
|
+
else
|
223
|
+
res[:log].info "OK - Validation successfull"
|
224
|
+
end
|
225
|
+
res[:log].info "End of validation"
|
226
|
+
|
227
|
+
[ok, msg, exception]
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
show_info "End of validations"
|
232
|
+
end
|
233
|
+
|
234
|
+
def mail_notify
|
235
|
+
puts ""
|
236
|
+
show_info "Sending e-mail notifications..."
|
237
|
+
|
238
|
+
begin
|
239
|
+
mail = Mail.new
|
240
|
+
mail[:from] = @mail_server["sender"]
|
241
|
+
mail[:to] = @mail_receivers["error"]
|
242
|
+
mail[:subject] = @mail_server["subject"]
|
243
|
+
mail.delivery_method :smtp, {
|
244
|
+
:address => @mail_server["address"],
|
245
|
+
:port => @mail_server["port"],
|
246
|
+
:user_name => @mail_server["smtp_user"],
|
247
|
+
:password => @mail_server["smtp_password"],
|
248
|
+
:authentication => @mail_server["smtp_user"].nil? ? "none" : "plain",
|
249
|
+
:ssl => @mail_server["ssl"],
|
250
|
+
:openssl_verify_mode => OpenSSL::SSL::VERIFY_NONE
|
251
|
+
}
|
252
|
+
|
253
|
+
# message
|
254
|
+
msg = "CKAN CLI task completed.\r\n\r\n#{@resources.length} files processed"
|
255
|
+
|
256
|
+
# attachments
|
257
|
+
@resources.each do |res|
|
258
|
+
msg = "#{msg}\r\n- #{File.basename(res[:path])}"
|
259
|
+
if !res[:summary].nil?
|
260
|
+
msg = "#{msg} (#{res[:summary][:errors].length} errors, #{res[:summary][:warnings].length} warnings)"
|
261
|
+
end
|
262
|
+
mail.add_file :filename => File.basename(res[:path] + ".log"), :content => File.read(res[:path] + ".log")
|
263
|
+
end
|
264
|
+
msg = "#{msg}\r\n\r\nLog files attached."
|
265
|
+
mail[:body] = msg
|
266
|
+
|
267
|
+
mail.deliver!
|
268
|
+
rescue StandardError => e
|
269
|
+
show_error "Could not send e-mail", e
|
270
|
+
end
|
271
|
+
|
272
|
+
show_info "End of sending"
|
273
|
+
end
|
274
|
+
|
275
|
+
def upload()
|
276
|
+
puts ""
|
277
|
+
show_info "Starting upload..."
|
278
|
+
@resources.each do |res|
|
279
|
+
next unless res[:valid]
|
280
|
+
|
281
|
+
task("Uploading resource: #{File.basename(res[:path])}") do
|
282
|
+
res[:log].info "Uploading resource..."
|
283
|
+
|
284
|
+
ok = true
|
285
|
+
msg = nil
|
286
|
+
exception = nil
|
287
|
+
|
288
|
+
params = @resource_config["result"].clone
|
289
|
+
|
290
|
+
if @update_modified
|
291
|
+
params["last_modified"] = JSON.parse(Time.new.to_json)
|
292
|
+
end
|
293
|
+
|
294
|
+
# check if resource exists and if overwriting
|
295
|
+
if !params["id"].nil? && !@overwrite
|
296
|
+
resource = @ckan_client.get_resource(params["id"])
|
297
|
+
|
298
|
+
if !resource.nil?
|
299
|
+
ok = false
|
300
|
+
msg = "Resource already exists - skipping. Use parameter 'overwrite' to overwrite changes."
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
if ok
|
305
|
+
@ckan_client.create_or_update_resource(params, File.new(res[:path], 'rb'), true){ |response, status_ok|
|
306
|
+
ok = status_ok
|
307
|
+
|
308
|
+
if !ok
|
309
|
+
msg = "HTTP error #{response.code}"
|
310
|
+
|
311
|
+
log_error response.body
|
312
|
+
end
|
313
|
+
}
|
314
|
+
end
|
315
|
+
|
316
|
+
if !ok
|
317
|
+
res[:log].error msg
|
318
|
+
else
|
319
|
+
res[:log].info "OK - upload successfull"
|
320
|
+
end
|
321
|
+
res[:log].info "End of upload"
|
322
|
+
|
323
|
+
[ok, msg, exception]
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
show_info "End of upload"
|
328
|
+
end
|
329
|
+
|
330
|
+
def update_dataset()
|
331
|
+
puts ""
|
332
|
+
show_info "Starting dataset update..."
|
333
|
+
|
334
|
+
task("Updating dataset") do
|
335
|
+
ok = false
|
336
|
+
msg = nil
|
337
|
+
exception = nil
|
338
|
+
|
339
|
+
@ckan_client.update_package(@dataset_config["result"]["id"], @dataset_config["result"].clone, true){ |response, status_ok|
|
340
|
+
ok = status_ok
|
341
|
+
|
342
|
+
if !ok
|
343
|
+
msg = "HTTP error #{response.code}"
|
344
|
+
|
345
|
+
log_error response.body
|
346
|
+
end
|
347
|
+
}
|
348
|
+
|
349
|
+
[ok, msg, exception]
|
350
|
+
end
|
351
|
+
show_info "End of dataset update"
|
352
|
+
end
|
353
|
+
|
354
|
+
def return_error(message, exception = nil)
|
355
|
+
show_error(message, exception)
|
356
|
+
|
357
|
+
exit 1
|
358
|
+
end
|
359
|
+
|
360
|
+
def show_error(message, exception = nil)
|
361
|
+
if !exception.nil?
|
362
|
+
message = "#{message} - #{exception.message}"
|
363
|
+
end
|
364
|
+
|
365
|
+
puts " #{message} ".white.on_red
|
366
|
+
|
367
|
+
log_fatal message, exception
|
368
|
+
end
|
369
|
+
|
370
|
+
def show_info(message)
|
371
|
+
puts " #{message} "
|
372
|
+
|
373
|
+
log_info message
|
374
|
+
end
|
375
|
+
|
376
|
+
def task(message)
|
377
|
+
log_info message
|
378
|
+
|
379
|
+
spinner = TTY::Spinner.new("[:spinner] #{message}...")
|
380
|
+
spinner.auto_spin
|
381
|
+
ok, message, exception = yield
|
382
|
+
spinner.stop(ok ? "OK".green : "ERROR".red)
|
383
|
+
|
384
|
+
if !message.nil?
|
385
|
+
message = " #{message}"
|
386
|
+
|
387
|
+
if !exception.nil?
|
388
|
+
show_error message, exception
|
389
|
+
else
|
390
|
+
show_info message
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def read_resource(source)
|
396
|
+
# check if URL or file
|
397
|
+
unless source =~ /^http(s)?/
|
398
|
+
source = File.new( source )
|
399
|
+
end
|
400
|
+
|
401
|
+
source
|
402
|
+
end
|
403
|
+
|
404
|
+
def read_schema(path)
|
405
|
+
schema = Csvlint::Schema.load_from_uri(path, false)
|
406
|
+
|
407
|
+
schema
|
408
|
+
end
|
409
|
+
|
410
|
+
def format_validation(error, schema = nil)
|
411
|
+
h = {
|
412
|
+
type: error.type,
|
413
|
+
category: error.category,
|
414
|
+
row: error.row,
|
415
|
+
col: error.column
|
416
|
+
}
|
417
|
+
|
418
|
+
if error.column && !schema.nil? && schema.class == Csvlint::Schema && schema.fields[error.column - 1] != nil
|
419
|
+
field = schema.fields[error.column - 1]
|
420
|
+
h[:header] = field.name
|
421
|
+
end
|
422
|
+
|
423
|
+
h
|
424
|
+
end
|
425
|
+
|
426
|
+
def log_fatal(message, exception = nil)
|
427
|
+
if !@logger.nil?
|
428
|
+
@logger.fatal message, exception
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
def log_info(message)
|
433
|
+
if !@logger.nil?
|
434
|
+
@logger.info message
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
def log_error(message)
|
439
|
+
if !@logger.nil?
|
440
|
+
@logger.error message
|
441
|
+
end
|
442
|
+
end
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
data/lib/ckancli.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'rest-client'
|
5
|
+
|
6
|
+
module CkanClient
|
7
|
+
class Client
|
8
|
+
def initialize(url, key)
|
9
|
+
raise ArgumentError, "No URL provided" unless !url.nil?
|
10
|
+
raise ArgumentError, "No API KEY provided" unless !key.nil?
|
11
|
+
|
12
|
+
# remove trailig slashes
|
13
|
+
@url = url.sub(/(\/)+$/,"")
|
14
|
+
@key = key
|
15
|
+
@headers = {}
|
16
|
+
@headers[:Authorization] = @key
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_resource(id, params = {}, &block)
|
20
|
+
raise ArgumentError, "No ID provided for resource" unless !id.nil?
|
21
|
+
|
22
|
+
result = nil
|
23
|
+
params["id"] = id
|
24
|
+
|
25
|
+
post("resource_show", params, nil){ |response, status_ok|
|
26
|
+
if status_ok
|
27
|
+
result = JSON.parse(response.body)["result"]
|
28
|
+
end
|
29
|
+
}
|
30
|
+
|
31
|
+
return result
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_resource(params = {}, file = nil, &block)
|
35
|
+
if params["name"].nil? && !file.nil?
|
36
|
+
params["name"] = File.basename(file.path)
|
37
|
+
end
|
38
|
+
|
39
|
+
post("resource_create", params, file, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def update_resource(id, params = {}, file = nil, resolve_delta = false, &block)
|
43
|
+
raise ArgumentError, "No ID provided for resource update" unless !id.nil?
|
44
|
+
|
45
|
+
params["id"] = id
|
46
|
+
|
47
|
+
if resolve_delta
|
48
|
+
metadata = get_resource(id)
|
49
|
+
if !metadata.nil?
|
50
|
+
params = metadata.merge(params)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
post("resource_update", params, file, &block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_or_update_resource(params = {}, file = nil, resolve_delta = false, &block)
|
58
|
+
if params["id"].nil?
|
59
|
+
create_resource(params, file, &block)
|
60
|
+
else
|
61
|
+
update_resource(params["id"], params, file, resolve_delta, &block)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_package(id, params = {}, &block)
|
66
|
+
raise ArgumentError, "No ID provided for package" unless !id.nil?
|
67
|
+
|
68
|
+
result = nil
|
69
|
+
params["id"] = id
|
70
|
+
|
71
|
+
post("package_show", params, nil){ |response, status_ok|
|
72
|
+
if status_ok
|
73
|
+
result = JSON.parse(response.body)["result"]
|
74
|
+
end
|
75
|
+
}
|
76
|
+
|
77
|
+
return result
|
78
|
+
end
|
79
|
+
|
80
|
+
def create_package(params = {}, &block)
|
81
|
+
post("package_create", params, nil, &block)
|
82
|
+
end
|
83
|
+
|
84
|
+
def update_package(id, params = {}, resolve_delta = false, &block)
|
85
|
+
raise ArgumentError, "No ID provided for package update" unless !id.nil?
|
86
|
+
|
87
|
+
params["id"] = id
|
88
|
+
|
89
|
+
if resolve_delta
|
90
|
+
metadata = get_package(id)
|
91
|
+
if !metadata.nil?
|
92
|
+
params = metadata.merge(params)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
post("package_update", params, nil, &block)
|
97
|
+
end
|
98
|
+
|
99
|
+
def create_or_update_package(params = {}, resolve_delta = false, &block)
|
100
|
+
if params["id"].nil?
|
101
|
+
create_package(params, &block)
|
102
|
+
else
|
103
|
+
update_package(params["id"], params, resolve_delta, &block)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
def post(action, params = {}, file = nil, &block)
|
109
|
+
raise ArgumentError, "No action provided" unless !action.nil?
|
110
|
+
|
111
|
+
payload = {}
|
112
|
+
if !file.nil?
|
113
|
+
payload[:upload] = file
|
114
|
+
end
|
115
|
+
payload = payload.merge(params)
|
116
|
+
|
117
|
+
if file.nil?
|
118
|
+
payload = payload.to_json
|
119
|
+
@headers[:content_type] = :json
|
120
|
+
else
|
121
|
+
@headers.delete(:content_type)
|
122
|
+
end
|
123
|
+
|
124
|
+
RestClient.post("#{@url}/action/#{action}", payload, @headers){ |response, request, result|
|
125
|
+
block.call(response, response.code == 200)
|
126
|
+
}
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
metadata
ADDED
@@ -0,0 +1,252 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ckan_cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- datagovlv
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-08-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: tty-color
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: tty-logger
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: tty-platform
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.2.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.2.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: tty-progressbar
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.16.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.16.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: tty-spinner
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.9.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.9.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: tty-which
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.4'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.4'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pastel
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.7.2
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.7.2
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: thor
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.20.0
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.20.0
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: csvlint
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.4.0
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.4.0
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rest-client
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 2.0.2
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 2.0.2
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: mail
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 2.7.1
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 2.7.1
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: bundler
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '1.17'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '1.17'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: rake
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '10.0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - "~>"
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '10.0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: rspec
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - "~>"
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '3.0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - "~>"
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '3.0'
|
209
|
+
description: CKAN CLI with built-in CSV file validation and CKAN API integration.
|
210
|
+
email:
|
211
|
+
- dati@varam.gov.lv
|
212
|
+
executables:
|
213
|
+
- ckancli.rb
|
214
|
+
extensions: []
|
215
|
+
extra_rdoc_files: []
|
216
|
+
files:
|
217
|
+
- LICENSE.txt
|
218
|
+
- README.md
|
219
|
+
- Rakefile
|
220
|
+
- bin/setup
|
221
|
+
- ckan_cli.gemspec
|
222
|
+
- exe/ckancli.rb
|
223
|
+
- lib/ckancli.rb
|
224
|
+
- lib/ckancli/cli.rb
|
225
|
+
- lib/ckancli/command.rb
|
226
|
+
- lib/ckancli/commands/upload.rb
|
227
|
+
- lib/ckancli/version.rb
|
228
|
+
- lib/ckanclient/ckanclient.rb
|
229
|
+
homepage: https://github.com/datagovlv/ckan_cli
|
230
|
+
licenses:
|
231
|
+
- MIT
|
232
|
+
metadata: {}
|
233
|
+
post_install_message:
|
234
|
+
rdoc_options: []
|
235
|
+
require_paths:
|
236
|
+
- lib
|
237
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
238
|
+
requirements:
|
239
|
+
- - ">="
|
240
|
+
- !ruby/object:Gem::Version
|
241
|
+
version: 2.0.0
|
242
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
243
|
+
requirements:
|
244
|
+
- - ">="
|
245
|
+
- !ruby/object:Gem::Version
|
246
|
+
version: '0'
|
247
|
+
requirements: []
|
248
|
+
rubygems_version: 3.0.3
|
249
|
+
signing_key:
|
250
|
+
specification_version: 4
|
251
|
+
summary: CKAN command line interface.
|
252
|
+
test_files: []
|