remise 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 304a7c206b64efe4a30a855771a5ce87d8de795c225a7f4d12b338055dd6c4a6
4
- data.tar.gz: 563d943553e0e12d293ddc9e09d38e7d25588d9ff376e4b2e635c108d5f17553
3
+ metadata.gz: cfdcb94bdbe258d69714fca1ce8770bb28e24f4fcbb2c4395b864a41166b1071
4
+ data.tar.gz: 48df1c1cc65f575539f297698824f31b4893847b5d6f122f30f230f7a8852863
5
5
  SHA512:
6
- metadata.gz: 9cb4e907c521a19faa9942ca25d6bcfa9a6e29b76fa5c0dd11efbc32c160f43cc5b9a9bb911b4a8592cc499b348d0743c68b4d7250a560344d4fe2e04c4b134c
7
- data.tar.gz: 53d83814716566f6c40a955994526fde3445c95e46a814d05c90652e041445f5f386335acd3c4050556bef94e12eb42a6c392a34b0c36741d16bb3b9ff3a411e
6
+ metadata.gz: f7adb266c2f825864de5ed254abda266fe0e7ea9f1b91c14b2c4252dd2f895a5cb22cb47f0d3b82cf0af701b87fabf0cac6dae0c74de977adee16336c6cabe0a
7
+ data.tar.gz: 336df48f731a136a255b20f79933748cf90db5b52bf5ccbbf2c54e01a9cfab46a407ee7ac23b00ad26f6d72b9d1923521ad858ad8b3844284b8d7bb248fbbc94
@@ -1,4 +1,16 @@
1
1
  module Remise
2
2
  class ApplicationController < ActionController::Base
3
+ def index
4
+ @config = read_config
5
+ end
6
+
7
+ protected
8
+
9
+ def read_config
10
+ config_path = Rails.root.join("config", "remise.yml")
11
+ return {} unless File.exist?(config_path)
12
+
13
+ Nero.load_file(config_path, symbolize_names: true)
14
+ end
3
15
  end
4
16
  end
@@ -1,4 +1,32 @@
1
1
  module Remise
2
2
  module ApplicationHelper
3
+ def leading_emoji(label)
4
+ label[/^\p{Emoji}/u]
5
+ end
6
+
7
+ def strip_leading_emoji(label)
8
+ label.sub(/^\p{Emoji}/u, "")
9
+ end
10
+
11
+ # Given a config like
12
+ # `url: { value: "https://...", formatted: "short.form/path" }`
13
+ # This method gets the formatted-value:
14
+ # prefer_formatted(config[:url]) #=> "short.form/path"
15
+ # Also works for `url: "one value"`.
16
+ def prefer_formatted(h)
17
+ return h[:formatted] if h.is_a?(Hash) && h.has_key?(:formatted)
18
+ return h[:value] if h.is_a?(Hash)
19
+ h
20
+ end
21
+
22
+ # Given a config like
23
+ # `url: { value: "https://...", formatted: "short.form/path" }`
24
+ # This method gets the value:
25
+ # prefer_value(config[:url]) #=> "https://..."
26
+ # Also works for `url: "one value"`.
27
+ def prefer_value(h)
28
+ return h[:value] if h.is_a?(Hash) && h.has_key?(:value)
29
+ h
30
+ end
3
31
  end
4
32
  end
@@ -1,17 +1,260 @@
1
1
  <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>Remise</title>
5
- <%= csrf_meta_tags %>
6
- <%= csp_meta_tag %>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>🚂 Remise - Development Dashboard</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
7
13
 
8
- <%= yield :head %>
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ <%# TODO customize %>
18
+ <%# background: #667EEA; %>
19
+ <%# background: linear-gradient(90deg, rgba(102, 126, 234, 1) 0%, rgba(87, 199, 133, 1) 50%, rgba(237, 221, 83, 1) 100%); %>
20
+ min-height: 100vh;
21
+ padding: 40px 20px;
22
+ }
9
23
 
10
- <%= stylesheet_link_tag "remise/application", media: "all" %>
11
- </head>
12
- <body>
24
+ .container {
25
+ max-width: 1200px;
26
+ margin: 0 auto;
27
+ }
13
28
 
