sqlui 0.1.69 → 0.1.70

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee20f44f7eec60439ca07c207698d2cc4a0ed19622cd10073185f1bdab4669e6
4
- data.tar.gz: 27a30d2475849a4ec78786fb6ed449245fa5e36fbb9f0baaefcdfb54cb98318d
3
+ metadata.gz: 476cdcf4c9f35aa0b46a5d8dd3f41aa409a4ef5cf72bb0d02099ea44d4eb92e3
4
+ data.tar.gz: f52996d5793db9b6529cf7f1c8f5dd1ee171bd010f00419604e2399d430eb811
5
5
  SHA512:
6
- metadata.gz: f5f53930cfe0ff9539908c47e78b9437ac39d971d4fcaf03e4090a64b7f5a0e25a31d5e7abdb4bbdc216c7703ecd1bbf757c5a4cc15ff3e2f4977879c33d1826
7
- data.tar.gz: d2f8b1df886420a358d1763f7c0c44cedf0eb9cfccab1b54915f505b29e206d4792dfe743a6c8b1e4479a940990ac7112235620dfa68fe0ee5fc83cd50342c76
6
+ metadata.gz: f37ae3ed5654474330b6ce34a432cbe55c87911494819aeb46e279cf22246b48d3afeddeeb7a4f2f69bee8e39e4186d2feab86c1dc8405e7b3f149a9e09a8d87
7
+ data.tar.gz: a8eaad53be8c5740cc63fc0a5d2017a4ff3070fc90521381e3f8d2dce99d5ea399dce493544e4112579a89b0da925c606328739b1b89c137bbeeb1e865bb1af0
data/.release-version CHANGED
@@ -1 +1 @@
1
- 0.1.69
1
+ 0.1.70
data/app/checks.rb ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Some argument check methods.
4
+ module Checks
5
+ def check_non_nil(hash)
6
+ hash.each do |key, value|
7
+ raise "invalid #{key}: #{value.nil? ? 'nil' : value}" if value.nil?
8
+ end
9
+
10
+ hash.size == 1 ? hash.values.first : hash.values
11
+ end
12
+
13
+ def check_is_a(hash)
14
+ hash.each do |key, (clazz, value)|
15
+ raise "invalid #{key} #{clazz}: #{value.nil? ? 'nil' : value} (#{value.class})" unless value.is_a?(clazz)
16
+ end
17
+
18
+ hash.size == 1 ? hash.values.first[1] : hash.values.map { |(_clazz, value)| value }
19
+ end
20
+
21
+ def check_boolean(hash)
22
+ hash.each do |key, value|
23
+ raise "invalid #{key}: #{value.nil? ? 'nil' : value}" unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
24
+ end
25
+
26
+ hash.size == 1 ? hash.values.first : hash.values
27
+ end
28
+
29
+ def check_non_empty_string(hash)
30
+ hash.each do |key, value|
31
+ raise "invalid #{key}: #{value.nil? ? 'nil' : value}" unless value.is_a?(String) && !value.empty?
32
+ end
33
+
34
+ hash.size == 1 ? hash.values.first : hash.values
35
+ end
36
+
37
+ def check_positive_integer(hash)
38
+ hash.each do |key, value|
39
+ raise "invalid #{key}: #{value.nil? ? 'nil' : value}" unless value.is_a?(Integer) && value.positive?
40
+ end
41
+
42
+ hash.size == 1 ? hash.values.first : hash.values
43
+ end
44
+
45
+ def check_enumerable_of(enumerable, clazz)
46
+ check_is_a(enumerable: [Enumerable, enumerable])
47
+ enumerable.each { |value| check_is_a(value: [clazz, value]) }
48
+ end
49
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A latch that allows you to wait for a maximum number of seconds.
4
+ class CountDownLatch
5
+ def initialize(count)
6
+ @count = count
7
+ @mutex = Mutex.new
8
+ @resource = ConditionVariable.new
9
+ end
10
+
11
+ def count_down
12
+ @mutex.synchronize do
13
+ if @count.positive?
14
+ @count -= 1
15
+ @resource.signal if @count.zero?
16
+ end
17
+ @count
18
+ end
19
+ end
20
+
21
+ def count
22
+ @mutex.synchronize { @count }
23
+ end
24
+
25
+ def await(timeout:)
26
+ @mutex.synchronize do
27
+ @resource.wait(@mutex, timeout) if @count.positive?
28
+ raise 'timed out while waiting' unless @count.zero?
29
+ end
30
+ end
31
+ end
@@ -5,10 +5,11 @@ require 'mysql2'
5
5
  require 'set'
