datapimp 0.0.1 → 1.0.0

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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/Gemfile +6 -0
  4. data/Guardfile +5 -0
  5. data/LICENSE.txt +4 -18
  6. data/README.md +199 -17
  7. data/Rakefile +24 -0
  8. data/bin/datapimp +18 -0
  9. data/datapimp.gemspec +27 -7
  10. data/lib/datapimp/cli/01_extensions.rb +25 -0
  11. data/lib/datapimp/cli/config.rb +24 -0
  12. data/lib/datapimp/cli/setup.rb +35 -0
  13. data/lib/datapimp/cli/sync.rb +29 -0
  14. data/lib/datapimp/cli.rb +49 -0
  15. data/lib/datapimp/clients/amazon.rb +172 -0
  16. data/lib/datapimp/clients/dropbox.rb +178 -0
  17. data/lib/datapimp/clients/github.rb +59 -0
  18. data/lib/datapimp/clients/google.rb +145 -0
  19. data/lib/datapimp/configuration.rb +210 -0
  20. data/lib/datapimp/core_ext.rb +5 -0
  21. data/lib/datapimp/data_sources/dropbox.rb +8 -0
  22. data/lib/datapimp/data_sources/excel.rb +5 -0
  23. data/lib/datapimp/data_sources/github.rb +5 -0
  24. data/lib/datapimp/data_sources/google.rb +5 -0
  25. data/lib/datapimp/data_sources/json.rb +5 -0
  26. data/lib/datapimp/data_sources/nokogiri.rb +5 -0
  27. data/lib/datapimp/data_sources.rb +10 -0
  28. data/lib/datapimp/sync/dropbox_delta.rb +67 -0
  29. data/lib/datapimp/sync/dropbox_folder.rb +10 -0
  30. data/lib/datapimp/sync/google_drive_folder.rb +5 -0
  31. data/lib/datapimp/sync.rb +30 -0
  32. data/lib/datapimp/version.rb +1 -1
  33. data/lib/datapimp.rb +30 -2
  34. data/packaging/wrapper.sh +32 -0
  35. data/spec/spec_helper.rb +29 -0
  36. data/spec/support/test_helpers.rb +7 -0
  37. data/tasks/distribution/configuration.rb +15 -0
  38. data/tasks/distribution/executable.rb +28 -0
  39. data/tasks/distribution/package.rb +85 -0
  40. data/tasks/distribution/package_helpers.rb +12 -0
  41. data/tasks/distribution/release.rb +49 -0
  42. data/tasks/distribution/release_notes.erb +14 -0
  43. data/tasks/distribution/release_notes.rb +62 -0
  44. data/tasks/distribution/tarball.rb +47 -0
  45. data/tasks/distribution/travelling_ruby.rb +87 -0
  46. data/tasks/package.rake +41 -0
  47. data/tasks/upload.rake +40 -0
  48. metadata +300 -26
