chronicle-etl 0.5.2 → 0.5.5

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
  SHA256:
3
- metadata.gz: b8faa084cfe4a9f080ee5494c69b268b78bfa8f3502354e740264e6941f13daf
4
- data.tar.gz: 1bf4f2751c71cadedc78a2fe3ed5b09bf86cd601a909e2fa2db0a0de8cc2c21d
3
+ metadata.gz: a2de46efc3c5fbdc7ac120137bef56e13a138c8a95c8dd7d0a3542a65be65959
4
+ data.tar.gz: e8e3e9ae236e270b2926037419d5349170f85b8597c640c0b7b899552257fdb9
5
5
  SHA512:
6
- metadata.gz: ff10779b663a3321b779fb03e07249856174d96fb96e405ae906a47441c288d6a245c852525801ba250cce1125cf05c523ef4ec75fdfb4335cef9003091437ed
7
- data.tar.gz: 509f6f92e95341d212c54b6b000bc54e8ba03898497191a3e5d3b14db7bff3ed625d0fee403888fbb6103c1edc14de66b215d9aa84ddb68cefcf51c0e6c74138
6
+ metadata.gz: 58b293d45d1a7f4589aee080d0fb1348e45d76753a8d48bb33e694d6a2b6ea123d1495a12a42e98ed6d3e65926ad48bd7de1fb98dac9bc7bac9225ef00d32fb3
7
+ data.tar.gz: e73c73b67b4e3790347da3df22ed7a11357fdf43e91b734925503f8d0c189aabd07e26f51ed25cf1409fd68ba74dd72e1e7e01c113f773832c509172f0b4ee84
data/README.md CHANGED
@@ -6,21 +6,28 @@
6
6
 
