yescode 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +23 -0
- data/Dockerfile +46 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.md +73 -0
- data/exe/yescode +16 -0
- data/lib/yes_app.rb +114 -0
- data/lib/yes_controller.rb +85 -0
- data/lib/yes_logger.rb +8 -0
- data/lib/yes_mail.rb +88 -0
- data/lib/yes_rack_logger.rb +59 -0
- data/lib/yes_record.rb +283 -0
- data/lib/yes_routes.rb +64 -0
- data/lib/yes_static.rb +25 -0
- data/lib/yes_view.rb +99 -0
- data/lib/yescode/asset.rb +24 -0
- data/lib/yescode/asset_compiler.rb +33 -0
- data/lib/yescode/assets.rb +30 -0
- data/lib/yescode/database.rb +136 -0
- data/lib/yescode/emote.rb +85 -0
- data/lib/yescode/env.rb +23 -0
- data/lib/yescode/generator.rb +487 -0
- data/lib/yescode/logfmt_formatter.rb +30 -0
- data/lib/yescode/queries.rb +27 -0
- data/lib/yescode/refinements.rb +19 -0
- data/lib/yescode/request_cache/middleware.rb +29 -0
- data/lib/yescode/request_cache.rb +56 -0
- data/lib/yescode/router.rb +78 -0
- data/lib/yescode/strings.rb +23 -0
- data/lib/yescode.rb +48 -0
- data/yescode.gemspec +41 -0
- metadata +161 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
# Copyright (c) 2011 Michel Martens
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require "cgi/escape"
|
22
|
+
|
23
|
+
class Emote
|
24
|
+
VERSION = "1.3.1".freeze
|
25
|
+
|
26
|
+
PATTERN = /
|
27
|
+
^[^\S\n]*(%)[^\S\n]*(.*?)(?:\n|\Z) | # Ruby evaluated lines
|
28
|
+
(<\?)\s+(.*?)\s+\?> | # Multiline Ruby blocks
|
29
|
+
(\$\{)(.*?)\} | # Ruby evaluated to strings unescaped
|
30
|
+
(\{\{)(.*?)\}\} # Ruby evaluated to strings html escaped
|
31
|
+
/mx
|
32
|
+
|
33
|
+
def self.h(value)
|
34
|
+
CGI.escapeHTML(value.to_s)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.src(template, vars = [])
|
38
|
+
terms = template.split(PATTERN)
|
39
|
+
|
40
|
+
code = "proc do |params, __o| params ||= {}; __o ||= '';"
|
41
|
+
|
42
|
+
vars.each do |var|
|
43
|
+
code << format("%<val>s = params[%<key>p];", { val: var, key: var })
|
44
|
+
end
|
45
|
+
|
46
|
+
while (term = terms.shift)
|
47
|
+
code << case term
|
48
|
+
when "<?"
|
49
|
+
"#{terms.shift}\n"
|
50
|
+
when "%"
|
51
|
+
"#{terms.shift}\n"
|
52
|
+
when "${"
|
53
|
+
"__o << (#{terms.shift}).to_s\n"
|
54
|
+
when "{{"
|
55
|
+
"__o << Emote.h(#{terms.shift}).to_s\n"
|
56
|
+
else
|
57
|
+
"__o << #{term.dump}\n"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
code << "__o; end"
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.parse(template, context = self, vars = [], name = "template")
|
65
|
+
context.instance_eval(src(template, vars), name, -1)
|
66
|
+
end
|
67
|
+
|
68
|
+
module Helpers
|
69
|
+
def emote(file, params = {}, context = self)
|
70
|
+
file_cache[file] ||= File.read(file)
|
71
|
+
key = params.hash + context.hash
|
72
|
+
emote_cache[key] ||= Emote.parse(file_cache[file], context, params.keys, file)
|
73
|
+
|
74
|
+
emote_cache[key][params]
|
75
|
+
end
|
76
|
+
|
77
|
+
def emote_cache
|
78
|
+
Thread.current[:_emote_cache] ||= {}
|
79
|
+
end
|
80
|
+
|
81
|
+
def file_cache
|
82
|
+
Thread.current[:_file_cache] ||= {}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/yescode/env.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yescode
|
4
|
+
class Env
|
5
|
+
class << self
|
6
|
+
def development?
|
7
|
+
ENV["RACK_ENV"] == "development"
|
8
|
+
end
|
9
|
+
|
10
|
+
def test?
|
11
|
+
ENV["RACK_ENV"] == "test"
|
12
|
+
end
|
13
|
+
|
14
|
+
def production?
|
15
|
+
ENV["RACK_ENV"] == "production"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.env
|
21
|
+
Env
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,487 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "securerandom"
|
5
|
+
|
6
|
+
module Yescode
|
7
|
+
class Generator
|
8
|
+
INVALID_COMMAND_MESSAGE = "Command not supported. Try g, gen, generate, migrate or rollback."
|
9
|
+
VIEW_DIR = File.join(".", "app", "views")
|
10
|
+
|
11
|
+
using Refinements
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def generate(*gen_args)
|
15
|
+
type, *args = gen_args
|
16
|
+
|
17
|
+
case type
|
18
|
+
when "controller"
|
19
|
+
generate_controller(*args)
|
20
|
+
when "queries"
|
21
|
+
generate_queries(*args)
|
22
|
+
when "model"
|
23
|
+
generate_model(*args)
|
24
|
+
when "view"
|
25
|
+
generate_view(*args)
|
26
|
+
when "views"
|
27
|
+
generate_views(*args)
|
28
|
+
when "template"
|
29
|
+
generate_template(*args)
|
30
|
+
when "migration"
|
31
|
+
generate_migration(*args)
|
32
|
+
when "mvc"
|
33
|
+
generate_mvc(*args)
|
34
|
+
when "app"
|
35
|
+
generate_app(*args)
|
36
|
+
else
|
37
|
+
puts INVALID_COMMAND_MESSAGE
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def generate_app(dir)
|
42
|
+
FileUtils.mkdir_p(File.join(".", dir))
|
43
|
+
{
|
44
|
+
"public" => %w[css js],
|
45
|
+
"db" => %w[migrations],
|
46
|
+
"app" => %w[models views controllers emails jobs modules]
|
47
|
+
}.each do |k, v|
|
48
|
+
v.each do |folder|
|
49
|
+
FileUtils.mkdir_p(File.join(".", dir, k, folder))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
File.write(
|
53
|
+
File.join(dir, "Gemfile"),
|
54
|
+
<<~RB
|
55
|
+
source "https://rubygems.org"
|
56
|
+
git_source(:github) { |repo| "https://github.com/\#\{repo\}.git" }
|
57
|
+
|
58
|
+
ruby "3.1.2"
|
59
|
+
|
60
|
+
gem "tipi", "0.52"
|
61
|
+
gem "yescode", "1.0.0"
|
62
|
+
RB
|
63
|
+
)
|
64
|
+
File.write(
|
65
|
+
File.join(dir, "Dockerfile"),
|
66
|
+
<<~DOCKER
|
67
|
+
FROM docker.io/library/ruby:slim
|
68
|
+
|
69
|
+
RUN apt-get update -qq
|
70
|
+
RUN apt-get install -y --no-install-recommends build-essential libjemalloc2 fonts-liberation wget gnupg2 libc6
|
71
|
+
RUN rm -rf /var/lib/apt/lists/*
|
72
|
+
|
73
|
+
RUN wget https://www.sqlite.org/2022/sqlite-autoconf-3380200.tar.gz && \
|
74
|
+
tar xvfz sqlite-autoconf-3380200.tar.gz && \
|
75
|
+
cd sqlite-autoconf-3380200 && \
|
76
|
+
./configure && \
|
77
|
+
make && \
|
78
|
+
make install && \
|
79
|
+
rm -rf sqlite-autoconf-3380200
|
80
|
+
|
81
|
+
RUN wget https://github.com/watchexec/watchexec/releases/download/cli-v1.18.11/watchexec-1.18.11-x86_64-unknown-linux-gnu.tar.xz && \
|
82
|
+
tar xf watchexec-1.18.11-x86_64-unknown-linux-gnu.tar.xz && \
|
83
|
+
mv watchexec-1.18.11-x86_64-unknown-linux-gnu/watchexec /usr/local/bin/ && \
|
84
|
+
rm -rf watchexec-1.18.11-x86_64-unknown-linux-gnu
|
85
|
+
|
86
|
+
RUN wget https://github.com/DarthSim/hivemind/releases/download/v1.1.0/hivemind-v1.1.0-linux-amd64.gz && \
|
87
|
+
gunzip hivemind-v1.1.0-linux-amd64.gz && \
|
88
|
+
mv hivemind-v1.1.0-linux-amd64 /usr/local/bin/hivemind && \
|
89
|
+
chmod +x /usr/local/bin/hivemind
|
90
|
+
|
91
|
+
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
|
92
|
+
|
93
|
+
ARG USER=app
|
94
|
+
ARG GROUP=app
|
95
|
+
ARG UID=1101
|
96
|
+
ARG GID=1101
|
97
|
+
ARG DIR=/home/app
|
98
|
+
|
99
|
+
RUN groupadd --gid $GID $GROUP
|
100
|
+
RUN useradd --uid $UID --gid $GID --groups $GROUP -ms /bin/bash $USER
|
101
|
+
|
102
|
+
RUN chown -R $USER:$GROUP $DIR
|
103
|
+
|
104
|
+
USER $USER
|
105
|
+
WORKDIR $DIR
|
106
|
+
|
107
|
+
COPY --chown=$USER Gemfile Gemfile.lock $DIR
|
108
|
+
|
109
|
+
RUN bundle install
|
110
|
+
|
111
|
+
COPY --chown=$USER . $DIR
|
112
|
+
DOCKER
|
113
|
+
)
|
114
|
+
File.write(
|
115
|
+
File.join(dir, "Procfile"),
|
116
|
+
"web: bundle exec tipi --listen 0.0.0.0:$PORT config.ru"
|
117
|
+
)
|
118
|
+
File.write(
|
119
|
+
File.join(dir, "Procfile.dev"),
|
120
|
+
"web: watchexec --exts rb,emote,sql --restart --signal SIGKILL --debounce 100 -- bundle exec tipi --listen 0.0.0.0:$PORT config.ru"
|
121
|
+
)
|
122
|
+
File.write(
|
123
|
+
File.join(dir, "config.ru"),
|
124
|
+
"require \"./app\"\n\nrun App.freeze.app"
|
125
|
+
)
|
126
|
+
File.write(
|
127
|
+
File.join(dir, "public", "404.html"),
|
128
|
+
"<h1>404</h1>"
|
129
|
+
)
|
130
|
+
File.write(
|
131
|
+
File.join(dir, "public", "500.html"),
|
132
|
+
"<h1>500</h1>"
|
133
|
+
)
|
134
|
+
File.write(
|
135
|
+
File.join(dir, "app", "routes.rb"),
|
136
|
+
<<~RB
|
137
|
+
class Routes < YesRoutes
|
138
|
+
get "/", :Home, :index
|
139
|
+
end
|
140
|
+
RB
|
141
|
+
)
|
142
|
+
File.write(
|
143
|
+
File.join(dir, "app", "controllers", "home.rb"),
|
144
|
+
<<~RB
|
145
|
+
class Home < YesController
|
146
|
+
def index
|
147
|
+
HomeIndex.new
|
148
|
+
end
|
149
|
+
end
|
150
|
+
RB
|
151
|
+
)
|
152
|
+
File.write(
|
153
|
+
File.join(dir, "app", "views", "home_index.rb"),
|
154
|
+
<<~RB
|
155
|
+
class HomeIndex < Layout
|
156
|
+
end
|
157
|
+
RB
|
158
|
+
)
|
159
|
+
File.write(
|
160
|
+
File.join(dir, "app", "views", "home_index.emote"),
|
161
|
+
<<~RB
|
162
|
+
<h1>Welcome to yescode!</h1>
|
163
|
+
RB
|
164
|
+
)
|
165
|
+
File.write(
|
166
|
+
File.join(dir, "app", "views", "layout.rb"),
|
167
|
+
<<~RB
|
168
|
+
class Layout < YesView
|
169
|
+
def title
|
170
|
+
"yescode"
|
171
|
+
end
|
172
|
+
|
173
|
+
def description
|
174
|
+
"Yes, another ruby mvc web framework"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
RB
|
178
|
+
)
|
179
|
+
File.write(
|
180
|
+
File.join(dir, "app", "views", "layout.emote"),
|
181
|
+
<<~RB
|
182
|
+
<!DOCTYPE html>
|
183
|
+
<html lang="en">
|
184
|
+
<head>
|
185
|
+
<title>{{title}}</title>
|
186
|
+
<meta charset="utf-8" />
|
187
|
+
<meta name="title" description="{{title}}" />
|
188
|
+
<meta name="description" description="{{description}}" />
|
189
|
+
% css.each do |href|
|
190
|
+
<link href={{href}} rel="stylesheet"></link>
|
191
|
+
% end
|
192
|
+
% js.each do |src|
|
193
|
+
<script src={{src}} type="application/javascript" defer></script>
|
194
|
+
% end
|
195
|
+
</head>
|
196
|
+
<body>
|
197
|
+
<nav>
|
198
|
+
<a href={{path :Home, :index}}>Home</a>
|
199
|
+
</nav>
|
200
|
+
${content}
|
201
|
+
</body>
|
202
|
+
</html>
|
203
|
+
<html
|
204
|
+
RB
|
205
|
+
)
|
206
|
+
File.write(
|
207
|
+
File.join(dir, "app.rb"),
|
208
|
+
<<~RB
|
209
|
+
require "yescode"
|
210
|
+
|
211
|
+
require_all %w[
|
212
|
+
./app/modules/*
|
213
|
+
./app/models/*
|
214
|
+
./app/emails/*
|
215
|
+
./app/jobs/*
|
216
|
+
./app/views/layout
|
217
|
+
./app/views/*
|
218
|
+
./app/controllers/*
|
219
|
+
./app/routes
|
220
|
+
]
|
221
|
+
|
222
|
+
class App < YesApp
|
223
|
+
logger YesLogger.new($stdout)
|
224
|
+
|
225
|
+
use YesStatic, root: "public" if development?
|
226
|
+
use YesRackLogger
|
227
|
+
use Rack::ShowExceptions if development?
|
228
|
+
use Rack::Runtime
|
229
|
+
use Rack::ETag
|
230
|
+
use Rack::Head
|
231
|
+
use Rack::ContentLength
|
232
|
+
use Rack::ContentType
|
233
|
+
use Rack::Session::Cookie, default_session_cookie
|
234
|
+
use Rack::Csrf, raise: development?
|
235
|
+
|
236
|
+
css %w[]
|
237
|
+
|
238
|
+
js %w[]
|
239
|
+
|
240
|
+
migrations "db/migrations/*.sql"
|
241
|
+
|
242
|
+
routes :Routes
|
243
|
+
|
244
|
+
if production?
|
245
|
+
migrate
|
246
|
+
bundle_static_files
|
247
|
+
end
|
248
|
+
end
|
249
|
+
RB
|
250
|
+
)
|
251
|
+
File.write(
|
252
|
+
File.join(dir, ".env"),
|
253
|
+
<<~SH
|
254
|
+
RACK_ENV=development
|
255
|
+
PORT=9292
|
256
|
+
SECRET=#{SecureRandom.hex(32)}
|
257
|
+
DATABASE_URL=development.sqlite3
|
258
|
+
SH
|
259
|
+
)
|
260
|
+
# .env
|
261
|
+
# Gemfile
|
262
|
+
# Gemfile.lock
|
263
|
+
# Dockerfile
|
264
|
+
# Procfile
|
265
|
+
# config.ru
|
266
|
+
# public/
|
267
|
+
# public/404.html
|
268
|
+
# public/500.html
|
269
|
+
# public/js
|
270
|
+
# public/css
|
271
|
+
# db/
|
272
|
+
# db/migrations
|
273
|
+
# app/
|
274
|
+
# app/models
|
275
|
+
# app/views
|
276
|
+
# app/controllers
|
277
|
+
# app/jobs
|
278
|
+
# app/modules
|
279
|
+
# app/emails
|
280
|
+
# app/routes.rb
|
281
|
+
# app.rb
|
282
|
+
end
|
283
|
+
|
284
|
+
def generate_mvc(filename)
|
285
|
+
generate_model(filename)
|
286
|
+
generate_controller(filename)
|
287
|
+
generate_views(filename)
|
288
|
+
end
|
289
|
+
|
290
|
+
def generate_queries(filename)
|
291
|
+
filepath = File.join(".", "app", "models", "#{filename}.sql")
|
292
|
+
contents = <<~SQL
|
293
|
+
-- name: all
|
294
|
+
select *
|
295
|
+
from #{filename}
|
296
|
+
|
297
|
+
-- name: count
|
298
|
+
-- fn: value
|
299
|
+
select count(*)
|
300
|
+
from #{filename}
|
301
|
+
|
302
|
+
-- name: by_#{filename}_id
|
303
|
+
select *
|
304
|
+
from #{filename}
|
305
|
+
where #{filename}_id = ?
|
306
|
+
|
307
|
+
-- name: latest
|
308
|
+
select *
|
309
|
+
from #{filename}
|
310
|
+
order by created_at desc
|
311
|
+
|
312
|
+
-- name: oldest
|
313
|
+
select *
|
314
|
+
from #{filename}
|
315
|
+
order by created_at
|
316
|
+
|
317
|
+
-- name: latest_with_limit
|
318
|
+
select *
|
319
|
+
from #{filename}
|
320
|
+
order by created_at desc
|
321
|
+
limit 30
|
322
|
+
SQL
|
323
|
+
|
324
|
+
File.write(filepath, contents)
|
325
|
+
end
|
326
|
+
|
327
|
+
def generate_model(filename)
|
328
|
+
filepath = File.join(".", "app", "models", "#{filename}.rb")
|
329
|
+
class_name = filename.pascal_case
|
330
|
+
contents = <<~RB
|
331
|
+
class #{class_name} < AppRecord
|
332
|
+
queries "#{filename}.sql"
|
333
|
+
end
|
334
|
+
RB
|
335
|
+
File.write(filepath, contents)
|
336
|
+
|
337
|
+
generate_migration("create_table_#{filename}")
|
338
|
+
generate_queries(filename)
|
339
|
+
end
|
340
|
+
|
341
|
+
def generate_controller(filename)
|
342
|
+
class_name = filename.pascal_case
|
343
|
+
var_name = filename
|
344
|
+
route = <<~RB
|
345
|
+
class #{class_name} < AppController
|
346
|
+
def index
|
347
|
+
all = #{class_name}.all
|
348
|
+
|
349
|
+
#{class_name}Index.new(all)
|
350
|
+
end
|
351
|
+
|
352
|
+
def show
|
353
|
+
#{class_name}Show.new(#{var_name})
|
354
|
+
end
|
355
|
+
|
356
|
+
def new
|
357
|
+
@#{var_name} ||= #{class_name}.new
|
358
|
+
|
359
|
+
#{class_name}New.new(@#{var_name})
|
360
|
+
end
|
361
|
+
|
362
|
+
def create
|
363
|
+
@#{var_name} = #{class_name}.new(#{var_name}_params)
|
364
|
+
|
365
|
+
if @#{var_name}.insert
|
366
|
+
redirect path(:#{class_name}, :index)
|
367
|
+
else
|
368
|
+
new
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def edit
|
373
|
+
#{class_name}Edit.new(#{var_name})
|
374
|
+
end
|
375
|
+
|
376
|
+
def update
|
377
|
+
if #{var_name}.update(#{var_name}_params)
|
378
|
+
redirect path(:#{class_name}, :index)
|
379
|
+
else
|
380
|
+
edit
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
def delete
|
385
|
+
#{var_name}.delete
|
386
|
+
|
387
|
+
redirect path(:#{class_name}, :index)
|
388
|
+
end
|
389
|
+
|
390
|
+
private
|
391
|
+
|
392
|
+
def #{var_name}_params
|
393
|
+
params.slice()
|
394
|
+
end
|
395
|
+
|
396
|
+
def #{var_name}
|
397
|
+
@#{var_name} ||= #{class_name}.first! :by_#{var_name}_id, params[:#{var_name}_id]
|
398
|
+
end
|
399
|
+
end
|
400
|
+
RB
|
401
|
+
|
402
|
+
File.write(
|
403
|
+
File.join(".", "app", "controllers", "#{filename}.rb"),
|
404
|
+
route
|
405
|
+
)
|
406
|
+
|
407
|
+
routes_filename = File.join(".", "app", "routes.rb")
|
408
|
+
routes_file = File.read(routes_filename)
|
409
|
+
idx = routes_file.rindex(/end/)
|
410
|
+
routes_file.insert(idx, "\n resource \"/#{filename}\", :#{class_name}\n")
|
411
|
+
|
412
|
+
File.write routes_filename, routes_file
|
413
|
+
end
|
414
|
+
|
415
|
+
def generate_view(filename, accessors = [])
|
416
|
+
class_name = filename.pascal_case
|
417
|
+
filepath = File.join(VIEW_DIR, "#{filename}.rb")
|
418
|
+
|
419
|
+
return if File.exist?(filepath)
|
420
|
+
|
421
|
+
view = <<~RB
|
422
|
+
class #{class_name} < Layout
|
423
|
+
view "#{filename}.emote"
|
424
|
+
attr_accessor #{accessors.map { |a| ":#{a}" }.join(', ')}
|
425
|
+
|
426
|
+
def initialize(#{accessors.join(', ')})
|
427
|
+
#{accessors.map { |a| "@#{a} = #{a}" }.join("\n")}
|
428
|
+
end
|
429
|
+
end
|
430
|
+
RB
|
431
|
+
File.write(filepath, view)
|
432
|
+
|
433
|
+
generate_template(filename)
|
434
|
+
end
|
435
|
+
|
436
|
+
def generate_views(prefix)
|
437
|
+
%w[new show edit index].each do |suffix|
|
438
|
+
generate_view("#{prefix}_#{suffix}", [])
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
def generate_template(filename)
|
443
|
+
filename = File.join(VIEW_DIR, "#{filename}.emote")
|
444
|
+
|
445
|
+
return if File.exist?(filename)
|
446
|
+
|
447
|
+
File.write(filename, "<div></div>")
|
448
|
+
end
|
449
|
+
|
450
|
+
def generate_templates(prefix)
|
451
|
+
%w[index new edit show form].each do |page|
|
452
|
+
generate_template("#{prefix}_#{page}")
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
def generate_migration(filename, columns = [])
|
457
|
+
table_name = filename.split('_').last
|
458
|
+
column_string = columns.map do |c|
|
459
|
+
name, type = c.split(':')
|
460
|
+
|
461
|
+
"#{name} #{type}"
|
462
|
+
end.join(",\n ")
|
463
|
+
|
464
|
+
sql = <<~SQL
|
465
|
+
-- name: up
|
466
|
+
create table #{table_name} (
|
467
|
+
#{table_name}_id integer primary key,
|
468
|
+
#{column_string}
|
469
|
+
created_at integer not null default(strftime('%s', 'now')),
|
470
|
+
updated_at integer
|
471
|
+
)
|
472
|
+
|
473
|
+
-- name: down
|
474
|
+
drop table #{table_name}
|
475
|
+
SQL
|
476
|
+
|
477
|
+
sql = "-- name: up\n\n-- name: down" unless filename.start_with?("create_table")
|
478
|
+
|
479
|
+
filename = "#{Time.now.to_i}_#{filename}"
|
480
|
+
filepath = File.join(".", "db", "migrations", "#{filename}.sql")
|
481
|
+
|
482
|
+
puts "Writing file #{filepath}"
|
483
|
+
File.write(filepath, sql)
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yescode
|
4
|
+
class LogfmtFormatter < ::Logger::Formatter
|
5
|
+
def call(severity, datetime, progname, msg)
|
6
|
+
timestamp = datetime.strftime('%Y-%m-%d %H:%M:%S.%L')
|
7
|
+
|
8
|
+
parts = {
|
9
|
+
level: severity.downcase,
|
10
|
+
in: progname
|
11
|
+
}
|
12
|
+
|
13
|
+
parts.merge!(msg)
|
14
|
+
|
15
|
+
output = "[#{timestamp}] #{parts.reject { |_, v| v.nil? }.map { |k, v| "#{k}=#{v}" }.join(' ')}\n"
|
16
|
+
|
17
|
+
if severity.downcase == "debug"
|
18
|
+
blue(output)
|
19
|
+
else
|
20
|
+
output
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def blue(str)
|
27
|
+
"\e[34m#{str}\e[0m"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Yescode
|
2
|
+
class Queries
|
3
|
+
class << self
|
4
|
+
def name(line)
|
5
|
+
line.match(/^--\s*name\s*:\s*(\S+)/).to_a.last
|
6
|
+
end
|
7
|
+
|
8
|
+
def sql(line)
|
9
|
+
line.match(/^[^-{2}](.*)/).to_a.first
|
10
|
+
end
|
11
|
+
|
12
|
+
def queries(filename)
|
13
|
+
queries = []
|
14
|
+
|
15
|
+
File.foreach filename do |line|
|
16
|
+
query_name = name(line)
|
17
|
+
query_sql = sql(line)
|
18
|
+
|
19
|
+
queries << [query_name.to_sym, ""] if query_name
|
20
|
+
queries.last[1] = "#{queries.last[1]} #{query_sql}".strip if query_sql
|
21
|
+
end
|
22
|
+
|
23
|
+
queries
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Refinements
|
2
|
+
SNAKE_CASE_REGEX = /\B([A-Z])/
|
3
|
+
|
4
|
+
refine String do
|
5
|
+
def snake_case
|
6
|
+
gsub(SNAKE_CASE_REGEX, '_\1').downcase
|
7
|
+
end
|
8
|
+
|
9
|
+
def camel_case
|
10
|
+
first, *rest = split("_")
|
11
|
+
|
12
|
+
"#{first}#{rest.map(&:capitalize).join}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def pascal_case
|
16
|
+
split("_").map(&:capitalize).join
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Yescode
|
2
|
+
module RequestCache
|
3
|
+
class Middleware
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
RequestCache.begin!
|
10
|
+
|
11
|
+
status, headers, body = @app.call(env)
|
12
|
+
|
13
|
+
body = Rack::BodyProxy.new(body) do
|
14
|
+
RequestCache.end!
|
15
|
+
RequestCache.clear!
|
16
|
+
end
|
17
|
+
|
18
|
+
returned = true
|
19
|
+
|
20
|
+
[status, headers, body]
|
21
|
+
ensure
|
22
|
+
unless returned
|
23
|
+
RequestCache.end!
|
24
|
+
RequestCache.clear!
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|