6
6
 
7
7
  require_relative 'args'
8
+ require_relative 'saved_config'
8
9
 
9
10
  # Config for a single database.
10
11
  class DatabaseConfig
11
- attr_reader :display_name, :description, :url_path, :joins, :saved_path, :tables, :columns, :client_params
12
+ attr_reader :display_name, :description, :url_path, :joins, :saved_config, :tables, :columns, :client_params
12
13
 
13
14
  def initialize(hash)
14
15
  @display_name = Args.fetch_non_empty_string(hash, :display_name).strip
@@ -17,7 +18,8 @@ class DatabaseConfig
17
18
  raise ArgumentError, 'url_path should not start with a /' if @url_path.start_with?('/')
18
19
  raise ArgumentError, 'url_path should not end with a /' if @url_path.end_with?('/')
19
20
 
20
- @saved_path = Args.fetch_non_empty_string(hash, :saved_path).strip
21
+ saved_config_hash = Args.fetch_optional_hash(hash, :saved_config)
22
+ @saved_config = saved_config_hash.nil? ? nil : SavedConfig.new(saved_config_hash)
21
23
 
22
24
  # Make joins an array. It is only a map to allow for YAML extension.
23
25
  @joins = (Args.fetch_optional_hash(hash, :joins) || {}).values
data/app/deep.rb CHANGED
@@ -1,43 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Deep extensions for Enumerable.
4
- module Enumerable
5
- def deep_transform_keys!(&block)
6
- each { |value| value.deep_transform_keys!(&block) if value.respond_to?(:deep_transform_keys!) }
7
- self
8
- end
9
-
10
- def deep_symbolize_keys!
11
- deep_transform_keys!(&:to_sym)
12
- end
13
-
14
- def deep_dup(result = {})
15
- map do |value|
16
- value.respond_to?(:deep_dup) ? value.deep_dup : value.clone
17
- end
18
- result
19
- end
20
- end
3
+ require 'active_support/core_ext/hash/deep_merge'
4
+ require 'active_support/core_ext/hash/keys'
5
+ require 'active_support/core_ext/object/deep_dup'
21
6
 
22
7
  # Deep extensions for Hash.
23
8
  class Hash
24
- def deep_transform_keys!(&block)
25
- transform_keys!(&block)
26
- each_value { |value| value.deep_transform_keys!(&block) if value.respond_to?(:deep_transform_keys!) }
27
- self
28
- end
29
-
30
- def deep_symbolize_keys!
31
- deep_transform_keys!(&:to_sym)
32
- end
33
-
34
- def deep_dup(result = {})
35
- each do |key, value|
36
- result[key] = value.respond_to?(:deep_dup) ? value.deep_dup : value.clone
37
- end
38
- result
39
- end
40
-
41
9
  def deep_set(*path, value:)
42
10
  raise ArgumentError, 'no path specified' if path.empty?
43
11
 
@@ -63,15 +31,4 @@ class Hash
63
31
  self.[](path[0]).deep_delete(*path[1..])
64
32
  end
65
33
  end