7
7
  Are you trying to archive your digital history or incorporate it into your own projects? You’ve probably discovered how frustrating it is to get machine-readable access to your own data. While [building a memex](https://hyfen.net/memex/), I learned first-hand what great efforts must be made before you can begin using the data in interesting ways.
8
8
 
9
- If you don’t want to spend all your time writing scrapers, reverse-engineering APIs, or parsing takeout data, this project is for you! (*If you do enjoy these things, please see the [open issues](https://github.com/chronicle-app/chronicle-etl/issues).*)
9
+ If you don’t want to spend all your time writing scrapers, reverse-engineering APIs, or parsing takeout data, this tool is for you! (*If you do enjoy these things, please see the [open issues](https://github.com/chronicle-app/chronicle-etl/issues).*)
10
10
 
11
- **`chronicle-etl` is a CLI tool that gives you a unified interface for accessing your personal data.** It uses the ETL pattern to *extract* it from a source (e.g. your local browser history, a directory of images, goodreads.com reading history), *transform* it (into a given schema), and *load* it to a source (e.g. a CSV file, JSON, external API).
11
+ **`chronicle-etl` is a CLI tool that gives you a unified interface to your personal data.** It uses the ETL pattern to *extract* data from a source (e.g. your local browser history, a directory of images, goodreads.com reading history), *transform* it (into a given schema), and *load* it to a destination (e.g. a CSV file, JSON, external API).
12
12
 
13
13
  ## What does `chronicle-etl` give you?
14
- * **CLI tool for working with personal data**. You can monitor progress of exports, manipulate the output, set up recurring jobs, manage credentials, and more.
15
- * **Plugins for many third-party providers**. A plugin system allows you to access data from third-party providers and hook it into the shared CLI infrastructure.
16
- * **A common, opinionated schema**: You can normalize different datasets into a single schema so that, for example, all your iMessages and emails are stored in a common schema. Don’t want to use the schema? `chronicle-etl` always allows you to fall back on working with the raw extraction data.
14
+ * **A CLI tool for working with personal data**. You can monitor progress of exports, manipulate the output, set up recurring jobs, manage credentials, and more.
15
+ * **Plugins for many third-party providers** (see [list](#available-plugins-and-connectors)). This plugin system allows you to access data from dozens of third-party services, all accessible through a common CLI interface.
16
+ * **A common, opinionated schema**: You can normalize different datasets into a single schema so that, for example, all your iMessages and emails are represented in a common schema. (Don’t want to use this schema? `chronicle-etl` always allows you to fall back on working with the raw extraction data.)
17
+
18
+ ## Chronicle-ETL in action
19
+
20
+ ![demo](https://user-images.githubusercontent.com/6291/161410839-b5ce931a-2353-4585-b530-929f46e3f960.svg)
21
+
22
+ ### Longer screencast
23
+
24
+ [![asciicast](https://asciinema.org/a/483455.svg)](https://asciinema.org/a/483455)
17
25
 
18
26
  ## Installation
19
27
 
20
28
  Using homebrew:
21
29
  ```sh
22
30
  $ brew install chronicle-app/etl/chronicle-etl
23
-
24
31
  ```
25
32
  Using rubygems:
26
33
  ```sh
@@ -38,11 +45,15 @@ $ chronicle-etl --version
38
45
  # Display help
39
46
  $ chronicle-etl help
40
47
 
41
- # Basic job usage
48
+ # Run a basic job
42
49
  $ chronicle-etl --extractor NAME --transformer NAME --loader NAME
43
50
 
44
51
  # Read test.csv and display it to stdout as a table
45
- $ chronicle-etl --extractor csv --input ./data.csv --loader table
52
+ $ chronicle-etl --extractor csv --input data.csv --loader table
53
+
54
+ # Show available plugins and install one
55
+ $ chronicle-etl plugins:list
56
+ $ chronicle-etl plugins:install shell
46
57
 
47
58
  # Retrieve shell commands run in the last 5 hours
48
59
  $ chronicle-etl -e shell --since 5h
@@ -79,37 +90,36 @@ Options:
79
90
  [--silent], [--no-silent] # Silence all output
80
91
  ```
81
92
 
82
- ### Saving jobs
83
-
84
- You can save details about a job to a local config file (saved by default in `~/.config/chronicle/etl/jobs/job_name.yml`) to save yourself the trouble of setting the CLI flags for each run.
93
+ ### Saving a job
94
+ You can save details about a job to a local config file (saved by default in `~/.config/chronicle/etl/jobs/JOB_NAME.yml`) to save yourself the trouble specifying options each time.
85
95
 
86
96
  ```sh
87
97
  # Save a job named 'sample' to ~/.config/chronicle/etl/jobs/sample.yml
88
98
  $ chronicle-etl jobs:save sample --extractor pinboard --since 10d
89
99
 
90
- # Show details about the job
91
- $ chronicle-etl jobs:show sample
92
-
93
100
  # Run the job
94
101
  $ chronicle-etl jobs:run sample
95
- # Or more simply:
96
- $ chronicle-etl sample
102
+
103
+ # Show details about the job
104
+ $ chronicle-etl jobs:show sample
97
105
 
98
106
  # Show all saved jobs
99
107
  $ chronicle-etl jobs:list
100
108
  ```
101
109
 
102
- ## Connectors
103
- Connectors are available to read, process, and load data from different formats or external services.
110
+ ## Connectors and plugins
111
+
112
+ Connectors let you work with different data formats or third-party providers.
113
+
114
+ ### Built-in Connectors
115
+
116
+ `chronicle-etl` comes with several built-in connectors for common formats and sources.
104
117
 
105
118
  ```sh
106
119
  # List all available connectors
107
120
  $ chronicle-etl connectors:list
108
121
  ```
109
122
 
110
- ### Built-in Connectors
111
- `chronicle-etl` comes with several built-in connectors for common formats and sources.
112
-
113
123
  #### Extractors
114
124
  - [`csv`](https://github.com/chronicle-app/chronicle-etl/blob/main/lib/chronicle/etl/extractors/csv_extractor.rb) - Load records from CSV files or stdin
115
125
  - [`json`](https://github.com/chronicle-app/chronicle-etl/blob/main/lib/chronicle/etl/extractors/json_extractor.rb) - Load JSON (either [line-separated objects](https://en.wikipedia.org/wiki/JSON_streaming#Line-delimited_JSON) or one object)
@@ -124,18 +134,19 @@ $ chronicle-etl connectors:list
124
134
  - [`json`](https://github.com/chronicle-app/chronicle-etl/blob/main/lib/chronicle/etl/loaders/json_loader.rb) - Load records serialized as JSON
125
135
  - [`rest`](https://github.com/chronicle-app/chronicle-etl/blob/main/lib/chronicle/etl/loaders/rest_loader.rb) - Serialize records with [JSONAPI](https://jsonapi.org/) and send to a REST API
126
136
 
127
- ## Chronicle Plugins
128
- Plugins provide access to data from third-party platforms, services, or formats. Plugins are packaged as separate rubygems and can be installed through the CLI (which installs the Gems under the hood).
137
+ ### Chronicle Plugins for third-party services
138
+
139
+ Plugins provide access to data from third-party platforms, services, or formats. Plugins are packaged as separate gems and can be installed through the CLI (under the hood, it's a `gem install chronicle-PLUGINNAME`)
129
140
 
130
- ### Plugin usage
141
+ #### Plugin usage
131
142
 
132
143
  ```bash
144
+ # List available plugins
145
+ $ chronicle-etl plugins:list
146
+
133
147
  # Install a plugin
134
148
  $ chronicle-etl plugins:install NAME
135
149
 
136
- # List installed plugins
137
- $ chronicle-etl plugins:list
138
-
139
150
  # Use a plugin
140
151
  $ chronicle-etl plugins:install shell
141
152
  $ chronicle-etl --extractor shell:history --limit 10
@@ -143,31 +154,45 @@ $ chronicle-etl --extractor shell:history --limit 10
143
154
  # Uninstall a plugin
144
155
  $ chronicle-etl plugins:uninstall NAME
145
156
  ```
146
-
147
- ### Status
157
+ #### Available plugins and connectors
158
+
159
+ The following are the officially-supported list of plugins and their available connectors:
160
+
161
+ | Plugin | Type | Identifier | Description | Description |
162
+ |---------------------------------------------------------------------|-------------|------------------|-------------------------------------------|-------------------------------------------|
163
+ | [email](https://github.com/chronicle-app/chronicle-email) | extractor | imap | emails over an IMAP connection | emails over an IMAP connection |
164
+ | [email](https://github.com/chronicle-app/chronicle-email) | extractor | mbox | emails from an .mbox file | emails from an .mbox file |
165
+ | [email](https://github.com/chronicle-app/chronicle-email) | transformer | email | email to Chronicle Schema | email to Chronicle Schema |
166
+ | [foursquare](https://github.com/chronicle-app/chronicle-foursquare) | extractor | checkins | Foursqure visits | Foursqure visits |
167
+ | [foursquare](https://github.com/chronicle-app/chronicle-foursquare) | transformer | checkin | checkin to Chronicle Schema | checkin to Chronicle Schema |
168
+ | [github](https://github.com/chronicle-app/chronicle-github) | extractor | activity | user activity stream | user activity stream |
169
+ | [imessage](https://github.com/chronicle-app/chronicle-imessage) | extractor | messages | imessages from local macOS | imessages from local macOS |
170
+ | [imessage](https://github.com/chronicle-app/chronicle-imessage) | transformer | message | imessage to Chronicle Schema | imessage to Chronicle Schema |
171
+ | [pinboard](https://github.com/chronicle-app/chronicle-pinboard) | extractor | bookmarks | Pinboard.in bookmarks | Pinboard.in bookmarks |
172
+ | [pinboard](https://github.com/chronicle-app/chronicle-pinboard) | transformer | bookmark | bookmark to Chronicle Schema | bookmark to Chronicle Schema |
173
+ | [safari](https://github.com/chronicle-app/chronicle-safari) | extractor | browser-history | browser history | browser history |
174
+ | [safari ](https://github.com/chronicle-app/chronicle-safari ) | transformer | browser-history | browser history to Chronicle Schema | browser history to Chronicle Schema |
175
+ | [shell](https://github.com/chronicle-app/chronicle-shell) | extractor | history | shell command history (bash / zsh) | shell command history (bash / zsh) |
176
+ | [shell](https://github.com/chronicle-app/chronicle-shell) | transformer | command | command to Chronicle Schema | command to Chronicle Schema |
177
+ | [spotify](https://github.com/chronicle-app/chronicle-spotify) | extractor | liked-tracks | liked tracks | liked tracks |
178
+ | [spotify](https://github.com/chronicle-app/chronicle-spotify) | extractor | saved-albums | saved albums | saved albums |
179
+ | [spotify](https://github.com/chronicle-app/chronicle-spotify) | extractor | listens | recently listened tracks (last 50 tracks) | recently listened tracks (last 50 tracks) |
180
+ | [spotify](https://github.com/chronicle-app/chronicle-spotify) | transformer | like | like to Chronicle Schema | like to Chronicle Schema |
181
+ | [spotify](https://github.com/chronicle-app/chronicle-spotify) | transformer | listen | listen to Chronicle Schema | listen to Chronicle Schema |
182
+ | [spotify](https://github.com/chronicle-app/chronicle-spotify) | authorizer | | OAuth authorizer | OAuth authorizer |
183
+ | [zulip](https://github.com/chronicle-app/chronicle-zulip) | extractor | private-messages | private messages | private messages |
184
+ | [zulip](https://github.com/chronicle-app/chronicle-zulip) | transformer | message | message to Chronicle Schema | message to Chronicle Schema |
185
+
186
+
187
+ ### Coming soon
148
188
 
149
189
  A few dozen importers exist [in my Memex project](https://hyfen.net/memex/) and I'm porting them over to the Chronicle system. The [Chronicle Plugin Tracker](https://github.com/orgs/chronicle-app/projects/1/views/1) lets you keep track what's available and what's coming soon.
150
190
 
151
191
  If you don't see a plugin for a third-party provider or data source that you're interested in using with `chronicle-etl`, [please open an issue](https://github.com/chronicle-app/chronicle-etl/issues/new). If you want to work together on a plugin, please [get in touch](#get-in-touch)!
152
192
 
153
- #### Currently available
154
-
155
- | Name | Description | Availability |
156
- |-----------------------------------------------------------------|---------------------------------------------------------------------------------------------|----------------------------------|
157
- | [imessage](https://github.com/chronicle-app/chronicle-imessage) | iMessage messages and attachments | Available |
158
- | [shell](https://github.com/chronicle-app/chronicle-shell) | Shell command history | Available (still needs zsh support) |
159
- | [email](https://github.com/chronicle-app/chronicle-email) | Emails and attachments from IMAP or .mbox files | Available (still needs IMAP support) |
160
- | [pinboard](https://github.com/chronicle-app/chronicle-email) | Bookmarks and tags | Available |
161
- | [safari](https://github.com/chronicle-app/chronicle-safari) | Browser history from local sqlite db | Available |
162
- | [github](https://github.com/chronicle-app/chronicle-github) | Github activity stream | Available |
163
-
164
- #### Coming soon
165
-
166
193
  In summary, the following **are coming soon**:
167
194
  anki, arc, bear, chrome, facebook, firefox, fitbit, foursquare, git, github, goodreads, google-calendar, images, instagram, lastfm, shazam, slack, strava, things, twitter, whatsapp, youtube.
168
195
 
169
- Please check the [Chronicle Plugin Tracker](https://github.com/orgs/chronicle-app/projects/1/views/1) for details.
170
-
171
196
  ### Writing your own plugin
172
197
 
173
198
  Additional connectors are packaged as separate ruby gems. You can view the [iMessage plugin](https://github.com/chronicle-app/chronicle-imessage) for an example.
@@ -202,6 +227,7 @@ module Chronicle
202
227
  end
203
228
  ```
204
229
 
230
+
205
231
  ## Secrets Management
206
232
 
207
233
  If your job needs secrets such as access tokens or passwords, `chronicle-etl` has a built-in secret management system.
@@ -236,8 +262,8 @@ $ chronicle-etl secrets:unset pinboard access_token
236
262
  ## Roadmap
237
263
 
238
264
  - Keep tackling **new plugins**. See: [Chronicle Plugin Tracker](https://github.com/orgs/chronicle-app/projects/1)
239
- - Add support for **incremental extractions** #37
240
- - **Improve stdin extractor and shell command transformer** (#5) so that users can easily integrate their own scripts/tools into jobs
265
+ - Add support for **incremental extractions** ([#37](https://github.com/chronicle-app/chronicle-etl/issues/37))
266
+ - **Improve stdin extractor and shell command transformer** so that users can easily integrate their own scripts/languages/tools into jobs ([#5](https://github.com/chronicle-app/chronicle-etl/issues/48))
241
267
  - **Add documentation for Chronicle Schema**. It's found throughout this project but never explained.
242
268
 
243
269
  ## Development
@@ -40,10 +40,14 @@ Gem::Specification.new do |spec|
40
40
  spec.add_dependency "activesupport", "~> 7.0"
41
41
  spec.add_dependency "chronic_duration", "~> 0.10.6"
42
42
  spec.add_dependency "colorize", "~> 0.8.1"
43
+ spec.add_dependency "gems", ">= 1"
44
+ spec.add_dependency "launchy"
43
45
  spec.add_dependency "marcel", "~> 1.0.2"
44
46
  spec.add_dependency "mini_exiftool", "~> 2.10"
45
47
  spec.add_dependency "nokogiri", "~> 1.13"
48
+ spec.add_dependency "omniauth", "~> 2"
46
49
  spec.add_dependency "sequel", "~> 5.35"
50
+ spec.add_dependency "sinatra", "~> 2"
47
51
  spec.add_dependency "sqlite3", "~> 1.4"
48
52
  spec.add_dependency "thor", "~> 1.2"
49
53
  spec.add_dependency "thor-hollaback", "~> 0.2"
@@ -54,12 +58,14 @@ Gem::Specification.new do |spec|
54
58
  spec.add_dependency "xdg", ">= 4.0"
55
59
 
56
60
  spec.add_development_dependency "bundler", "~> 2.1"
61
+ spec.add_development_dependency "fakefs", "~> 1.4"
57
62
  spec.add_development_dependency "guard-rspec", "~> 4.7.3"
58
- spec.add_development_dependency "fakefs"
59
63
  spec.add_development_dependency "pry-byebug", "~> 3.9"
60
64
  spec.add_development_dependency "rake", "~> 13.0"
61
65
  spec.add_development_dependency "rspec", "~> 3.9"
62
66
  spec.add_development_dependency "rubocop", "~> 1.25.1"
63
67
  spec.add_development_dependency "simplecov", "~> 0.21"
68
+ spec.add_development_dependency "vcr", "~> 6.1"
69
+ spec.add_development_dependency "webmock", "~> 3"
64
70
  spec.add_development_dependency "yard", "~> 0.9.7"
65
71
  end
@@ -0,0 +1,48 @@
1
+ require 'sinatra'
2
+ require 'omniauth'
3
+
4
+ module Chronicle
5
+ module ETL
6
+ class AuthorizationServer < Sinatra::Base
7
+ class << self
8
+ attr_accessor :latest_authorization
9
+ end
10
+
11
+ configure do
12
+ set :inline_templates, true
13
+ set :dump_errors, false
14
+ set :raise_errors, true
15
+ disable :logging
16
+ set :sessions, true
17
+ set :quiet, true
18
+ set :threaded, true
19
+ set :environment, ENV['APP_ENV'] == 'test' ? :test : :production
20
+ end
21
+
22
+ use OmniAuth::Builder do
23
+ Chronicle::ETL::OauthAuthorizer.all.each do |klass|
24
+ args = [klass.client_id, klass.client_secret, klass.options].compact
25
+ provider(
26
+ klass.strategy,
27
+ *args
28
+ )
29
+ end
30
+ end
31
+
32
+ OmniAuth.config.logger = Chronicle::ETL::Logger
33
+ OmniAuth.config.silence_get_warning = true
34
+ OmniAuth.config.allowed_request_methods = %i[get]
35
+
36
+ get '/auth/:provider/callback' do
37
+ authorization = request.env['omniauth.auth'].to_h.deep_transform_keys(&:to_sym)
38
+ self.class.latest_authorization = authorization
39
+ erb "<h1>Settings saved for #{params[:provider]}</h1><p>You can now close this tab and return to your terminal!</p>"
40
+ end
41
+
42
+ get '/auth/failure' do
43
+ # TODO: handle this
44
+ erb "<h1>Authentication Failed:</h1><h3>message:<h3> <pre>#{params}</pre>"
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,37 @@
1
+ module Chronicle
2
+ module ETL
3
+ # An authorization strategy for a third-party data source
4
+ class Authorizer
5
+ class << self
6
+ attr_reader :provider_name
7
+
8
+ # Macro for setting provider on an Authorizer
9
+ def provider(provider_name)
10
+ @provider_name = provider_name
11
+ end
12
+
13
+ # From all loaded Authorizers, return the first one that matches
14
+ # a given provider
15
+ #
16
+ # @todo Have a proper identifier system for authorizers
17
+ # (to have more than one per plugin)
18
+ def find_by_provider(provider)
19
+ ObjectSpace.each_object(::Class).select {|klass| klass < self }.find do |authorizer|
20
+ authorizer.provider_name == provider
21
+ end
22
+ end
23
+ end
24
+
25
+ # Construct a new authorizer
26
+ def initialize(args)
27
+ end
28
+
29
+ # Main entry-point for authorization flows. Implemented by subclass
30
+ def authorize!
31
+ raise NotImplementedError
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ require_relative 'oauth_authorizer'
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra'
4
+ require 'launchy'
5
+ require 'pp'
6
+
7
+ module Chronicle
8
+ module ETL
9
+ module CLI
10
+ # CLI commands for authorizing chronicle-etl with third-party services
11
+ class Authorizations < SubcommandBase
12
+ default_task 'new'
13
+ namespace :authorizations
14
+
15
+ desc "authorize", "Authorize with a third-party provider"
16
+ option :port, desc: 'Port to run authorization server on', type: :numeric, default: 4567
17
+ option :credentials, desc: 'Secrets namespace for where to read credentials from (default: PROVIDER)', type: :string, banner: 'NAMESPACE'
18
+ option :secrets, desc: 'Secrets namespace for where authorization should be saved to (default: PROVIDER)', type: :string, banner: 'NAMESPACE'
19
+ option :print, desc: 'Show authorization results (instead of just saving secrets)', type: :boolean, default: false
20
+ def new(provider)
21
+ authorizer_klass = find_authorizer_klass(provider)
22
+ credentials = load_credentials(provider: provider, credentials_source: options[:credentials])
23
+ authorizer = authorizer_klass.new(port: options[:port], credentials: credentials)
24
+
25
+ secrets = authorizer.authorize!
26
+ secrets_namespace = options[:secrets] || provider
27
+ Chronicle::ETL::Secrets.set_all(secrets_namespace, secrets)
28
+
29
+ pp secrets if options[:print]
30
+
31
+ cli_exit(message: "Authorization saved to '#{secrets_namespace}' secrets")
32
+ rescue StandardError => e
33
+ cli_fail(message: "Authorization not successful.\n" + e.message, exception: e)
34
+ end
35
+
36
+ private
37
+
38
+ def find_authorizer_klass(provider)
39
+ # TODO: this assumes provider:plugin one-to-one
40
+ unless Chronicle::ETL::Registry::Plugins.installed?(provider)
41
+ cli_fail(message: "Plugin for #{provider} is not installed.")
42
+ end
43
+
44
+ begin
45
+ Chronicle::ETL::Registry::Plugins.activate(provider)
46
+ rescue PluginError => e
47
+ cli_fail(message: "Could not load plugin '#{provider}'.\n" + e.message, exception: e)
48
+ end
49
+
50
+ Authorizer.find_by_provider(provider.to_sym) || cli_fail(message: "No authorizer available for '#{provider}'")
51
+ end
52
+
53
+ def load_credentials(provider:, credentials_source: nil)
54
+ if credentials_source && !Chronicle::ETL::Secrets.exists?(credentials_source)
55
+ cli_fail(message: "OAuth credentials specified as '#{credentials_source}' but a secrets namespace with that name does not exist.")
56
+ end
57
+
58
+ Chronicle::ETL::Secrets.read(credentials_source || provider)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -6,6 +6,10 @@ module Chronicle
6
6
  no_commands do
7
7
  # Shorthand for cli_exit(status: :failure)
8
8
  def cli_fail(message: nil, exception: nil)
9
+ if exception && Chronicle::ETL::Logger.log_level > Chronicle::ETL::Logger::DEBUG
10
+ message += "\nRe-run the command with --verbose to see details."
11
+ end
12
+
9
13
  cli_exit(status: :failure, message: message, exception: exception)
10
14
  end
11
15
 
@@ -13,7 +13,7 @@ module Chronicle
13
13
  desc "list", "Lists available connectors"
14
14
  # Display all available connectors that chronicle-etl has access to
15
15
  def list
16
- connector_info = Chronicle::ETL::Registry.connectors.map do |connector_registration|
16
+ connector_info = Chronicle::ETL::Registry::Connectors.connectors.map do |connector_registration|
17
17
  {
18
18
  identifier: connector_registration.identifier,
19
19
  phase: connector_registration.phase,
@@ -43,7 +43,7 @@ module Chronicle
43
43
  end
44
44
 
45
45
  begin
46
- connector = Chronicle::ETL::Registry.find_by_phase_and_identifier(phase.to_sym, identifier)
46
+ connector = Chronicle::ETL::Registry::Connectors.find_by_phase_and_identifier(phase.to_sym, identifier)
47
47
  rescue Chronicle::ETL::ConnectorNotAvailableError, Chronicle::ETL::PluginError => e
48
48
  cli_fail(message: "Could not find #{phase} #{identifier}", exception: e)
49
49
  end
@@ -43,6 +43,17 @@ module Chronicle
43
43
  LONG_DESC
44
44
  # Run an ETL job
45
45
  def start(name = nil)
46
+ # If someone runs `$ chronicle-etl` with no arguments, show help menu.
47
+ # TODO: decide if we should check that there's nothing in stdin pipe
48
+ # in case user wants to actually run this sort of job stdin->null->stdout
49
+ if name.nil? && options[:extractor].nil?
50
+ m = Chronicle::ETL::CLI::Main.new
51
+ m.help
52
+ cli_exit
53
+ end
54
+
55
+ cli_fail(message: "Job '#{name}' does not exist") if name && !Chronicle::ETL::Config.exists?("jobs", name)
56
+
46
57
  job_definition = build_job_definition(name, options)
47
58
 
48
59
  if job_definition.plugins_missing?
@@ -82,11 +93,10 @@ LONG_DESC
82
93
 
83
94
  if write_config
84
95
  Chronicle::ETL::Config.write("jobs", name, job_definition.definition)
85
- cli_exit(message: "Job saved. Run it with `$chronicle-etl jobs:run #{name}`")
96
+ cli_exit(message: "Job saved. Run it with `$ chronicle-etl jobs:run #{name}`")
86
97
  else
87
98
  cli_fail(message: "\nJob not saved")
88
99
  end
89
-
90
100
  rescue Chronicle::ETL::JobDefinitionError => e
91
101
  cli_fail(message: "Job definition error", exception: e)
92
102
  end
@@ -94,6 +104,8 @@ LONG_DESC
94
104
  desc "show", "Show details about a job"
95
105
  # Show an ETL job
96
106
  def show(name = nil)
107
+ cli_fail(message: "Job '#{name}' does not exist") if name && !Chronicle::ETL::Config.exists?("jobs", name)
108
+
97
109
  job_definition = build_job_definition(name, options)
98
110
  job_definition.validate!
99
111
  puts Chronicle::ETL::Job.new(job_definition)
@@ -132,10 +144,11 @@ LONG_DESC
132
144
  job_definition.validate!
133
145
  # FIXME: clumsy to make CLI responsible for setting secrets here. Think about a better way to do this
134
146
  job_definition.apply_default_secrets
135
-
136
147
  job = Chronicle::ETL::Job.new(job_definition)
137
148
  runner = Chronicle::ETL::Runner.new(job)
138
149
  runner.run!
150
+ rescue RunnerError => e
151
+ cli_fail(message: "#{e.message}", exception: e)
139
152
  end
140
153
 
141
154
  # TODO: probably could merge this with something in cli/plugin
@@ -27,6 +27,9 @@ module Chronicle
27
27
  desc 'secrets:COMMAND', 'Manage secrets', hide: true
28
28
  subcommand 'secrets', Secrets
29
29
 
30
+ desc 'authorizations', 'Authorize', hide: true
31
+ subcommand 'authorizations', Authorizations
32
+
30
33
  # Entrypoint for the CLI
31
34
  def self.start(given_args = ARGV, config = {})
32
35
  # take a subcommand:command and splits them so Thor knows how to hand off to the subcommand class
@@ -54,24 +57,40 @@ module Chronicle
54
57
  klass, task = ::Thor::Util.find_class_and_task_by_namespace("#{meth}:#{meth}")
55
58
  klass.start(['-h', task].compact, shell: shell)
56
59
  else
57
- shell.say "ABOUT".bold
58
- shell.say " #{'chronicle-etl'.italic} is a utility tool for #{'extracting'.underline}, #{'transforming'.underline}, and #{'loading'.underline} personal data."
60
+ shell.say "ABOUT:".bold
61
+ shell.say " #{'chronicle-etl'.italic} is a toolkit for extracting and working with your digital"
62
+ shell.say " history. 📜"
63
+ shell.say
64
+ shell.say " A job #{'extracts'.underline} personal data from a source, #{'transforms'.underline} it (Chronicle"
65
+ shell.say " Schema or preserves raw data), and then #{'loads'.underline} it to a destination. Use"
66
+ shell.say " built-in extractors (json, csv, stdin) and loaders (csv, json, table,"
67
+ shell.say " rest) or use plugins to connect to third-party services."
68
+ shell.say
69
+ shell.say " Plugins: https://github.com/chronicle-app/chronicle-etl#currently-available"
59
70
  shell.say
60
- shell.say "USAGE".bold
61
- shell.say " $ chronicle-etl COMMAND"
71
+ shell.say "USAGE:".bold
72
+ shell.say " # Basic job usage:".italic.light_black
73
+ shell.say " $ chronicle-etl --extractor NAME --transformer NAME --loader NAME"
62
74
  shell.say
63
- shell.say "EXAMPLES".bold
64
- shell.say " Show available connectors:".italic.light_black
65
- shell.say " $ chronicle-etl connectors:list"
75
+ shell.say " # Read test.csv and display it to stdout as a table:".italic.light_black
76
+ shell.say " $ chronicle-etl --extractor csv --input data.csv --loader table"
66
77
  shell.say
67
- shell.say " Run a simple job:".italic.light_black
68
- shell.say " $ chronicle-etl jobs:run --extractor stdin --transformer null --loader stdout"
78
+ shell.say " # Show available plugins:".italic.light_black
79
+ shell.say " $ chronicle-etl plugins:list"
69
80
  shell.say
70
- shell.say " Show full job options:".italic.light_black
81
+ shell.say " # Save an access token as a secret and use it in a job:".italic.light_black
82
+ shell.say " $ chronicle-etl secrets:set pinboard access_token username:foo123"
83
+ shell.say " $ chronicle-etl secrets:list"
84
+ shell.say " $ chronicle-etl -e pinboard --since 1mo"
85
+ shell.say
86
+ shell.say " # Show full job options:".italic.light_black
71
87
  shell.say " $ chronicle-etl jobs help run"
88
+ shell.say
89
+ shell.say "FULL DOCUMENTATION:".bold
90
+ shell.say " https://github.com/chronicle-app/chronicle-etl".blue
91
+ shell.say
72
92
 
73
93
  list = []
74
-
75
94
  ::Thor::Util.thor_classes_in(Chronicle::ETL::CLI).each do |thor_class|
76
95
  list += thor_class.printable_tasks(false)
77
96
  end
@@ -79,25 +98,18 @@ module Chronicle
79
98
  list.unshift ["help", "# This help menu"]
80
99
 
81
100
  shell.say
82
- shell.say 'ALL COMMANDS'.bold
101
+ shell.say 'ALL COMMANDS:'.bold
83
102
  shell.print_table(list, indent: 2, truncate: true)
84
103
  shell.say
85
- shell.say "VERSION".bold
104
+ shell.say "VERSION:".bold
86
105
  shell.say " #{Chronicle::ETL::VERSION}"
87
106
  shell.say
88
107
  shell.say " Display current version:".italic.light_black
89
108
  shell.say " $ chronicle-etl --version"
90
- shell.say
91
- shell.say "FULL DOCUMENTATION".bold
92
- shell.say " https://github.com/chronicle-app/chronicle-etl".blue
93
- shell.say
94
109
  end
95
110
  end
96
111
 
97
112
  no_commands do
98
- def testb
99
- puts "hi"
100
- end
101
113
  def set_color_output
102
114
  String.disable_colorization true if options[:'no-color'] || ENV['NO_COLOR']
103
115
  end
@@ -16,7 +16,7 @@ module Chronicle
16
16
  cli_fail(message: "Please specify a plugin to install") unless plugins.any?
17
17
 
18
18
  installed, not_installed = plugins.partition do |plugin|
19
- Chronicle::ETL::Registry::PluginRegistry.installed?(plugin)
19
+ Chronicle::ETL::Registry::Plugins.installed?(plugin)
20
20
  end
21
21
 
22
22
  puts "Already installed: #{installed.join(", ")}" if installed.any?
@@ -27,7 +27,7 @@ module Chronicle
27
27
 
28
28
  not_installed.each do |plugin|
29
29
  spinner.update(title: "Installing #{plugin}")
30
- Chronicle::ETL::Registry::PluginRegistry.install(plugin)
30
+ Chronicle::ETL::Registry::Plugins.install(plugin)
31
31
 
32
32
  rescue Chronicle::ETL::PluginError => e
33
33
  spinner.error("Error".red)
@@ -41,7 +41,7 @@ module Chronicle
41
41
  def uninstall(name)
42
42
  spinner = TTY::Spinner.new("[:spinner] Uninstalling plugin #{name}...", format: :dots_2)
43
43
  spinner.auto_spin
44
- Chronicle::ETL::Registry::PluginRegistry.uninstall(name)
44
+ Chronicle::ETL::Registry::Plugins.uninstall(name)
45
45
  spinner.success("(#{'successful'.green})")
46
46
  rescue Chronicle::ETL::PluginError => e
47
47
  spinner.error("Error".red)
@@ -51,20 +51,24 @@ module Chronicle
51
51
  desc "list", "Lists available plugins"
52
52
  # Display all available plugins that chronicle-etl has access to
53
53
  def list
54
- plugins = Chronicle::ETL::Registry::PluginRegistry.all_installed_latest
55
-
56
- info = plugins.map do |plugin|
57
- {
58
- name: plugin.name.sub("chronicle-", ""),
59
- description: plugin.description,
60
- version: plugin.version
61
- }
54
+ values = Chronicle::ETL::Registry::Plugins.all
55
+ .map do |plugin|
56
+ [
57
+ plugin.name,
58
+ plugin.description,
59
+ plugin.installed ? '✓' : '',
60
+ plugin.version
61
+ ]
62
62
  end
63
63
 
64
- headers = ['name', 'description', 'latest version'].map{ |h| h.to_s.upcase.bold }
65
- table = TTY::Table.new(headers, info.map(&:values))
66
- puts "Installed plugins:"
67
- puts table.render(indent: 2, padding: [0, 0])
64
+ headers = ['name', 'description', 'installed', 'version'].map{ |h| h.to_s.upcase.bold }
65
+ table = TTY::Table.new(headers, values)
66
+ puts "Available plugins:"
67
+ puts table.render(
68
+ indent: 2,
69
+ padding: [0, 0],
70
+ alignments: [:left, :left, :center, :left]
71
+ )
68
72
  end
69
73
  end
70
74
  end