datapimp 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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