gemstar 0.0.2 → 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.
@@ -0,0 +1,226 @@
1
+ (() => {
2
+ const projectSelect = document.querySelector("[data-project-select]");
3
+ const fromSelect = document.querySelector("[data-from-select]");
4
+ const toSelect = document.querySelector("[data-to-select]");
5
+ const sidebarPanel = document.querySelector("[data-sidebar-panel]");
6
+ const filterButtons = Array.from(document.querySelectorAll("[data-filter-button]"));
7
+ const gemSearch = document.querySelector("[data-gem-search]");
8
+ const emptyGemList = document.querySelector("[data-gem-list-empty]");
9
+ let detailPanel = document.querySelector("[data-detail-panel]");
10
+ const gemLinks = Array.from(document.querySelectorAll("[data-gem-link]"));
11
+ let detailPollTimer = null;
12
+ let currentFilter = <%= selected_filter_json %>;
13
+ let currentSearch = "";
14
+ const emptyDetailHtml = <%= empty_detail_html_json %>;
15
+
16
+ const visibleGemLinks = () => gemLinks.filter((link) => !link.hidden);
17
+ const currentSelectedIndex = () => visibleGemLinks().findIndex((link) => link.classList.contains("is-selected"));
18
+ const clearSidebarSelection = () => {
19
+ gemLinks.forEach((link) => {
20
+ link.classList.remove("is-selected");
21
+ });
22
+ };
23
+ const requestedGemName = () => new URL(window.location.href).searchParams.get("gem");
24
+
25
+ const applyGemFilter = (filter) => {
26
+ currentFilter = filter;
27
+ const pinnedGemName = requestedGemName();
28
+ const searchTerm = currentSearch.trim().toLowerCase();
29
+
30
+ filterButtons.forEach((button) => {
31
+ button.classList.toggle("is-active", button.dataset.filterButton === filter);
32
+ });
33
+
34
+ gemLinks.forEach((link) => {
35
+ const updated = link.dataset.gemUpdated === "true";
36
+ const pinned = pinnedGemName && link.dataset.gemName === pinnedGemName;
37
+ const matchesSearch = searchTerm === "" || link.dataset.gemName.toLowerCase().includes(searchTerm);
38
+ link.hidden = ((filter === "updated" && !updated && !pinned) || !matchesSearch);
39
+ });
40
+
41
+ if (emptyGemList) {
42
+ const emptyMessage = searchTerm === "" ? "No updated gems in this revision range." : "No gems match this filter.";
43
+ emptyGemList.hidden = visibleGemLinks().length !== 0;
44
+ const text = emptyGemList.querySelector("p");
45
+ if (text) text.textContent = emptyMessage;
46
+ }
47
+
48
+ if (currentSelectedIndex() === -1) {
49
+ clearSidebarSelection();
50
+ replaceDetail(emptyDetailHtml);
51
+ }
52
+ };
53
+
54
+ const syncSidebarSelection = (gemName = null, keepVisible = false) => {
55
+ const effectiveGemName = gemName || new URL(window.location.href).searchParams.get("gem");
56
+ if (!effectiveGemName) {
57
+ clearSidebarSelection();
58
+ return;
59
+ }
60
+
61
+ let selectedLink = null;
62
+ gemLinks.forEach((link) => {
63
+ const matches = link.dataset.gemName === effectiveGemName;
64
+ link.classList.toggle("is-selected", matches);
65
+ if (matches) {
66
+ selectedLink = link;
67
+ }
68
+ });
69
+
70
+ if (keepVisible && sidebarPanel && selectedLink) {
71
+ selectedLink.scrollIntoView({ block: "nearest" });
72
+ }
73
+ };
74
+
75
+ if ("scrollRestoration" in history) {
76
+ history.scrollRestoration = "manual";
77
+ }
78
+
79
+ const replaceDetail = (html) => {
80
+ if (!detailPanel) return;
81
+ detailPanel.outerHTML = html;
82
+ detailPanel = document.querySelector("[data-detail-panel]");
83
+ if (detailPanel) detailPanel.scrollTop = 0;
84
+ scheduleDetailPoll();
85
+ };
86
+
87
+ const stopDetailPoll = () => {
88
+ if (detailPollTimer) {
89
+ clearTimeout(detailPollTimer);
90
+ detailPollTimer = null;
91
+ }
92
+ };
93
+
94
+ const fetchDetail = (url, pushHistory = true) => {
95
+ fetch(url, { headers: { "X-Requested-With": "gemstar-detail" } })
96
+ .then((response) => response.text())
97
+ .then((html) => {
98
+ replaceDetail(html);
99
+ if (pushHistory) {
100
+ const pageUrl = new URL(window.location.href);
101
+ const detailUrl = new URL(url, window.location.origin);
102
+ pageUrl.search = detailUrl.search;
103
+ pageUrl.searchParams.set("filter", currentFilter);
104
+ window.history.pushState({}, "", pageUrl);
105
+ }
106
+ const detailUrl = new URL(url, window.location.origin);
107
+ syncSidebarSelection(detailUrl.searchParams.get("gem"));
108
+ });
109
+ };
110
+
111
+ const activateGemLink = (link, pushHistory = true, keepVisible = false) => {
112
+ if (!link) return;
113
+
114
+ syncSidebarSelection(link.dataset.gemName, keepVisible);
115
+ fetchDetail(link.dataset.detailUrl || link.href, pushHistory);
116
+
117
+ if (sidebarPanel) {
118
+ sidebarPanel.focus({ preventScroll: true });
119
+ }
120
+ };
121
+
122
+ const scheduleDetailPoll = () => {
123
+ stopDetailPoll();
124
+ if (!detailPanel || detailPanel.dataset.detailPending !== "true") return;
125
+ detailPollTimer = setTimeout(() => {
126
+ fetchDetail(detailPanel.dataset.detailUrl, false);
127
+ }, 1000);
128
+ };
129
+
130
+ if (detailPanel) {
131
+ detailPanel.scrollTop = 0;
132
+ scheduleDetailPoll();
133
+ }
134
+
135
+ syncSidebarSelection(null, true);
136
+ applyGemFilter(currentFilter);
137
+
138
+ gemLinks.forEach((link) => {
139
+ link.addEventListener("click", (event) => {
140
+ event.preventDefault();
141
+ activateGemLink(link);
142
+ });
143
+ });
144
+
145
+ filterButtons.forEach((button) => {
146
+ button.addEventListener("click", () => {
147
+ applyGemFilter(button.dataset.filterButton);
148
+ if (sidebarPanel) {
149
+ sidebarPanel.scrollTop = 0;
150
+ }
151
+ const url = new URL(window.location.href);
152
+ url.searchParams.set("filter", currentFilter);
153
+ window.history.replaceState({}, "", url);
154
+ syncSidebarSelection(null, true);
155
+ });
156
+ });
157
+
158
+ if (gemSearch) {
159
+ gemSearch.addEventListener("input", (event) => {
160
+ currentSearch = event.target.value || "";
161
+ applyGemFilter(currentFilter);
162
+ if (sidebarPanel) {
163
+ sidebarPanel.scrollTop = 0;
164
+ }
165
+ syncSidebarSelection(null, true);
166
+ });
167
+ }
168
+
169
+ const navigate = (params) => {
170
+ const url = new URL(window.location.href);
171
+ Object.entries(params).forEach(([key, value]) => {
172
+ if (value === null || value === undefined || value === "") {
173
+ url.searchParams.delete(key);
174
+ } else {
175
+ url.searchParams.set(key, value);
176
+ }
177
+ });
178
+ window.location.href = url.toString();
179
+ };
180
+
181
+ if (projectSelect) {
182
+ projectSelect.addEventListener("change", (event) => {
183
+ if (event.target.value === "__add__") {
184
+ window.alert("Add project UI is next. For now, restart gemstar server with another --project path.");
185
+ event.target.value = "<%= selected_project_index %>";
186
+ return;
187
+ }
188
+ navigate({ project: event.target.value, from: null, to: "worktree", filter: currentFilter, gem: null });
189
+ });
190
+ }
191
+
192
+ if (fromSelect) {
193
+ fromSelect.addEventListener("change", (event) => {
194
+ navigate({ from: event.target.value, filter: currentFilter, gem: null });
195
+ });
196
+ }
197
+
198
+ if (toSelect) {
199
+ toSelect.addEventListener("change", (event) => {
200
+ navigate({ to: event.target.value, filter: currentFilter, gem: null });
201
+ });
202
+ }
203
+
204
+ document.addEventListener("keydown", (event) => {
205
+ const tagName = document.activeElement && document.activeElement.tagName;
206
+ if (tagName === "INPUT" || tagName === "TEXTAREA" || tagName === "SELECT") return;
207
+ const links = visibleGemLinks();
208
+ if (!links.length) return;
209
+
210
+ const selectedGem = currentSelectedIndex();
211
+ const currentIndex = selectedGem >= 0 ? selectedGem : 0;
212
+ let nextIndex = null;
213
+
214
+ if (event.key === "ArrowDown") nextIndex = Math.min(currentIndex + 1, links.length - 1);
215
+ if (event.key === "ArrowUp") nextIndex = Math.max(currentIndex - 1, 0);
216
+
217
+ if (nextIndex !== null && nextIndex !== currentIndex) {
218
+ event.preventDefault();
219
+ activateGemLink(links[nextIndex], true, true);
220
+ }
221
+ });
222
+
223
+ window.addEventListener("popstate", () => {
224
+ window.location.reload();
225
+ });
226
+ })();
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title><%= title %></title>
7
+ <link rel="icon" href="<%= favicon_data_uri %>">
8
+ <style>
9
+ <%= styles_css %>
10
+ </style>
11
+ </head>
12
+ <body>
13
+ <%= body_html %>
14
+ </body>
15
+ </html>
@@ -0,0 +1,22 @@
1
+ module Gemstar
2
+ class WEBrickLogger < WEBrick::Log
3
+ EXPECTED_DISCONNECT_ERRORS = [
4
+ Errno::ECONNRESET,
5
+ Errno::ECONNABORTED,
6
+ Errno::EPIPE
7
+ ].freeze
8
+
9
+ def error(message)
10
+ return if expected_disconnect?(message)
11
+
12
+ super
13
+ end
14
+
15
+ private
16
+
17
+ def expected_disconnect?(message)
18
+ EXPECTED_DISCONNECT_ERRORS.any? { |error_class| message.is_a?(error_class) } ||
19
+ message.to_s.start_with?("Errno::ECONNRESET:", "Errno::ECONNABORTED:", "Errno::EPIPE:")
20
+ end
21
+ end
22
+ end
data/lib/gemstar.rb CHANGED
@@ -1,16 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "gemstar/version"
4
- require "gemstar/railtie" if defined?(Rails::Railtie)
4
+ require "gemstar/cache_cli"
5
5
  require "gemstar/cli"