@@ -0,0 +1,210 @@
1
+ require 'singleton'
2
+ require 'json'
3
+
4
+ module Datapimp
5
+ class Configuration
6
+ include Singleton
7
+
8
+ DefaultSettings = {
9
+ manifest_filename: "datapimp.json",
10
+
11
+ github_username: '',
12
+ github_organization: '',
13
+ github_app_key: '',
14
+ github_app_secret: '',
15
+ github_access_token: '',
16
+
17
+ dnsimple_api_token: '',
18
+ dnsimple_username: '',
19
+
20
+ dropbox_app_key: '',
21
+ dropbox_app_secret: '',
22
+ dropbox_app_type: 'sandbox',
23
+ dropbox_client_token: '',
24
+ dropbox_client_secret: '',
25
+
26
+ aws_access_key_id: '',
27
+ aws_secret_access_key: '',
28
+
29
+ google_client_id: '',
30
+ google_client_secret: '',
31
+ google_refresh_token: '',
32
+ google_access_token: ''
33
+ }
34
+
35
+ def self.method_missing(meth, *args, &block)
36
+ if instance.respond_to?(meth)
37
+ return instance.send meth, *args, &block
38
+ end
39
+
40
+ nil
41
+ end
42
+
43
+ def method_missing(meth, *args, &block)
44
+ if current.key?(meth.to_s)
45
+ return current.fetch(meth)
46
+ end
47
+
48
+ super
49
+ end
50
+
51
+ def initialize!(fresh=false)
52
+ return if home_config_path.exist? && !fresh
53
+
54
+ FileUtils.mkdir_p home_config_path.dirname
55
+
56
+ home_config_path.open("w+") do |fh|
57
+ fh.write(DefaultSettings.to_json)
58
+ end
59
+ end
60
+
61
+ def manifest_filename
62
+ "datapimp.json"
63
+ end
64
+
65
+ def dnsimple_setup?
66
+ dnsimple_api_token.to_s.length > 0 && dnsimple_username.to_s.length > 0
67
+ end
68
+
69
+ def dropbox_setup?
70
+ dropbox_app_key.to_s.length > 0 && dropbox_app_secret.to_s.length > 0
71
+ end
72
+
73
+ def google_setup?
74
+ google_client_secret.to_s.length > 0 && google_client_id.to_s.length > 0
75
+ end
76
+
77
+ def amazon_setup?
78
+ aws_access_key_id.to_s.length > 0 && aws_secret_access_key.to_s.length > 0
79
+ end
80
+
81
+ def show
82
+ current.each do |p|
83
+ key, value = p
84
+
85
+ unless key == 'sites_directory'
86
+ puts "#{key}: #{ value.inspect }"
87
+ end
88
+ end
89
+ end
90
+
91
+ def primary_config
92
+ cwd_config_path.exist? ? cwd_config : home_config
93
+ end
94
+
95
+ def get(setting)
96
+ setting = setting.to_s.downcase
97
+ primary_config[setting]
98
+ end
99
+
100
+ def set(setting, value, persist = true, options={})
101
+ setting = setting.to_s.downcase
102
+ primary_config[setting] = value
103
+ save! if persist == true
104
+ value
105
+ end
106
+
107
+ def apply_all(options={})
108
+ current.merge!(options)
109
+ end
110
+
111
+ def unset(setting, persist = true)
112
+ primary_config.delete(setting)
113
+ save! if persist == true
114
+ end
115
+
116
+ def defaults
117
+ DefaultSettings.dup
118
+ end
119
+
120
+ def current(using_environment = true)
121
+ @current ||= calculate_config(using_environment)
122
+ end
123
+
124
+ def calculate_config(using_environment = true)
125
+ @current = defaults.merge(home_config.merge(cwd_config.merge(applied_config))).to_mash
126
+
127
+ (defaults.keys + home_config.keys + cwd_config.keys).uniq.each do |key|
128
+ upper = key.to_s.upcase
129
+ if ENV[upper]
130
+ @current[key] = ENV[upper]
131
+ end
132
+ end if using_environment
133
+
134
+ @current
135
+ end
136
+
137
+ def apply_config(hash={})
138
+ applied_config.merge!(hash)
139
+ current.merge(applied_config)
140
+ end
141
+
142
+ def apply_config_from_path(path)
143
+ path = Pathname(path)
144
+ parsed = JSON.parse(path.read) rescue {}
145
+ applied_config.merge!(parsed)
146
+ nil
147
+ end
148
+
149
+ def save!
150
+ save_home_config
151
+ save_cwd_config
152
+ @current = nil
153
+ true
154
+ end
155
+
156
+ def save_cwd_config
157
+ return nil unless cwd_config_path.exist?
158
+
159
+ File.open(cwd_config_path, 'w+') do |fh|
160
+ fh.write JSON.generate(cwd_config.to_hash)
161
+ end
162
+ end
163
+
164
+ def save_home_config
165
+ File.open(home_config_path, 'w+') do |fh|
166
+ fh.write JSON.generate(home_config.to_hash)
167
+ end
168
+ end
169
+
170
+ # Applied config is configuration values passed in context
171
+ # usually from the cli, but also in the unit tests
172
+ def applied_config
173
+ @applied_config ||= {}
174
+ end
175
+
176
+ def cwd_config
177
+ @cwd_config ||= begin
178
+ (cwd_config_path.exist? rescue false) ? JSON.parse(cwd_config_path.read) : {}
179
+ rescue
180
+ {}
181
+ end
182
+ end
183
+
184
+ def home_config
185
+ initialize! unless home_config_path.exist?
186
+
187
+ @home_config ||= begin
188
+ (home_config_path.exist? rescue false) ? JSON.parse(home_config_path.read) : {}
189
+ rescue
190
+ {}
191
+ end
192
+ end
193
+
194
+ def home_config_path= value
195
+ @home_config_path = Pathname(value)
196
+ end
197
+
198
+ def home_config_path
199
+ @home_config_path || Pathname(ENV['HOME']).join(".datapimp", manifest_filename)
200
+ end
201
+
202
+ def cwd_config_path= value
203
+ @cwd_config_path = Pathname(value)
204
+ end
205
+
206
+ def cwd_config_path
207
+ @cwd_config_path || Pathname(Datapimp.pwd).join(manifest_filename)
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,5 @@
1
+ class Hash
2
+ def to_mash
3
+ Hashie::Mash.new(self)
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ # The `Datapimp::Sources::Dropbox` class represents a
2
+ # file on the Dropbox file sharing system (usually an excel file)
3
+ # that will get turned into a JSON array of objects
4
+ module Datapimp::Sources
5
+ class Dropbox < OpenStruct
6
+
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Datapimp::Sources
2
+ class Excel < OpenStruct
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Datapimp::Sources
2
+ class Github < OpenStruct
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Datapimp::Sources
2
+ class Google < OpenStruct
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Datapimp::Sources
2
+ class Json < OpenStruct
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Datapimp::Sources
2
+ class Nokogiri < OpenStruct
3
+
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ # The `Datapimp::Dataources` module houses the various
2
+ # types of remote data stores we are reading and converting into
3
+ # a JSON array of objects that gets cached on our filesystem.
4
+ module Datapimp
5
+ module DataSources
6
+
7
+ end
8
+ end
9
+
10
+ Dir[Datapimp.lib.join("datapimp/data_sources/**/*.rb")].each {|f| require(f) }
@@ -0,0 +1,67 @@
1
+ module Datapimp
2
+ module Sync
3
+ class DropboxDelta
4
+
5
+ attr_accessor :client,
6
+ :data,
7
+ :cursor,
8
+ :entries,
9
+ :path_prefix
10
+
11
+ def initialize(client, cursor, path_prefix=nil)
12
+ @client = client
13
+ @cursor = cursor
14
+ @path_prefix = path_prefix
15
+ end
16
+
17
+ def processed!
18
+ # TODO
19
+ # Should update cursor
20
+ end
21
+
22
+ def entries
23
+ return @entries if @entries
24
+ fetch
25
+ @entries
26
+ end
27
+
28
+ def _dropbox_delta at=nil
29
+ at ||= cursor
30
+ response = client.delta(at, path_prefix)
31
+ self.cursor = response["cursor"]
32
+ response
33
+ end
34
+
35
+ def data
36
+ @data ||= fetch
37
+ end
38
+
39
+ def on_reset path_prefix, cursor
40
+ end
41
+
42
+ def fetch
43
+ return @response if @response
44
+
45
+ response = _dropbox_delta
46
+
47
+ if response["reset"] == true
48
+ on_reset(path_prefix, cursor)
49
+ end
50
+
51
+ self.entries = {}.to_mash
52
+
53
+ response["entries"].each do |entry|
54
+ path, meta = entry
55
+ self.entries[path] = meta
56
+ end
57
+
58
+ if response["has_more"] == true
59
+ # TODO Implement
60
+ end
61
+
62
+ @response = response
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,10 @@
1
+ module Datapimp
2
+ class Sync::DropboxFolder < OpenStruct
3
+ class_attribute :default_path_prefix, :default_root
4
+
5
+
6
+ def push
7
+ Datapimp.dropbox(token: client_token, secret: client_secret)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ module Datapimp
2
+ class Sync::GoogleDriveFolder < OpenStruct
3
+
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ # The `Datapimp::Sync` module will delegate to the underlying service layer
2
+ # which we are pushing or pulling files and data from. It will wrap the client
3
+ # implementation we are using.
4
+ module Datapimp
5
+ module Sync
6
+ def self.data_source_types
7
+ %w(dropbox amazon github google json excel nokogiri)
8
+ end
9
+
10
+ def self.amazon(options={})
11
+ require 'datapimp/clients/amazon'
12
+ Datapimp::Clients::Amazon.client(options)
13
+ end
14
+
15
+ def self.dropbox(options={})
16
+ require 'datapimp/clients/dropbox'
17
+ Datapimp::Clients::Dropbox.client(options)
18
+ end
19
+
20
+ def self.github(options={})
21
+ require 'datapimp/clients/github'
22
+ Datapimp::Clients::Github.client(options)
23
+ end
24
+
25
+ def self.google(options={})
26
+ require 'datapimp/clients/google'
27
+ Datapimp::Clients::Google.client(options)
28
+ end
29
+ end
30
+ end
@@ -1,3 +1,3 @@
1
1
  module Datapimp
