db-gui 0.1.0 → 0.2.1

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: b070cff693f1d1416c8b30af8e004ef0c11832b498825f7cabbda62e15f553f6
4
- data.tar.gz: 219ad78e67ea7a7fc9fcb10e26daadfe4f58ffd797a6eee6983492128b656c6c
3
+ metadata.gz: 38eb8b2669f281d3dddb78945f259d5fe8dea12eb31b51f5761ce4ced71381e9
4
+ data.tar.gz: a0cb8959b31975f176302f20404011e3a8115d5e597461de0f90fe57a5305cf3
5
5
  SHA512:
6
- metadata.gz: 04ac2266301b0bfb854d698b82ccb5f29e0aff6d23d882140975a4234840b85c3fdd996c1c80c18d89c4473ee4055ebcf169bc51ae560b211cf083c9c6cd4c9a
7
- data.tar.gz: 138346868157342145069c35d60b1773e21a86b8b503b615705387e2bc51fb245cfc2d51ba19c99c10ae3bf081c042131b1ed908af1e6fedc12460dc01638a8b
6
+ metadata.gz: b664e4ed31d076e644451b58bd809dc9e9f58b8526d96d052d41eef238c4f293413f886eb0b4fe7f0d9fe19b66eba563d7d98f936fad43d0032b0e4f590ed9ec
7
+ data.tar.gz: 43696e4a6cb8ecd26363a5dc9f33588b1b88895aa5c16a63a88caa6fce393c9d2907f05e52b9ce66ecc3e35d6de8549e85e29f34af1191b81a1fda5b03e8cf5e
data/CHANGELOG.md CHANGED
@@ -1,8 +1,23 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.2.1
4
+
5
+ - Fix bug in 0.2.0 with crashing whenever running a DB query for the second time.
6
+
7
+ ## 0.2.0
8
+
9
+ - Edit -> Copy Table menu item to copy table data as a formatted string to the clipboard
10
+ - Edit -> Copy Selected Row menu item to copy selected row data as a formatted string to the clipboard
11
+
12
+ ## 0.1.1
13
+
14
+ - Automatically extend DB command timeout after timing out by retrying 6 times (7 times total) with exponential timeout increases
15
+ - Avoid DB command timeout if DB result row count is received
16
+ - Show error when DB command fails
17
+
3
18
  ## 0.1.0
4
19
 
5
- - Save last DB command
20
+ - Remember last DB command
6
21
  - Move saved configuration from ~/.db_gui as a file to ~/.db_gui as a directory with multiple files underneath: ~/.db_gui/.db_configs & ~/.db_gui/.db_commands
7
22
 
8
23
  ## 0.0.4
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # DB GUI (Database Graphical User Interface) 0.1.0
1
+ # DB GUI (Database Graphical User Interface) 0.2.1
2
2
  ## [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=40 /> Glimmer DSL for LibUI Application](https://github.com/AndyObtiva/glimmer-dsl-libui)