66
-
67
- def deep_merge!(hash)
68
- hash.each do |key, value|
69
- if self[key].is_a?(Hash) && value.is_a?(Hash)
70
- self[key].deep_merge!(value)
71
- else
72
- self[key] = value
73
- end
74
- end
75
- self
76
- end
77
34
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../checks'
4
+
5
+ module Github
6
+ # A cache for GitHub API GET requests.
7
+ class Cache
8
+ include Checks
9
+
10
+ # A cache entry.
11
+ class Entry
12
+ include Checks
13
+
14
+ attr_accessor :value, :expires_at
15
+
16
+ def initialize(value, max_age_seconds)
17
+ @value = check_non_nil(value: value)
18
+ @expires_at = Time.now + check_positive_integer(max_age_seconds: max_age_seconds)
19
+ end
20
+ end
21
+
22
+ def initialize(hash, logger: Logger.new($stdout))
23
+ @mutex = Mutex.new
24
+ @hash = check_is_a(hash: [Hash, hash])
25
+ @logger = check_non_nil(logger: logger)
26
+ end
27
+
28
+ def [](key)
29
+ check_non_empty_string(key: key)
30
+
31
+ @mutex.synchronize do
32
+ evict
33
+ if (value = @hash[key])
34
+ @logger.info "#{self.class} entry found for #{key}"
35
+ value
36
+ else
37
+ @logger.info "#{self.class} entry not found for #{key}"
38
+ nil
39
+ end
40
+ end
41
+ end
42
+
43
+ def []=(key, value)
44
+ check_non_empty_string(key: key)
45
+ check_is_a(value: [Entry, value])
46
+
47
+ @mutex.synchronize do
48
+ @logger.info "#{self.class} caching entry for #{key} until #{value.expires_at}"
49
+ @hash[key] = value
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def evict
56
+ now = Time.now
57
+ @hash.delete_if do |key, value|
58
+ if value.expires_at < now
59
+ @logger.info "#{self.class} evicting #{key}"
60
+ true
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../checks'
4
+ require_relative '../deep'
5
+ require_relative 'cache'
6
+ require_relative 'client'
7
+
8
+ module Github
9
+ # Like a Github::Client but with a cache.
10
+ class CachingClient
11
+ include Checks
12
+
13
+ def initialize(client, cache)
14
+ @client = check_is_a(client: [Client, client])
15
+ @cache = check_is_a(cache: [Cache, cache])
16
+ end
17
+
18
+ def get_with_caching(url, cache_for:)
19
+ check_non_empty_string(url: url)
20
+ check_positive_integer(cache_for: cache_for)
21
+
22
+ if (cache_entry = @cache[url])
23
+ return cache_entry.value
24
+ end
25
+
26
+ response = @client.get(url)
27
+ @cache[url] = Cache::Entry.new(response, cache_for)
28
+ response.deep_dup
29
+ end
30
+
31
+ def get_without_caching(url)
32
+ check_non_empty_string(url: url)
33
+ @client.get(url)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'logger'
5
+ require 'rest-client'
6
+
7
+ require_relative '../checks'
8
+
9
+ module Github
10
+ # It's a GitHub client. I know there is an official client library.
11
+ class Client
12
+ include Checks
13
+
14
+ def initialize(access_token:, logger: Logger.new($stdout))
15
+ @access_token = check_non_empty_string(access_token: access_token)
16
+ @logger = check_non_nil(logger: logger)
17
+ end
18
+
19
+ def get(url)
20
+ check_non_empty_string(url: url)
21
+
22
+ @logger.info "#{self.class} GET #{url}"
23
+ response = RestClient.get(
24
+ url,
25
+ {
26
+ 'Accept' => 'application/vnd.github+json',
27
+ 'Authorization' => "Bearer #{@access_token}",
28
+ 'X-GitHub-Api-Version' => '2022-11-28'
29
+ }
30
+ )
31
+ raise "get #{url} failed: #{response}" unless response.code == 200
32
+
33
+ JSON.parse(response)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../checks'
4
+
5
+ module Github
6
+ # A GitHub file.
7
+ class File
8
+ include Checks
9
+
10
+ attr_reader :display_path, :content, :github_url
11
+
12
+ def initialize(owner:, repo:, branch:, path:, content:)
13
+ check_non_empty_string(owner: owner, repo: repo, branch: branch, path: path)
14
+ @content = check_is_a(content: [String, content])
15
+ @display_path = "#{owner}/#{repo}/#{branch}/#{path}"
16
+ @github_url = "https://github.com/#{owner}/#{repo}/blob/#{branch}/#{path}"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Github
4
+ # Methods for dealing with GitHub paths.
5
+ class Paths
6
+ # "<owner>/<repo>/<ref>/<some_path>"
7
+ PATH_PATTERN = %r{^(?:[^/]+/){3}.*[^/]$}
8
+ private_constant :PATH_PATTERN
9
+
10
+ # Parses a path like "<owner>/<repo>/<ref>/<some_path>" into owner, repo, ref, path.
11
+ def self.parse_file_path(path)
12
+ check_non_empty_string(path: path)
13
+ raise "invalid path: #{path}" unless PATH_PATTERN.match?(path)
14
+
15
+ owner, repo, ref, *path = path.split('/')
16
+ path = path.join('/')
17
+
18
+ [owner, repo, ref, path]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../checks'
4
+ require_relative 'file'
5
+
6
+ module Github
7
+ # A GitHub file tree.
8
+ class Tree
9
+ include Checks
10
+ include Enumerable
11
+
12
+ attr_reader :truncated
13
+
14
+ def initialize(files:, truncated: false)
15
+ @files = check_enumerable_of(files, File)
16
+ @truncated = check_boolean(truncated: truncated)
17
+ end
18
+
19
+ class << self
20
+ include Checks
21
+
22
+ def for(owner:, repo:, branch:, tree_response:)
23
+ check_non_empty_string(owner: owner, repo: repo, branch: branch)
24
+ check_is_a(tree_response: [Hash, tree_response])
25
+
26
+ truncated = check_boolean(truncated: tree_response['truncated'])
27
+ tree = check_is_a(tree: [Array, tree_response['tree']])
28
+ files = tree.map do |blob|
29
+ File.new(owner: owner, repo: repo, branch: branch, path: blob['path'], content: blob['content'])
30
+ end
31
+
32
+ Tree.new(files: files, truncated: truncated)
33
+ end
34
+ end
35
+
36
+ def each(&block)
37
+ @files.each(&block)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'concurrent/executor/fixed_thread_pool'
5
+ require 'json'
6
+ require 'logger'
7
+
8
+ require_relative 'caching_client'
9
+ require_relative 'tree'
10
+ require_relative '../checks'
11
+ require_relative '../count_down_latch'
12
+
13
+ module Github
14
+ # Wraps a Github::Client to provide tree-specific features.
15
+ class TreeClient
16
+ include Checks
17
+
18
+ @thread_pool = Concurrent::FixedThreadPool.new(5)
19
+
20
+ class << self
21
+ attr_reader :thread_pool
22
+ end
23
+
24
+ DEFAULT_MAX_TREE_CACHE_AGE_SECONDS = 60 * 5 # 5 minutes
25
+ private_constant :DEFAULT_MAX_TREE_CACHE_AGE_SECONDS
26
+
27
+ DEFAULT_MAX_FILE_CACHE_AGE_SECONDS = 60 * 60 * 24 * 7 # 1 week
28
+ private_constant :DEFAULT_MAX_FILE_CACHE_AGE_SECONDS
29
+
30
+ MAX_TREE_SIZE = 50
31
+ private_constant :MAX_TREE_SIZE
32
+
33
+ def initialize(access_token:, cache:, logger: Logger.new($stdout))
34
+ check_non_empty_string(access_token: access_token)
35
+ check_is_a(cache: [Cache, cache])
36
+
37
+ @client = Github::CachingClient.new(Github::Client.new(access_token: access_token, logger: logger), cache)
38
+ @logger = check_non_nil(logger: logger)
39
+ end
40
+
41
+ def get_tree(owner:, repo:, branch:, regex:)
42
+ check_non_empty_string(owner: owner, repo: repo, branch: branch)
43
+ check_is_a(regex: [Regexp, regex])
44
+
45
+ response = @client.get_with_caching(
46
+ "https://api.github.com/repos/#{owner}/#{repo}/git/trees/#{branch}?recursive=true",
47
+ cache_for: DEFAULT_MAX_TREE_CACHE_AGE_SECONDS
48
+ )
49
+
50
+ response['tree'] = response['tree'].select { |blob| regex.match?(blob['path']) }
51
+ raise "trees with more than #{MAX_TREE_SIZE} blobs not supported" if response['tree'].size > MAX_TREE_SIZE
52
+
53
+ latch = CountDownLatch.new(response['tree'].size)
54
+ response['tree'].each do |blob|
55
+ TreeClient.thread_pool.post do
56
+ blob['content'] =
57
+ get_file_content_with_caching(owner: owner, repo: repo, path: blob['path'], ref: response['sha'])
58
+ ensure
59
+ latch.count_down
60
+ end
61
+ end
62
+
63
+ latch.await(timeout: 10)
64
+ raise 'failed to load saved files' unless response['tree'].all? { |blob| blob['content'] }
65
+
66
+ Tree.for(owner: owner, repo: repo, branch: branch, tree_response: response)
67
+ end
68
+
69
+ private
70
+
71
+ # Returns the contents of the file at the specified path. Uses the cache.
72
+ def get_file_content_with_caching(owner:, repo:, path:, ref:)
73
+ check_non_empty_string(owner: owner, repo: repo, sha: ref, path: path)
74
+
75
+ response = @client.get_with_caching(
76
+ "https://api.github.com/repos/#{owner}/#{repo}/contents/#{path}?ref=#{ref}",
77
+ cache_for: DEFAULT_MAX_FILE_CACHE_AGE_SECONDS
78
+ )
79
+ Base64.decode64(response['content'])
80
+ end
81
+
82
+ # Returns the contents of the file at the specified path. Uses the cache.
83
+ def get_file_content_without_caching(owner:, repo:, path:, ref:)
84
+ check_non_empty_string(owner: owner, repo: repo, sha: ref, path: path)
85
+
86
+ response = @client.get_without_caching(
87
+ "https://api.github.com/repos/#{owner}/#{repo}/contents/#{path}?ref=#{ref}"
88
+ )
89
+
90
+ Base64.decode64(response['content'])
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'args'
4
+
5
+ # Config for saved files.
6
+ class SavedConfig
7
+ attr_reader :token, :owner, :repo, :branch, :regex
8
+
9
+ def initialize(hash)
10
+ @token = Args.fetch_non_empty_string(hash, :token).strip
11
+ @owner = Args.fetch_non_empty_string(hash, :owner).strip
12
+ @repo = Args.fetch_non_empty_string(hash, :repo).strip
13
+ @branch = Args.fetch_non_empty_string(hash, :branch).strip
14
+ @regex = Regexp.new(Args.fetch_non_empty_string(hash, :regex))
15
+ end
16
+ end
data/app/server.rb CHANGED
@@ -11,6 +11,11 @@ require 'sinatra/base'
11
11
  require 'uri'
