sqlui 0.1.69 → 0.1.71

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: ee20f44f7eec60439ca07c207698d2cc4a0ed19622cd10073185f1bdab4669e6
4
- data.tar.gz: 27a30d2475849a4ec78786fb6ed449245fa5e36fbb9f0baaefcdfb54cb98318d
3
+ metadata.gz: 8367a239699e11c5f2e0156e954babfc90ca40a984fa8738a3649c5bbb90ab99
4
+ data.tar.gz: 7353908da8d6d1bcbd694a3b5fed268d631219f6e48fe2dfab24459cd9fdd4c7
5
5
  SHA512:
6
- metadata.gz: f5f53930cfe0ff9539908c47e78b9437ac39d971d4fcaf03e4090a64b7f5a0e25a31d5e7abdb4bbdc216c7703ecd1bbf757c5a4cc15ff3e2f4977879c33d1826
7
- data.tar.gz: d2f8b1df886420a358d1763f7c0c44cedf0eb9cfccab1b54915f505b29e206d4792dfe743a6c8b1e4479a940990ac7112235620dfa68fe0ee5fc83cd50342c76
6
+ metadata.gz: 70fe8390ec20a1907b384ba98f607df311e5ed21ae28421399d8c478f0e4ffc8bf4f9cecb621ad03aaca15371cee25b8e2d2ce7cc495d86a8a8c8cc9870519e4
7
+ data.tar.gz: cef2ed4e6d4ff35abf6b457a97cd6eeae50906966ea4ca0c606fe7aa5720c891912830bff464845d3e0eaf71d39b3bd16baadbd2aa71b70695ed31f3118f2e94
data/.release-version CHANGED
@@ -1 +1 @@
1
- 0.1.69
1
+ 0.1.71
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.deep_dup
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.71
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