rubiclifier 0.1.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.
- checksums.yaml +7 -0
- data/Gemfile +6 -0
- data/lib/args.rb +35 -0
- data/lib/base_api.rb +52 -0
- data/lib/base_application.rb +118 -0
- data/lib/cipher.rb +29 -0
- data/lib/colorized_strings.rb +29 -0
- data/lib/database.rb +62 -0
- data/lib/notification.rb +30 -0
- data/lib/rubiclifier.rb +8 -0
- data/lib/setting.rb +39 -0
- metadata +55 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: fd4ba1b9ad523fe07ff82c350cb25fdbc4e888b05d3706acb65939af67fb3fed
|
|
4
|
+
data.tar.gz: b99ec401e3abe0a155c7548babe520dad1385ab64151162237bb92a3080361a6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e543847ffc6a0f3a9de7a6fdc19355bd5a13157f721c3924d1b5e9ac839f8d7e56660e63aca4a21f497b31dc4393fb7ffa0af02ed8692d21c4e8cf497e3e2199
|
|
7
|
+
data.tar.gz: b3b203e291003208172f463cb2d9241fb7b99b5ad2e185513b0002701d1c4466c71bed82497d14671fa7f7b3281e20fea2623e0af4fb0971e55e94a761c5fdae
|
data/Gemfile
ADDED
data/lib/args.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Rubiclifier
|
|
2
|
+
class Args
|
|
3
|
+
attr_reader :args
|
|
4
|
+
private :args
|
|
5
|
+
|
|
6
|
+
def initialize(args)
|
|
7
|
+
@args = args
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def command
|
|
11
|
+
args[0]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def subcommand
|
|
15
|
+
args[1]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def none?
|
|
19
|
+
args.length == 0
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def string(full_name, aliased = nil)
|
|
23
|
+
pos = args.find_index { |a| a == "--#{full_name}" || a == "-#{aliased}" }
|
|
24
|
+
pos && args[pos + 1]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def number(full_name, aliased = nil)
|
|
28
|
+
string(full_name, aliased)&.to_i
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def boolean(full_name, aliased = nil)
|
|
32
|
+
!!args.find_index { |a| a == "--#{full_name}" || a == "-#{aliased}" }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/base_api.rb
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require "httparty"
|
|
2
|
+
|
|
3
|
+
module Rubiclifier
|
|
4
|
+
class BaseApi
|
|
5
|
+
include HTTParty
|
|
6
|
+
|
|
7
|
+
def self.api_token
|
|
8
|
+
base_uri(base_api_url)
|
|
9
|
+
@api_token ||= DB.get_setting(api_token_db_key) ||
|
|
10
|
+
begin
|
|
11
|
+
t = login_and_get_api_token
|
|
12
|
+
if t.nil?
|
|
13
|
+
invalid_credentials_error
|
|
14
|
+
else
|
|
15
|
+
DB.save_setting(api_token_db_key, t, is_secret: true)
|
|
16
|
+
t
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.invalid_credentials_error
|
|
22
|
+
raise "Invalid credentials!"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.login_and_get_api_token
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.api_token_db_key
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.base_api_url_db_key
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.wrap_with_authentication(&block)
|
|
38
|
+
res = block.call
|
|
39
|
+
if res.code == 401 || res.code == 403
|
|
40
|
+
@api_token = nil
|
|
41
|
+
DB.save_setting(api_token_db_key, nil, is_secret: false)
|
|
42
|
+
block.call
|
|
43
|
+
else
|
|
44
|
+
res
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.base_api_url
|
|
49
|
+
@base_api_url ||= DB.get_setting(base_api_url_db_key)&.chomp("/")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require "io/console"
|
|
2
|
+
require_relative "./args.rb"
|
|
3
|
+
require_relative "./colorized_strings.rb"
|
|
4
|
+
require_relative "./setting.rb"
|
|
5
|
+
|
|
6
|
+
module Rubiclifier
|
|
7
|
+
class BaseApplication
|
|
8
|
+
attr_reader :args
|
|
9
|
+
private :args
|
|
10
|
+
|
|
11
|
+
def initialize(args)
|
|
12
|
+
@args = Args.new(args)
|
|
13
|
+
DB.hydrate(data_directory, migrations_location)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
show_help if args.command == "help" || args.boolean("help", "h")
|
|
18
|
+
setup_or_fail
|
|
19
|
+
run_application
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def show_help
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def run_application
|
|
27
|
+
raise NotImplementedError
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def additional_setup
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def not_setup
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def is_background_service?
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def sends_notifications?
|
|
41
|
+
raise NotImplementedError
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def settings
|
|
45
|
+
raise NotImplementedError
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def executable_name
|
|
49
|
+
raise NotImplementedError
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def data_directory
|
|
53
|
+
raise NotImplementedError
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def migrations_location
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def brew_dependencies
|
|
61
|
+
[]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def setup_or_fail
|
|
65
|
+
if args.command == "setup" || args.boolean("setup", "s")
|
|
66
|
+
puts("Installing brew dependencies...")
|
|
67
|
+
all_brew_dependencies = [
|
|
68
|
+
"sqlite",
|
|
69
|
+
("terminal-notifier" if sends_notifications?)
|
|
70
|
+
].concat(brew_dependencies).compact
|
|
71
|
+
system("brew install #{all_brew_dependencies.join(" ")}")
|
|
72
|
+
|
|
73
|
+
puts
|
|
74
|
+
|
|
75
|
+
settings.each do |s|
|
|
76
|
+
s.populate
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
additional_setup
|
|
80
|
+
|
|
81
|
+
puts
|
|
82
|
+
|
|
83
|
+
if is_background_service?
|
|
84
|
+
setup_as_background_service
|
|
85
|
+
else
|
|
86
|
+
puts("Finished setup! Run with `#{executable_name}`".green)
|
|
87
|
+
end
|
|
88
|
+
exit
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
unless settings.all?(&:is_setup?)
|
|
92
|
+
not_setup
|
|
93
|
+
puts
|
|
94
|
+
puts("Oops! You must finish setup first by running with the `--setup` option.".red)
|
|
95
|
+
puts(" `#{executable_name} --setup`".red)
|
|
96
|
+
exit
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def setup_as_background_service
|
|
101
|
+
puts("It's recommended that you set this up as a system service with serviceman. You can check it out here: https://git.rootprojects.org/root/serviceman".blue)
|
|
102
|
+
puts("Set #{executable_name} to run on startup with `serviceman add --name #{executable_name} #{executable_name}`".blue)
|
|
103
|
+
puts
|
|
104
|
+
print("Would you like this script to set it up for you? (y/n) ")
|
|
105
|
+
if STDIN.gets.chomp.downcase == "y"
|
|
106
|
+
puts "Installing serviceman..."
|
|
107
|
+
system("curl -sL https://webinstall.dev/serviceman | bash")
|
|
108
|
+
puts "Adding #{executable_name} as a service..."
|
|
109
|
+
system("serviceman add --name #{executable_name} #{executable_name}")
|
|
110
|
+
puts
|
|
111
|
+
puts("Finished setup! The service is set to run in the background!".green)
|
|
112
|
+
else
|
|
113
|
+
puts
|
|
114
|
+
puts("Finished setup!".green)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
data/lib/cipher.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require "base64"
|
|
2
|
+
require "openssl"
|
|
3
|
+
require "digest/sha1"
|
|
4
|
+
|
|
5
|
+
class Cipher
|
|
6
|
+
def self.encrypt(text)
|
|
7
|
+
cipher = OpenSSL::Cipher.new("aes-256-cbc")
|
|
8
|
+
cipher.encrypt
|
|
9
|
+
key = Digest::SHA1.hexdigest("yourpass")[0..31]
|
|
10
|
+
iv = cipher.random_iv
|
|
11
|
+
cipher.key = key
|
|
12
|
+
cipher.iv = iv
|
|
13
|
+
encrypted = cipher.update(text)
|
|
14
|
+
encrypted << cipher.final
|
|
15
|
+
["#{key}:#{Base64.encode64(iv).encode("utf-8")}", Base64.encode64(encrypted).encode("utf-8")]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.decrypt(encrypted, salt)
|
|
19
|
+
key, iv = salt.split(":")
|
|
20
|
+
iv = Base64.decode64(iv.encode("ascii-8bit"))
|
|
21
|
+
cipher = OpenSSL::Cipher.new("aes-256-cbc")
|
|
22
|
+
cipher.decrypt
|
|
23
|
+
cipher.key = key
|
|
24
|
+
cipher.iv = iv
|
|
25
|
+
decrypted = cipher.update(Base64.decode64(encrypted.encode("ascii-8bit")))
|
|
26
|
+
decrypted << cipher.final
|
|
27
|
+
decrypted
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class String
|
|
2
|
+
def colorize(color_code)
|
|
3
|
+
"\e[#{color_code}m#{self}\e[0m"
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def red
|
|
7
|
+
colorize(31)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def green
|
|
11
|
+
colorize(32)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def yellow
|
|
15
|
+
colorize(33)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def blue
|
|
19
|
+
colorize(34)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def pink
|
|
23
|
+
colorize(35)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def light_blue
|
|
27
|
+
colorize(36)
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/database.rb
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require "sqlite3"
|
|
2
|
+
require_relative "./cipher.rb"
|
|
3
|
+
|
|
4
|
+
module Rubiclifier
|
|
5
|
+
class DB
|
|
6
|
+
SETTINGS_TABLE_MIGRATION = "CREATE TABLE IF NOT EXISTS settings (key VARCHAR(50) PRIMARY KEY, value TEXT, salt TEXT);"
|
|
7
|
+
|
|
8
|
+
def self.hydrate(data_directory, migrations_location)
|
|
9
|
+
system("mkdir -p \"#{data_directory.sub("~", "$HOME")}\"")
|
|
10
|
+
@conn = SQLite3::Database.new(File.expand_path("#{data_directory}/data.sqlite3"))
|
|
11
|
+
migrate_if_needed(migrations_location)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.conn
|
|
15
|
+
@conn
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.execute(sql)
|
|
19
|
+
conn.execute(sql)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.query_single_row(sql)
|
|
23
|
+
conn.execute(sql) do |row|
|
|
24
|
+
return row
|
|
25
|
+
end
|
|
26
|
+
return nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.get_setting(key)
|
|
30
|
+
row = query_single_row("SELECT value, salt FROM settings WHERE key = '#{key}'")
|
|
31
|
+
if row && row[1]
|
|
32
|
+
Cipher.decrypt(row[0], row[1])
|
|
33
|
+
elsif row
|
|
34
|
+
row[0]
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.save_setting(key, value, is_secret:)
|
|
39
|
+
salt = "NULL"
|
|
40
|
+
if is_secret
|
|
41
|
+
salt, encrypted = Cipher.encrypt(value)
|
|
42
|
+
salt = "'#{salt}'"
|
|
43
|
+
value = encrypted
|
|
44
|
+
end
|
|
45
|
+
conn.execute("DELETE FROM settings WHERE key = '#{key}';")
|
|
46
|
+
conn.execute("INSERT INTO settings (key, value, salt) VALUES('#{key}', '#{value}', #{salt});")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.migrate_if_needed(migrations_location)
|
|
50
|
+
all_migrations = [SETTINGS_TABLE_MIGRATION]
|
|
51
|
+
all_migrations.concat(eval(File.read(migrations_location))) if migrations_location
|
|
52
|
+
conn.execute("CREATE TABLE IF NOT EXISTS migrations (id INT PRIMARY KEY);")
|
|
53
|
+
last_migration = query_single_row("SELECT id FROM migrations ORDER BY id DESC LIMIT 1;")&.to_a&.fetch(0) || -1
|
|
54
|
+
if all_migrations.length - 1 > last_migration
|
|
55
|
+
all_migrations[(last_migration + 1)..-1].each_with_index do |sql, i|
|
|
56
|
+
conn.execute(sql)
|
|
57
|
+
conn.execute("INSERT INTO migrations (id) VALUES (#{i + last_migration + 1});")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/notification.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Rubiclifier
|
|
2
|
+
class Notification
|
|
3
|
+
attr_reader :title, :message, :subtitle, :icon, :url
|
|
4
|
+
private :title, :message, :subtitle, :icon, :url
|
|
5
|
+
def initialize(title, message, subtitle = nil, icon = nil, url = nil)
|
|
6
|
+
@title = title
|
|
7
|
+
@message = message
|
|
8
|
+
@subtitle = subtitle
|
|
9
|
+
@icon = icon
|
|
10
|
+
@url = url
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def send
|
|
14
|
+
args = {
|
|
15
|
+
"title" => title,
|
|
16
|
+
"message" => message,
|
|
17
|
+
"subtitle" => subtitle,
|
|
18
|
+
"appIcon" => icon,
|
|
19
|
+
"open" => url
|
|
20
|
+
}
|
|
21
|
+
all_args = args.keys.reduce("") do |arg_string, key|
|
|
22
|
+
if args[key]
|
|
23
|
+
arg_string += " -#{key} '#{args[key]}'"
|
|
24
|
+
end
|
|
25
|
+
arg_string
|
|
26
|
+
end
|
|
27
|
+
system("/usr/local/bin/terminal-notifier #{all_args}")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/rubiclifier.rb
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
require_relative "./args.rb"
|
|
2
|
+
require_relative "./base_api.rb"
|
|
3
|
+
require_relative "./base_application.rb"
|
|
4
|
+
require_relative "./cipher.rb"
|
|
5
|
+
require_relative "./colorized_strings.rb"
|
|
6
|
+
require_relative "./database.rb"
|
|
7
|
+
require_relative "./notification.rb"
|
|
8
|
+
require_relative "./setting.rb"
|
data/lib/setting.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Rubiclifier
|
|
2
|
+
class Setting
|
|
3
|
+
attr_reader :key, :label, :explanation, :is_secret, :nullable
|
|
4
|
+
private :key, :label, :explanation, :is_secret, :nullable
|
|
5
|
+
|
|
6
|
+
def initialize(key, label, explanation: nil, is_secret: false, nullable: false)
|
|
7
|
+
@key = key
|
|
8
|
+
@label = label
|
|
9
|
+
@explanation = explanation
|
|
10
|
+
@is_secret = is_secret
|
|
11
|
+
@nullable = nullable
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def populate
|
|
15
|
+
print("What's the #{label}? #{explanation_text}")
|
|
16
|
+
input = if is_secret
|
|
17
|
+
STDIN.noecho(&:gets).chomp.tap { puts }
|
|
18
|
+
else
|
|
19
|
+
STDIN.gets.chomp
|
|
20
|
+
end
|
|
21
|
+
if input == ""
|
|
22
|
+
input = nil
|
|
23
|
+
end
|
|
24
|
+
DB.save_setting(key, input, is_secret: is_secret) unless input.nil?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def explanation_text
|
|
28
|
+
if explanation.is_a?(Proc)
|
|
29
|
+
"(#{explanation.call}) "
|
|
30
|
+
elsif explanation
|
|
31
|
+
"(#{explanation}) "
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def is_setup?
|
|
36
|
+
DB.get_setting(key) || nullable
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rubiclifier
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kyle Grinstead
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2020-07-08 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description:
|
|
14
|
+
email: kyleag@hey.com
|
|
15
|
+
executables: []
|
|
16
|
+
extensions: []
|
|
17
|
+
extra_rdoc_files: []
|
|
18
|
+
files:
|
|
19
|
+
- Gemfile
|
|
20
|
+
- lib/args.rb
|
|
21
|
+
- lib/base_api.rb
|
|
22
|
+
- lib/base_application.rb
|
|
23
|
+
- lib/cipher.rb
|
|
24
|
+
- lib/colorized_strings.rb
|
|
25
|
+
- lib/database.rb
|
|
26
|
+
- lib/notification.rb
|
|
27
|
+
- lib/rubiclifier.rb
|
|
28
|
+
- lib/setting.rb
|
|
29
|
+
homepage: https://rubygems.org/gems/rubiclifier
|
|
30
|
+
licenses:
|
|
31
|
+
- MIT
|
|
32
|
+
metadata:
|
|
33
|
+
source_code_uri: https://github.com/MrGrinst/rubiclifier
|
|
34
|
+
post_install_message:
|
|
35
|
+
rdoc_options: []
|
|
36
|
+
require_paths:
|
|
37
|
+
- lib
|
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '0'
|
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
requirements: []
|
|
49
|
+
rubyforge_project:
|
|
50
|
+
rubygems_version: 2.7.6.2
|
|
51
|
+
signing_key:
|
|
52
|
+
specification_version: 4
|
|
53
|
+
summary: Easily spin up new Ruby CLI applications with built-in backgrounding and
|
|
54
|
+
data storage.
|
|
55
|
+
test_files: []
|