12
12
  require 'webrick'
13
13
  require_relative 'database_metadata'
14
+ require_relative 'github/cache'
15
+ require_relative 'github/caching_client'
16
+ require_relative 'github/client'
17
+ require_relative 'github/tree'
18
+ require_relative 'github/tree_client'
14
19
  require_relative 'mysql_types'
15
20
  require_relative 'sql_parser'
16
21
  require_relative 'sqlui'
@@ -22,7 +27,7 @@ class Server < Sinatra::Base
22
27
  @logger ||= WEBrick::Log.new
23
28
  end
24
29
 
25
- def self.init_and_run(config, resources_dir)
30
+ def self.init_and_run(config, resources_dir, github_cache = Github::Cache.new({}, logger: Server.logger))
26
31
  logger.info("Starting SQLUI v#{Version::SQLUI}")
27
32
  logger.info("Airbrake enabled: #{config.airbrake[:server]&.[](:enabled) || false}")
28
33
 
@@ -105,6 +110,13 @@ class Server < Sinatra::Base
105
110
  end
106
111
 
107
112
  post "#{config.base_url_path}/#{database.url_path}/metadata" do
113
+ tree = Github::Tree.new(files: [], truncated: false)
114
+ if (saved_config = database.saved_config)
115
+ tree_client = Github::TreeClient.new(access_token: database.saved_config.token, cache: github_cache,
116
+ logger: Server.logger)
117
+ tree = tree_client.get_tree(owner: saved_config.owner, repo: saved_config.repo, branch: saved_config.branch,
118
+ regex: saved_config.regex)
119
+ end
108
120
  metadata = database.with_client do |client|