6
+ require "gemstar/cache_warmer"
6
7
  require "gemstar/commands/command"
8
+ require "gemstar/commands/cache"
7
9
  require "gemstar/commands/diff"
10
+ require "gemstar/commands/server"
11
+ require "gemstar/config"
8
12
  require "gemstar/outputs/basic"
9
13
  require "gemstar/outputs/html"
10
14
  require "gemstar/cache"
11
15
  require "gemstar/change_log"
12
16
  require "gemstar/git_hub"
13
17
  require "gemstar/lock_file"
18
+ require "gemstar/project"
14
19
  require "gemstar/remote_repository"
15
20
  require "gemstar/utils"
16
21
  require "gemstar/ruby_gems_metadata"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gemstar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: '1.0'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Dejako
@@ -65,6 +65,20 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '5.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rerun
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.14'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.14'
68
82
  - !ruby/object:Gem::Dependency
69
83
  name: kramdown
70
84
  requirement: !ruby/object:Gem::Requirement
@@ -149,6 +163,48 @@ dependencies:
149
163
  - - "~>"
150
164
  - !ruby/object:Gem::Version
151
165
  version: '1.18'
166
+ - !ruby/object:Gem::Dependency
167
+ name: roda
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '3.90'
173
+ type: :runtime
174
+ prerelease: false
175
+ version_requirements: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: '3.90'
180
+ - !ruby/object:Gem::Dependency
181
+ name: rackup
182
+ requirement: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - "~>"
185
+ - !ruby/object:Gem::Version
186
+ version: '2.2'
187
+ type: :runtime
188
+ prerelease: false
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - "~>"
192
+ - !ruby/object:Gem::Version
193
+ version: '2.2'
194
+ - !ruby/object:Gem::Dependency
195
+ name: webrick
196
+ requirement: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - "~>"
199
+ - !ruby/object:Gem::Version
200
+ version: '1.9'
201
+ type: :runtime
202
+ prerelease: false
203
+ version_requirements: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - "~>"
206
+ - !ruby/object:Gem::Version
207
+ version: '1.9'
152
208
  description: Gem changelog viewer and more, but starting as a changelog viewer for