3
3
  [![Gem Version](https://badge.fury.io/rb/db-gui.svg)](http://badge.fury.io/rb/db-gui)
4
4
 
@@ -12,7 +12,7 @@ It currently supports PostgreSQL as a start, with the potential of supporting ma
12
12
 
13
13
  Run:
14
14
  ```
15
- gem install db-gui -v0.1.0
15
+ gem install db-gui -v0.2.1
16
16
  ```
17
17
 
18
18
  ## Usage
@@ -26,6 +26,16 @@ Or, run one of the aliases: `db-ui` / `dbgui` / `db-gui`
26
26
 
27
27
  Note that it stores the last connection details under `~/.db_gui`, and will auto-connect using that configuration on startup for extra convenience (in the future, there is the potential to support multiple connection configurations).
28
28
 
29
+ ### Menu Items
30
+
31
+ **Edit -> Copy Table**
32
+
33
+ Click on this menu item to copy the table data as a formatted string to the clipboard.
34
+
35
+ **Edit -> Copy Selected Row**
36
+
37
+ Click on this menu item to copy the selected row data as a formatted string to the clipboard.
38
+
29
39
  ## Change Log
30
40
 
31
41
  [CHANGELOG.md](/CHANGELOG.md)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.1
@@ -1,27 +1,33 @@
1
1
  require 'fileutils'
2
2
  require 'timeout'
3
+ require 'clipboard'
3
4
 
4
5
  class DbGui
5
6
  module Model
6
- Db = Struct.new(:host, :port, :dbname, :username, :password, keyword_init: true) do
7
+ Db = Struct.new(:host, :port, :dbname, :username, :password, :db_command_timeout, keyword_init: true) do
7
8
  DIR_DB_GUI = File.expand_path(File.join('~', '.db_gui'))
8
9
  FileUtils.rm(DIR_DB_GUI) if File.file?(DIR_DB_GUI)
9
10
  FileUtils.mkdir_p(DIR_DB_GUI)
10
11
  FILE_DB_CONFIGS = File.expand_path(File.join(DIR_DB_GUI, '.db_configs'))
11
12
  FILE_DB_COMMANDS = File.expand_path(File.join(DIR_DB_GUI, '.db_commands'))
13
+ COUNT_RETRIES = ENV.fetch('DB_COMMAND_COUNT_RETRIES', 7)
14
+ REGEX_ROW_COUNT = /^\((\d+) row/
15
+ ERROR_PREFIX = 'ERROR:'
16
+ NEW_LINE = OS.windows? ? "\r\n" : "\n"
12
17
 
13
18
  attr_accessor :connected
14
19
  alias connected? connected
15
20
  attr_accessor :db_command_result
16
21
  attr_accessor :db_command
17
- attr_accessor :db_command_timeout
22
+ attr_accessor :db_command_result_selection
18
23
 
19
24
  def initialize
20
- self.port = 5432 # PostgreSQL default port
21
- self.db_command_result = ''
22
- self.db_command_timeout = (ENV['DB_COMMAND_TIMEOUT_IN_MILLISECONDS'] || 300).to_i
23
25
  load_db_config
24
26
  load_db_command
27
+ self.port ||= 5432 # PostgreSQL default port
28
+ self.db_command_result = ''
29
+ self.db_command_timeout ||= ENV.fetch('DB_COMMAND_TIMEOUT_IN_MILLISECONDS', 300).to_i
30
+ self.db_command_result_selection = 0
25
31
  connect if to_h.except(:password).none? {|value| value.nil? || (value.respond_to?(:empty?) && value.empty?) }
26
32
  end
27
33
 
@@ -46,7 +52,8 @@ class DbGui
46
52
  end
47
53
 
48
54
  def io
49
- @io ||= IO.popen("PGPASSWORD=\"#{password}\" psql --host=#{host} --port=#{port} --username=#{username} --dbname=#{dbname}", 'r+')
55
+ db_connection_command = "PGPASSWORD=\"#{password}\" psql --host=#{host} --port=#{port} --username=#{username} --dbname=#{dbname}"
56
+ @io ||= IO.popen(db_connection_command, 'r+', err: [:child, :out])
50
57
  end
51
58
 
52
59
  def run_io_command(command)
@@ -56,13 +63,22 @@ class DbGui
56
63
  @io_command_try += 1
57
64
  io.puts(command)
58
65
  read_io_into_db_command_result
59
- rescue Errno::EPIPE => e
66
+ @io_command_try = nil
67
+ rescue Timeout::Error, Errno::EPIPE => e
68
+ puts e.message
60
69
  @io = nil
61
- run_io_command(command) unless @io_command_try > 1
70
+ if @io_command_try > COUNT_RETRIES
71
+ @io_command_try = nil
72
+ else
73
+ self.db_command_timeout *= 2
74
+ puts "Retrying DB Command... {try: #{@io_command_try + 1}, db_command_timeout: #{db_command_timeout}}"
75
+ run_io_command(command) unless db_command_result_error?
76
+ end
62
77
  end
63
78
 
64
79
  def run_db_command
65
80
  run_io_command(db_command)
81
+ save_db_config
66
82
  save_db_command
67
83
  end
68
84
 
@@ -78,16 +94,50 @@ class DbGui
78
94
  db_command_result_count_headers_rows[2]
79
95
  end
80
96
 
97
+ def db_command_result_error?
98
+ db_command_result.to_s.strip.start_with?(ERROR_PREFIX)
99
+ end
100
+
101
+ def copy_table
102
+ return if db_command_result_rows.empty?
103
+ Clipboard.copy(formatted_table_string)
104
+ end
105
+
106
+ def copy_selected_row
107
+ return if db_command_result_rows.empty?
108
+ Clipboard.copy(formatted_selected_row_string)
109
+ end
110
+
111
+ def formatted_table_string
112
+ column_max_lengths = db_command_result_column_max_lengths
113
+ db_command_result_rows.map do |row|
114
+ row.each_with_index.map do |data, column_index|
115
+ data.ljust(column_max_lengths[column_index])
116
+ end.join(" | ")
117
+ end.join(NEW_LINE)
118
+ end
119
+
120
+ def formatted_selected_row_string
121
+ selected_row = db_command_result_rows[db_command_result_selection]
122
+ selected_row.join(' | ')
123
+ end
124
+
81
125
  private
82
126
 
83
127
  def read_io_into_db_command_result
84
- self.db_command_result = ''
85
- while (line = io_gets)
86
- result = line.to_s
87
- self.db_command_result += result
128
+ self.db_command_result = read_db_command_result = ''
129
+ while (!(@line.to_s.match(REGEX_ROW_COUNT) || @line.to_s.strip == "^") && (@line = io_gets))
130
+ read_db_command_result += @line.to_s
88
131
  end
89
- rescue Errno::EPIPE => e
90
- @io = nil
132
+ self.db_command_result = read_db_command_result
133
+ rescue
134
+ if read_db_command_result.to_s.strip.start_with?(ERROR_PREFIX)
135
+ self.db_command_result = read_db_command_result
136
+ else
137
+ raise
138
+ end
139
+ ensure
140
+ @line = nil
91
141
  end
92
142
 
93
143
  def save_db_config
@@ -123,11 +173,10 @@ class DbGui
123
173
  end
124
174
 
125
175
  def io_gets
126
- # TODO figure out a way of knowing the end of input without timing out
127
176
  Timeout.timeout(db_command_timeout/1000.0) { io.gets }
128
177
  rescue
129
178
  @io = nil
130
- nil
179
+ raise
131
180
  end
132
181
 
133
182
  def db_command_result_count_headers_rows
@@ -141,12 +190,11 @@ class DbGui
141
190
  def compute_db_command_result_count_headers_rows
142
191
  count = 0
143
192
  headers = rows = []
144
- db_command_result_lines = db_command_result.lines
145
- db_command_result_lines.pop if db_command_result_lines.last == "\n"
193
+ db_command_result_lines = db_command_result.lines.reject { |line| line == "\n" }
146
194
  if db_command_result_lines.any?
147
195
  headers = db_command_result_lines.first.split('|').map(&:strip)
148
196
  count_footer = db_command_result_lines.last
149
- count_match = count_footer.match(/^\((\d+) row/)
197
+ count_match = count_footer.match(REGEX_ROW_COUNT)
150
198
  if count_match
151
199
  count = count_match[1].to_i
152
200
  rows = db_command_result_lines[2..-2].map {|row| row.split('|').map(&:strip) }
@@ -154,6 +202,18 @@ class DbGui
154
202
  end
155
203
  [count, headers, rows]
156
204
  end
205
+
206
+ def db_command_result_column_max_lengths
207
+ column_count = db_command_result_rows.first.size
208
+ column_max_lengths = []
209
+ db_command_result_rows.each do |row|
210
+ row.each_with_index do |value, column_index|
211
+ column_max_lengths[column_index] ||= 0
212
+ column_max_lengths[column_index] = [column_max_lengths[column_index], value.size].max
213
+ end
214
+ end
215
+ column_max_lengths
216
+ end
157
217
  end
158
218
  end
159
219
  end
@@ -29,7 +29,7 @@ class DbGui
29
29
  }
30
30
  spinbox(0, TIMEOUT_MAX_IN_MILLISECONDS) {
31
31
  stretchy false
32
- value <=> [db, :db_command_timeout]
32
+ value <=> [db, :db_command_timeout, on_read: :to_i]
33
33
  }
34
34
 
35
35
  label('Row(s): ') {
@@ -10,13 +10,21 @@ class DbGui
10
10
  body {
11
11
  vertical_box {
12
12
  content(db, :db_command_result) {
13
- if db.db_command_result_count > 0
13
+ if db.db_command_result_error?
14
+ label(db.db_command_result)
15
+ elsif db.db_command_result_count > 0
14
16
  table {
15
17
  db.db_command_result_headers.each do |header|
16
18
  text_column(header)
17
19
  end
18
20
 
19
21
  cell_rows db.db_command_result_rows
22
+ selection_mode :one
23
+ selection db.db_command_result_selection
24
+
25
+ on_selection_changed do |t, selection, added_selection, removed_selection|
26
+ db.db_command_result_selection = selection
27
+ end
20
28
  }
21
29
  else
22
30
  label('No data')
@@ -13,7 +13,7 @@ class DbGui
13
13
 
14
14
  before_body do
15
15
  @db = Model::Db.new
16
- # menu_bar # TODO implement
16
+ menu_bar
17
17
  end
18
18
 
19
19
  body {
@@ -35,38 +35,48 @@ class DbGui
35
35
  }
36
36
  }
37
37
 
38
- # def menu_bar
39
- # menu('File') {
40
- # menu_item('Preferences...') {
41
- # on_clicked do
42
- # display_preferences_dialog
43
- # end
44
- # }
45
- #
46
- # quit_menu_item if OS.mac?
47
- # }
48
- # menu('Help') {
49
- # if OS.mac?
50
- # about_menu_item {
51
- # on_clicked do
52
- # display_about_dialog
53
- # end
54
- # }
55
- # end
56
- #
57
- # menu_item('About') {
58
- # on_clicked do
59
- # display_about_dialog
60
- # end
61
- # }
62
- # }
63
- # end
64
- #
65
- # def display_about_dialog
66
- # message = "Db Gui #{VERSION}\n\n#{LICENSE}"
67
- # msg_box('About', message)
68
- # end
69
- #
38
+ def menu_bar
39
+ menu('Edit') {
40
+ menu_item('Copy Table') {
41
+ enabled <= [db, :db_command_result_rows, computed_by: :db_command_result, on_read: -> (data) { !data.empty? }]
42
+
43
+ on_clicked do
44
+ db.copy_table
45
+ end
46
+ }
47
+
48
+ menu_item('Copy Selected Row') {
49
+ enabled <= [db, :db_command_result_rows, computed_by: :db_command_result, on_read: -> (data) { !data.empty? }]
50
+
51
+ on_clicked do
52
+ db.copy_selected_row
53
+ end
54
+ }
55
+
56
+ quit_menu_item if OS.mac?
57
+ }
58
+ menu('Help') {
59
+ if OS.mac?
60
+ about_menu_item {
61
+ on_clicked do
62
+ display_about_dialog
63
+ end
64
+ }
65
+ end
66
+
67
+ menu_item('About') {
68
+ on_clicked do
69
+ display_about_dialog
70
+ end
71
+ }
72
+ }
73
+ end
74
+
75
+ def display_about_dialog
76
+ message = "DB GUI #{VERSION}\n\n#{LICENSE}"
77
+ msg_box('About', message)
78
+ end
79
+
70
80
  # def display_preferences_dialog
71
81
  # window {
72
82
  # title 'Preferences'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: db-gui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-03-23 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: glimmer-dsl-libui
@@ -24,6 +23,20 @@ dependencies:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
25
  version: 0.12.7
26
+ - !ruby/object:Gem::Dependency
27
+ name: clipboard
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 2.0.0
27
40
  - !ruby/object:Gem::Dependency
28
41
  name: rspec
29
42
  requirement: !ruby/object:Gem::Requirement
@@ -67,12 +80,12 @@ dependencies:
67
80
  - !ruby/object:Gem::Version
68
81
  version: '0'
69
82
  description: DB GUI (Database Graphical User Interface) - Enables Interaction with
70
- Relational SQL Databases
83
+ Relational SQL Databases. This alpha version only supports PostgreSQL at the moment.
71
84
  email: andy.am@gmail.com
72
85
  executables:
73
86
  - db-gui
74
- - dbgui
75
87
  - db-ui
88
+ - dbgui
76
89
  - dbui
77
90
  extensions: []
78
91
  extra_rdoc_files:
@@ -102,7 +115,6 @@ homepage: http://github.com/AndyObtiva/db-gui
102
115
  licenses:
103
116
  - MIT
104
117
  metadata: {}
105
- post_install_message:
106
118
  rdoc_options: []
107
119
  require_paths:
108
120
  - lib
@@ -118,8 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
130
  - !ruby/object:Gem::Version
119
131
  version: '0'
120
132
  requirements: []
121
- rubygems_version: 3.4.19
122
- signing_key:
133
+ rubygems_version: 3.6.7
123
134
  specification_version: 4
124
135
  summary: DB GUI
125
136
  test_files: []