109
121
  {
110
122
  server: "#{config.name} - #{database.display_name}",
@@ -113,19 +125,18 @@ class Server < Sinatra::Base
113
125
  tables: database.tables,
114
126
  columns: database.columns,
115
127
  joins: database.joins,
116
- saved: Dir.glob("#{database.saved_path}/*.sql").to_h do |path|
117
- contents = File.read(path)
118
- comment_lines = contents.split("\n").take_while do |l|
128
+ saved: tree.to_h do |file|
129
+ comment_lines = file.content.split("\n").take_while do |l|
119
130
  l.start_with?('--')
120
131
  end
121
- filename = File.basename(path)
122
132
  description = comment_lines.map { |l| l.sub(/^-- */, '') }.join("\n")
123
133
  [
124
- filename,
134
+ file.display_path,
125
135
  {
126
- filename: filename,
136
+ filename: file.display_path,
137
+ github_url: file.github_url,
127
138
  description: description,
128
- contents: contents.strip
139
+ contents: file.content
129
140
  }
130
141
  ]
131
142
  end
@@ -47,7 +47,7 @@
47
47
  }
48
48
 
49
49
  .link {
50
- margin-right: 10px;
50
+ margin-right: 30px;
51
51
  color: darkblue;
52
52
  font-size: 17px;
53
53
  text-decoration: none;
@@ -395,7 +395,7 @@ body {
395
395
  }
396
396
 
397
397
  .link {
398
- margin-right: 10px;
398
+ margin-right: 30px;
399
399
  color: darkblue;
400
400
  font-size: 17px;
401
401
  text-decoration: none;
@@ -24997,6 +24997,12 @@
24997
24997
  route(event.target, event, runUrl, true);
24998
24998
  });