153
209
  bundled gems.
154
210
  email:
@@ -164,20 +220,31 @@ files:
164
220
  - bin/gemstar
165
221
  - lib/gemstar.rb
166
222
  - lib/gemstar/cache.rb
223
+ - lib/gemstar/cache_cli.rb
224
+ - lib/gemstar/cache_warmer.rb
167
225
  - lib/gemstar/change_log.rb
168
226
  - lib/gemstar/cli.rb
227
+ - lib/gemstar/commands/cache.rb
169
228
  - lib/gemstar/commands/command.rb
170
229
  - lib/gemstar/commands/diff.rb
230
+ - lib/gemstar/commands/server.rb
231
+ - lib/gemstar/config.rb
171
232
  - lib/gemstar/git_hub.rb
172
233
  - lib/gemstar/git_repo.rb
173
234
  - lib/gemstar/lock_file.rb
174
235
  - lib/gemstar/outputs/basic.rb
175
236
  - lib/gemstar/outputs/html.rb
176
- - lib/gemstar/railtie.rb
237
+ - lib/gemstar/project.rb
177
238
  - lib/gemstar/remote_repository.rb
239
+ - lib/gemstar/request_logger.rb
178
240
  - lib/gemstar/ruby_gems_metadata.rb
179
241
  - lib/gemstar/utils.rb
180
242
  - lib/gemstar/version.rb
243
+ - lib/gemstar/web/app.rb
244
+ - lib/gemstar/web/templates/app.css
245
+ - lib/gemstar/web/templates/app.js.erb
246
+ - lib/gemstar/web/templates/page.html.erb
247
+ - lib/gemstar/webrick_logger.rb
181
248
  homepage: https://github.com/FDj/gemstar
182
249
  licenses:
183
250
  - MIT
@@ -199,7 +266,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
199
266
  - !ruby/object:Gem::Version
200
267
  version: '0'
201
268
  requirements: []
202
- rubygems_version: 3.7.0
269
+ rubygems_version: 3.6.9
203
270
  specification_version: 4
204
271
  summary: Making sense of gems.
205
272
  test_files: []
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gemstar # :nodoc:
4
- class Railtie < ::Rails::Railtie # :nodoc:
5
- end
6
- end