2
- VERSION = "0.0.1"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/datapimp.rb CHANGED
@@ -1,5 +1,33 @@
1
- require "datapimp/version"
1
+ require 'ostruct'
2
+ require 'set'
3
+ require 'pathname'
4
+ require 'hashie'
5
+ require 'datapimp/core_ext'
2
6
 
3
7
  module Datapimp
4
- # Your code goes here...
8
+ def self.config
9
+ Datapimp::Configuration.instance
10
+ end
11
+
12
+ def self.pwd
13
+ Pathname(ENV.fetch('DATAPIMP_PWD') { Dir.pwd })
14
+ end
15
+
16
+ def self.lib
17
+ Pathname(File.dirname(__FILE__))
18
+ end
19
+
20
+ def self.method_missing(meth, *args, &block)
21
+ case
22
+ when %w(dropbox amazon github google).include?(meth.to_s)
23
+ Datapimp::Sync.send(meth, *args, &block)
24
+ else
25
+ super
26
+ end
27
+ end
5
28
  end
29
+
30
+ require 'datapimp/version'
31
+ require 'datapimp/configuration'
32
+ require 'datapimp/data_sources'
33
+ require 'datapimp/sync'
@@ -0,0 +1,32 @@
1
+ #!/bin/bash
2
+
3
+ export DATAPIMP_PWD=$PWD
4
+ set -e
5
+
6
+ TARGET_FILE=$0
7
+
8
+ cd `dirname $TARGET_FILE`
9
+ TARGET_FILE=`basename $TARGET_FILE`
10
+
11
+ # Iterate down a (possible) chain of symlinks
12
+ while [ -L "$TARGET_FILE" ]
13
+ do
14
+ TARGET_FILE=`readlink $TARGET_FILE`
15
+ cd `dirname $TARGET_FILE`
16
+ TARGET_FILE=`basename $TARGET_FILE`
17
+ done
18
+
19
+ # Compute the canonicalized name by finding the physical path
20
+ # for the directory we're in and appending the target file.
21
+ PHYS_DIR=`pwd -P`
22
+ RESULT=$PHYS_DIR/$TARGET_FILE
23
+
24
+ # Figure out where this script is located.
25
+ SELFDIR=$(dirname "$RESULT")
26
+
27
+ # Tell Bundler where the Gemfile and gems are.
28
+ export BUNDLE_GEMFILE="$SELFDIR/lib/app/Gemfile"
29
+ unset BUNDLE_IGNORE_CONFIG
30
+
31
+ # Run the actual app using the bundled Ruby interpreter.
32
+ exec "$SELFDIR/lib/ruby/bin/ruby" -rbundler/setup "$SELFDIR/lib/app/bin/datapimp" "$@"
@@ -0,0 +1,29 @@
1
+ require 'rack/test'
2
+ require 'pry'
3
+ require 'datapimp'
4
+
5
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
6
+
7
+ module Datapimp
8
+ def self.spec_root
9
+ Pathname(File.dirname(__FILE__))
10
+ end
11
+
12
+ def self.dummy_path
13
+ spec_root.join("dummy")
14
+ end
15
+
16
+ def self.fixtures_path
17
+ spec_root.join("support","fixtures")
18
+ end
19
+ end
20
+
21
+ Skypager::Site.directory = {}
22
+
23
+ RSpec.configure do |config|
24
+ config.mock_with :rspec
25
+ config.order = :random
26
+
27
+ config.include Rack::Test
28
+ config.include Requests::JsonHelpers, type: :request
29
+ end
@@ -0,0 +1,7 @@
1
+ module Requests
2
+ module JsonHelpers
3
+ def json
4
+ @json ||= JSON.parse(response.body)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ module Distribution
2
+ class << self
3
+ attr_accessor :configuration
4
+ end
5
+
6
+ def self.configure
7
+ self.configuration ||= Configuration.new
8
+ yield configuration
9
+ end
10
+
11
+ class Configuration
12
+ attr_accessor :package_name, :packaging_dir, :version, :rb_version,
13
+ :native_extensions
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ Dir[File.join(Dir.pwd, 'tasks', '**', '*.rb')].each { |f| require f }
2
+
3
+ module Distribution
4
+ class Executable
5
+ include PackageHelpers
6
+ extend Forwardable
7
+
8
+ attr_reader :package
9
+
10
+ def_delegators :@package, :dir, :package_name
11
+
12
+ def initialize(package)
13
+ @package = package
14
+ end
15
+
16
+ def self.create(package)
17
+ executable = new(package)
18
+ executable.copy_wrapper
19
+ executable
20
+ end
21
+
22
+ def copy_wrapper
23
+ print_to_console 'Creating exexutable...'
24
+
25
+ FileUtils.cp 'packaging/wrapper.sh', "#{dir}/#{package_name}"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,85 @@
1
+ require 'fileutils'
2
+ require 'forwardable'
3
+
4
+ Dir[File.join(Dir.pwd, 'tasks', '**', '*.rb')].each { |f| require f }
5
+
6
+ module Distribution
7
+ class Package
8
+ extend Forwardable
9
+ include PackageHelpers
10
+
11
+ attr_reader :config, :dir, :tarball, :package, :arch, :root
12
+
13
+ def_delegators :@config, :version, :rb_version, :package_name,
14
+ :packaging_dir, :native_extensions
15
+
16
+ def initialize(arch)
17
+ abort 'Ruby 2.1.x required' if RUBY_VERSION !~ /^2\.1\./
18
+
19
+ @arch = arch
20
+ @config = ::Distribution.configuration
21
+ @dir = "#{package_name}-#{version}-#{arch}"
22
+ @package = self
23
+ @root = File.expand_path '.'
24
+ end
25
+
26
+ def self.create(args)
27
+ new(*args).build
28
+ end
29
+
30
+ def build
31
+ initialize_install_dir
32
+ copy_datapimp
33
+ install_ruby_and_gems
34
+ create_executable
35
+ post_cleanup
36
+ @tarball = create_tarball
37
+ clean_dir
38
+ end
39
+
40
+ private
41
+
42
+ def clean_dir
43
+ FileUtils.cd root do
44
+ FileUtils.remove_dir(dir, true) if Dir.exist? dir
45
+ end
46
+ end
47
+
48
+ def post_cleanup
49
+ print_to_console 'Cleaning up...'
50
+
51
+ files = ["#{packaging_dir}/traveling-ruby-#{rb_version}-#{arch}.tar.gz"]
52
+
53
+ files.each { |file| FileUtils.rm file if File.exist? file }
54
+ end
55
+
56
+ def create_tarball
57
+ Tarball.new self
58
+ end
59
+
60
+ def create_executable
61
+ Executable.create self
62
+ end
63
+
64
+ def install_ruby_and_gems
65
+ TravellingRuby.install self
66
+ end
67
+
68
+ def initialize_install_dir
69
+ clean_dir
70
+
71
+ FileUtils.cd root do
72
+ FileUtils.mkdir_p "#{dir}/lib/app"
73
+ end
74
+ end
75
+
76
+ def copy_datapimp
77
+ print_to_console 'Copying datapimp...'
78
+
79
+ %w(datapimp.gemspec Gemfile Gemfile.lock lib bin)
80
+ .each do |folder|
81
+ FileUtils.cp_r File.join(root, folder), "#{dir}/lib/app"
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,12 @@
1
+ module Distribution
2
+ module PackageHelpers
3
+ def curl(file)
4
+ system "curl -L -O --fail --silent #{file}"
5
+ end
6
+
7
+ def print_to_console(msg)
8
+ arch = package.arch
9
+ puts "[#{arch}]:" + ' ' * (16 - arch.size) + '=>' + ' ' + msg
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,49 @@
1
+ Dir[File.join(Dir.pwd, 'tasks', '**', '*.rb')].each { |f| require f }
2
+
3
+ require 'digest'
4
+ require 'octokit'
5
+ require 'pathname'
6
+
7
+ module Distribution
8
+ class Release
9
+ extend Forwardable
10
+ include PackageHelpers
11
+
12
+ attr_reader :tarball, :github, :package
13
+
14
+ def_delegators :@tarball, :package, :version, :file
15
+
16
+ def initialize(tarball)
17
+ @tarball = tarball
18
+ @github = Octokit::Client.new access_token: ENV['OCTODOWN_TOKEN']
19
+ end
20
+
21
+ def self.create(tarball)
22
+ release = new(tarball)
23
+ release.create_new_release
24
+ end
25
+
26
+ def create_new_release
27
+ print_to_console 'Publishing release to GitHub...'
28
+ github.create_release(
29
+ 'datapimp/datapimp',
30
+ "v#{version}",
31
+ name: "v#{version}",
32
+ body: ReleaseNotes.new.content
33
+ )
34
+ end
35
+
36
+ def upload_asset
37
+ print_to_console 'Uploading to GitHub...'
38
+ github.upload_asset find_upload_url, file
39
+ end
40
+
41
+ private
42
+
43
+ def find_upload_url
44
+ Octokit.releases('datapimp/datapimp').find do |n|
45
+ n.tag_name == "v#{version}"
46
+ end[:url]
47
+ end
48
+ end
49
+ end