24999
24999
 
25000
+ const viewOnGithubLinkElement = document.createElement('a');
25001
+ viewOnGithubLinkElement.classList.add('link', 'view-github-link');
25002
+ viewOnGithubLinkElement.innerText = 'github';
25003
+ viewOnGithubLinkElement.href = file.github_url;
25004
+ viewOnGithubLinkElement.target = '_blank';
25005
+
25000
25006
  const nameElement = document.createElement('h2');
25001
25007
  nameElement.innerText = file.filename;
25002
25008
  nameElement.classList.add('name');
@@ -25005,6 +25011,7 @@
25005
25011
  linksElement.classList.add('links');
25006
25012
  linksElement.appendChild(viewLinkElement);
25007
25013
  linksElement.appendChild(runLinkElement);
25014
+ linksElement.appendChild(viewOnGithubLinkElement);
25008
25015
 
25009
25016
  const descriptionElement = document.createElement('p');
25010
25017
  descriptionElement.innerText = file.description;
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqlui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.69
4
+ version: 0.1.70
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Dower
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-01 00:00:00.000000000 Z
11
+ date: 2023-04-03 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: airbrake
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -24,6 +38,20 @@ dependencies:
24
38
  - - "~>"
25
39
  - !ruby/object:Gem::Version
26
40
  version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: concurrent-ruby
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: mysql2
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +80,20 @@ dependencies:
52
80
  - - "~>"
53
81
  - !ruby/object:Gem::Version
54
82
  version: '4.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rest-client
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
55
97
  - !ruby/object:Gem::Dependency
56
98
  name: sinatra
57
99
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +122,20 @@ dependencies:
80
122
  - - "~>"
81
123
  - !ruby/object:Gem::Version
82
124
  version: '1.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.0'
83
139
  - !ruby/object:Gem::Dependency
84
140
  name: rspec-core
85
141
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +206,20 @@ dependencies:
150
206
  - - "~>"
151
207
  - !ruby/object:Gem::Version
152
208
  version: '4.0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: webmock
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: '3.0'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: '3.0'
153
223
  description: A SQL UI.
154
224
  email: nicholasdower@gmail.com
155
225
  executables:
@@ -159,10 +229,20 @@ extra_rdoc_files: []
159
229
  files:
160
230
  - ".release-version"
161
231
  - app/args.rb
232
+ - app/checks.rb
233
+ - app/count_down_latch.rb
162
234
  - app/database_config.rb
163
235
  - app/database_metadata.rb
164
236
  - app/deep.rb
237
+ - app/github/cache.rb
238
+ - app/github/caching_client.rb
239
+ - app/github/client.rb
240
+ - app/github/file.rb
241
+ - app/github/paths.rb
242
+ - app/github/tree.rb
243
+ - app/github/tree_client.rb
165
244
  - app/mysql_types.rb
245
+ - app/saved_config.rb
166
246
  - app/server.rb
167
247
  - app/sql_parser.rb
168
248
  - app/sqlui.rb