14
- <%= yield %>
29
+ header {
30
+ text-align: center;
31
+ margin-bottom: 40px;
32
+ }
15
33
 
16
- </body>
34
+ h1 {
35
+ color: white;
36
+ font-size: 3em;
37
+ margin-bottom: 10px;
38
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
39
+ }
40
+
41
+ .subtitle {
42
+ color: rgba(255,255,255,0.9);
43
+ font-size: 1.2em;
44
+ }
45
+
46
+ .sections-grid {
47
+ display: grid;
48
+ grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
49
+ gap: 24px;
50
+ }
51
+
52
+ .section {
53
+ background: white;
54
+ border-radius: 12px;
55
+ padding: 24px;
56
+ box-shadow: 0 8px 16px rgba(0,0,0,0.1);
57
+ transition: transform 0.2s, box-shadow 0.2s;
58
+ }
59
+
60
+ .section:hover {
61
+ transform: translateY(-4px);
62
+ box-shadow: 0 12px 24px rgba(0,0,0,0.15);
63
+ }
64
+
65
+ .section-header {
66
+ display: flex;
67
+ align-items: center;
68
+ gap: 12px;
69
+ margin-bottom: 20px;
70
+ padding-bottom: 12px;
71
+ border-bottom: 2px solid #f0f0f0;
72
+ }
73
+
74
+ .section-emoji {
75
+ font-size: 2em;
76
+ }
77
+
78
+ .section-title {
79
+ font-size: 1.4em;
80
+ font-weight: 600;
81
+ color: #333;
82
+ }
83
+
84
+ .links-list {
85
+ display: flex;
86
+ flex-direction: column;
87
+ gap: 12px;
88
+ }
89
+
90
+ .link-item {
91
+ display: flex;
92
+ align-items: flex-start;
93
+ gap: 12px;
94
+ padding: 12px;
95
+ background: #f8f9fa;
96
+ border-radius: 8px;
97
+ text-decoration: none;
98
+ color: #333;
99
+ transition: all 0.2s;
100
+ border-left: 3px solid transparent;
101
+ }
102
+
103
+ .link-item:hover {
104
+ background: #e9ecef;
105
+ border-left-color: #667eea;
106
+ transform: translateX(4px);
107
+ }
108
+
109
+ .link-emoji {
110
+ font-size: 1.5em;
111
+ flex-shrink: 0;
112
+ }
113
+
114
+ .link-content {
115
+ flex: 1;
116
+ overflow: hidden;
117
+ }
118
+
119
+ .link-title {
120
+ font-weight: 600;
121
+ color: #333;
122
+ margin-bottom: 4px;
123
+ }
124
+
125
+ .link-url {
126
+ font-size: 0.9em;
127
+ color: #666;
128
+ font-family: 'Monaco', 'Courier New', monospace;
129
+ }
130
+
131
+ .link-meta {
132
+ font-size: 0.85em;
133
+ color: #28a745;
134
+ margin-top: 4px;
135
+ font-style: italic;
136
+ }
137
+
138
+ /* Expandable sub-links using details/summary */
139
+ .link-item-expandable {
140
+ display: block;
141
+ padding: 12px;
142
+ background: #f8f9fa;
143
+ border-radius: 8px;
144
+ border-left: 3px solid transparent;
145
+ transition: all 0.2s;
146
+ }
147
+
148
+ .link-item-expandable:hover {
149
+ background: #e9ecef;
150
+ border-left-color: #667eea;
151
+ }
152
+
153
+ .link-item-expandable summary {
154
+ display: flex;
155
+ align-items: flex-start;
156
+ gap: 12px;
157
+ cursor: pointer;
158
+ list-style: none;
159
+ user-select: none;
160
+ }
161
+
162
+ .link-item-expandable summary::-webkit-details-marker {
163
+ display: none;
164
+ }
165
+
166
+ .link-item-expandable summary::before {
167
+ content: '▶';
168
+ font-size: 0.8em;
169
+ color: #666;
170
+ transition: transform 0.2s;
171
+ margin-top: 4px;
172
+ }
173
+
174
+ .link-item-expandable[open] summary::before {
175
+ transform: rotate(90deg);
176
+ }
177
+
178
+ .sub-links {
179
+ margin-top: 12px;
180
+ padding-top: 12px;
181
+ border-top: 1px solid #dee2e6;
182
+ display: flex;
183
+ flex-direction: column;
184
+ gap: 8px;
185
+ }
186
+
187
+ .sub-link {
188
+ display: flex;
189
+ align-items: center;
190
+ gap: 8px;
191
+ padding: 8px 12px;
192
+ margin-left: 24px;
193
+ background: white;
194
+ border-radius: 6px;
195
+ text-decoration: none;
196
+ color: #555;
197
+ font-size: 0.9em;
198
+ transition: all 0.2s;
199
+ border-left: 2px solid transparent;
200
+ }
201
+
202
+ .sub-link:hover {
203
+ background: #f8f9fa;
204
+ border-left-color: #667eea;
205
+ transform: translateX(4px);
206
+ color: #333;
207
+ }
208
+
209
+ .sub-link-icon {
210
+ font-size: 1.2em;
211
+ }
212
+
213
+ .sub-link-title {
214
+ font-weight: 500;
215
+ }
216
+
217
+ .sub-link-path {
218
+ color: #888;
219
+ font-size: 0.85em;
220
+ font-family: 'Monaco', 'Courier New', monospace;
221
+ margin-left: auto;
222
+ }
223
+
224
+ .status-indicator {
225
+ display: inline-block;
226
+ width: 10px;
227
+ height: 10px;
228
+ border-radius: 50%;
229
+ margin-right: 6px;
230
+ }
231
+
232
+ .status-production { background: #28a745; }
233
+ .status-staging { background: #ffc107; }
234
+ .status-development { background: #007bff; }
235
+
236
+ .last-updated {
237
+ text-align: center;
238
+ color: #ffffff;
239
+ font-size: 0.9em;
240
+ margin-top: 30px;
241
+ font-style: italic;
242
+ }
243
+
244
+ footer {
245
+ text-align: center;
246
+ margin-top: 40px;
247
+ color: rgba(255,255,255,0.8);
248
+ font-size: 0.9em;
249
+ }
250
+ </style>
251
+ </head>
252
+ <body>
253
+ <%= yield %>
254
+
255
+ <div class="last-updated">
256
+ Last updated:
257
+ <span id="lastUpdated"><%= Time.current %></span>
258
+ </div>
259
+ </body>
17
260
  </html>
@@ -0,0 +1,63 @@
1
+ <header>
2
+ <h1>🚂 Remise</h1>
3
+ <p class="subtitle">Your Rails Development Hub</p>
4
+ </header>
5
+
6
+ <div class="sections-grid">
7
+ <%- @config[:sections].each do |section_key, section| %>
8
+ <div class="section">
9
+ <div class="section-header">
10
+ <span class="section-emoji"><%= leading_emoji(section[:label]) %></span>
11
+ <h2 class="section-title"><%= strip_leading_emoji(section[:label]) %></h2>
12
+ </div>
13
+ <div class="links-list">
14
+ <% section[:items].each do |section_item_key, section_item| %>
15
+ <% if section_item[:paths].present? %>
16
+ <details class="link-item-expandable">
17
+ <summary>
18
+ <span class="link-emoji"><%= leading_emoji(section_item[:label]) %></span>
19
+ <div class="link-content">
20
+ <div class="link-title"><%= strip_leading_emoji(section_item[:label]) %></div>
21
+ <div class="link-url"><a
22
+ href="<%= prefer_value(section_item[:url]) %>"
23
+ target="_blank"
24
+ onclick="event.stopPropagation()"
25
+ title="<%= prefer_value(section_item[:url]) %>"
26
+ ><%= prefer_formatted(section_item[:url]) %></a></div>
27
+ <%# TODO dynamic stuff
28
+ <div class="link-meta">v4.0.21 • 2025-07-01 by eval</div> %>
29
+ </div>
30
+ </summary>
31
+ <div class="sub-links">
32
+ <% section_item[:paths].each do |section_item_path_key, section_item_path| %>
33
+ <a
34
+ href="<%= prefer_value(section_item_path[:url]).start_with?("http") ? prefer_value(section_item_path[:url]) : prefer_value(section_item[:url]) + prefer_value(section_item_path[:url]) %>"
35
+ title="<%= prefer_value(section_item_path[:url]) %>"
36
+ target="_blank"
37
+ class="sub-link"
38
+ >
39
+ <span class="sub-link-icon"><%= leading_emoji(section_item_path[:title]) %></span>
40
+ <span class="sub-link-title"><%= strip_leading_emoji(section_item_path[:title]) %></span>
41
+ <span class="sub-link-path"><%= prefer_formatted(section_item_path[:url]) %></span>
42
+ </a>
43
+ <% end %>
44
+ </div>
45
+ </details>
46
+ <% else %>
47
+ <a href="<%= prefer_value(section_item[:url]) %>"
48
+ class="link-item"
49
+ target="_blank"
50
+ title="<%= prefer_value(section_item[:url]) %>"
51
+ >
52
+ <span class="link-emoji"><%= leading_emoji(section_item[:label]) %></span>
53
+ <div class="link-content">
54
+ <div class="link-title"><%= strip_leading_emoji(section_item[:label]) %></div>
55
+ <div class="link-url"><%= prefer_formatted(section_item[:url]) %></div>
56
+ </div>
57
+ </a>
58
+ <% end %>
59
+ <% end %>
60
+ </div>
61
+ </div>
62
+ <%- end %>
63
+ </div>
data/config/routes.rb CHANGED
@@ -1,2 +1,3 @@
1
1
  Remise::Engine.routes.draw do
2
+ root to: "application#index"
2
3
  end
data/lib/remise/engine.rb CHANGED
@@ -1,5 +1,23 @@
1
1
  module Remise
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Remise
4
+
5
+ config.before_initialize do
6
+ Nero.configure do |nero|
7
+ nero.add_tag("uri.fmt/strip-protocol") do |tag|
8
+ {
9
+ value: tag.args.join,
10
+ formatted: tag.args.join[/https?:\/\/(.+)/, 1]
11
+ }
12
+ end
13
+
14
+ nero.add_tag("uri.fmt/path-only") do |tag|
15
+ {
16
+ value: tag.args.join,
17
+ formatted: URI(tag.args.join).path
18
+ }
19
+ end
20
+ end
21
+ end
4
22
  end
5
23
  end
@@ -1,3 +1,3 @@
1
1
  module Remise
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/remise.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "remise/version"
2
2
  require "remise/engine"
3
+ require "nero"
3
4
 
4
5
  module Remise
5
6
  # Your code goes here...
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: remise
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gert Goet
@@ -23,10 +23,25 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: 7.2.2.1
26
- description: "A remise (from French) is a railway roundhouse - the building where
27
- locomotives \nare stored, maintained, and prepared for their daily runs. Just like
28
- a remise \nis where train journeys begin, this gem is your starting point for navigating
29
- \nyour Rails development environment."
26
+ - !ruby/object:Gem::Dependency
27
+ name: nero
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ description: |-
41
+ A remise (from French) is a railway roundhouse - the building where locomotives
42
+ are stored, maintained, and prepared for their daily runs. Just like a remise
43
+ is where train journeys begin, this gem is your starting point for navigating
44
+ your Rails development environment.
30
45
  email:
31
46
  - gert@thinkcreate.dk
32
47
  executables: []
@@ -41,8 +56,8 @@ files:
41
56
  - app/controllers/remise/application_controller.rb
42
57
  - app/helpers/remise/application_helper.rb
43
58
  - app/jobs/remise/application_job.rb
44
- - app/models/remise/application_record.rb
45
59
  - app/views/layouts/remise/application.html.erb
60
+ - app/views/remise/application/index.html.erb
46
61
  - config/routes.rb
47
62
  - lib/remise.rb
48
63
  - lib/remise/engine.rb
@@ -1,5 +0,0 @@
1
- module Remise
2
- class ApplicationRecord < ActiveRecord::Base
3
- self.abstract_class = true
4
- end
5
- end