tina4ruby 3.13.126 → 3.13.128
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 +4 -4
- data/lib/tina4/cli.rb +123 -1
- data/lib/tina4/drivers/firebird_driver.rb +17 -11
- data/lib/tina4/drivers/mssql_driver.rb +14 -8
- data/lib/tina4/version.rb +1 -1
- metadata +2 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 63a254da7848fb136bf9c014d3ac291c31b9046ee2ad7bd40222189f5d8e031b
|
|
4
|
+
data.tar.gz: e9a23c45ec6b6c3ff46125454259447e743ffe6097a7866f486c6b5a8cc5aaad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dd3e42f53f1515ddfb137cd6713a11650309d4da359030cecde6be6368cad5309175b04943b25fa8bc5349e6b8505c2231a128b7de3c905c7a5605bf4d71c454
|
|
7
|
+
data.tar.gz: 81c929c2b3b06ce99682c440e1ce6385cb2bda7d836638e158fd0c0d2bcc41526eb7b7e055159c9aeab38468a16843d05b77b57f76beec525dab16ccc8cf9dec
|
data/lib/tina4/cli.rb
CHANGED
|
@@ -6,6 +6,7 @@ require "set"
|
|
|
6
6
|
require "json"
|
|
7
7
|
require "tmpdir" # Dir.mktmpdir — used by the ADR-0063 sandbox edit-hint scan
|
|
8
8
|
require "stringio" # StringIO — captures generator stdout during the sandbox run
|
|
9
|
+
require "open3" # Open3.capture2e — captures `ruby -c` output+status for `lint`
|
|
9
10
|
require_relative "port_takeover"
|
|
10
11
|
|
|
11
12
|
module Tina4
|
|
@@ -70,6 +71,7 @@ module Tina4
|
|
|
70
71
|
"test" => { handler: :cmd_test, summary: "Run inline tests" },
|
|
71
72
|
"queue" => { handler: :cmd_queue, usage: "<work|stats|retry|clear> [topic]", subcommands: QUEUE_SUBCOMMANDS.keys, summary: "Run queue workers and manage jobs" },
|
|
72
73
|
"build" => { handler: :cmd_build, usage: "[--tag NAME] [--file PATH]", summary: "Build the deployable Docker image" },
|
|
74
|
+
"lint" => { handler: :cmd_lint, usage: "[--fix] [--no-install]", summary: "Lint src/ + app.rb (rubocop, installed dev-only on demand, else ruby -c)" },
|
|
73
75
|
"version" => { handler: :cmd_version, summary: "Show Tina4 version" },
|
|
74
76
|
"routes" => { handler: :cmd_routes, summary: "List all registered routes" },
|
|
75
77
|
"console" => { handler: :cmd_console, summary: "Start an interactive console" },
|
|
@@ -516,7 +518,7 @@ module Tina4
|
|
|
516
518
|
# Parse --key value and --flag from args. Returns [flags_hash, positional_array]
|
|
517
519
|
def parse_flags(args)
|
|
518
520
|
# Boolean-only flags that never take a value argument
|
|
519
|
-
boolean_flags = %w[no-browser no-reload production managed all clear dev json public no-migration once dry-run]
|
|
521
|
+
boolean_flags = %w[no-browser no-reload production managed all clear dev json public no-migration once dry-run fix no-install]
|
|
520
522
|
|
|
521
523
|
flags = {}
|
|
522
524
|
positional = []
|
|
@@ -1206,6 +1208,126 @@ module Tina4
|
|
|
1206
1208
|
puts " Run: docker run -p 7147:7147 #{tag}"
|
|
1207
1209
|
end
|
|
1208
1210
|
|
|
1211
|
+
# ── lint ──────────────────────────────────────────────────────────────
|
|
1212
|
+
#
|
|
1213
|
+
# Lint the project's source. The framework ships NO linter (so a Tina4 app
|
|
1214
|
+
# stays zero-dependency); `tina4ruby lint` uses the project's own rubocop and
|
|
1215
|
+
# INSTALLS it as a DEV dependency on demand when it is absent. Layers:
|
|
1216
|
+
#
|
|
1217
|
+
# * rubocop present (bundled via the project Gemfile, or on PATH): run it.
|
|
1218
|
+
# `--fix` runs `rubocop -a` (safe autocorrect). rubocop reports syntax
|
|
1219
|
+
# too, so it is the whole pass when present.
|
|
1220
|
+
# * rubocop absent: silently `bundle add rubocop --group development` --
|
|
1221
|
+
# running `tina4ruby lint` is the consent to add it -- then run it. It
|
|
1222
|
+
# lands in the development group only, never the app's runtime
|
|
1223
|
+
# dependencies. `--no-install` skips this.
|
|
1224
|
+
# * Baseline (zero dependency): the built-in `ruby -c` syntax parse -- no
|
|
1225
|
+
# execution, nothing written, no dependency. Used with `--no-install` or
|
|
1226
|
+
# when the install cannot run (no bundler/Gemfile, offline).
|
|
1227
|
+
#
|
|
1228
|
+
# Contract (identical across all four frameworks): exit 0 = clean, non-zero =
|
|
1229
|
+
# findings; the summary names the tool that ran in [brackets]. Scope is the
|
|
1230
|
+
# user's app (`src/` recursively + `app.rb`), mirroring how `tina4ruby test`
|
|
1231
|
+
# runs the project's own specs -- not the framework's code. Ruby mirror of
|
|
1232
|
+
# the Python master's `_lint` (tina4-python/tina4_python/cli/__init__.py).
|
|
1233
|
+
#
|
|
1234
|
+
# tina4ruby lint # rubocop (installed dev-only on demand), else baseline
|
|
1235
|
+
# tina4ruby lint --fix # rubocop -a
|
|
1236
|
+
# tina4ruby lint --no-install # rubocop if already present, else the syntax baseline
|
|
1237
|
+
def cmd_lint(argv)
|
|
1238
|
+
flags, _positional = parse_flags(argv)
|
|
1239
|
+
fix = flags["fix"] == true
|
|
1240
|
+
no_install = flags["no-install"] == true
|
|
1241
|
+
|
|
1242
|
+
rb_files = []
|
|
1243
|
+
rb_files.concat(Dir.glob(File.join("src", "**", "*.rb")).sort) if File.directory?("src")
|
|
1244
|
+
rb_files << "app.rb" if File.file?("app.rb")
|
|
1245
|
+
|
|
1246
|
+
if rb_files.empty?
|
|
1247
|
+
puts " lint: nothing to lint (no src/ or app.rb)."
|
|
1248
|
+
exit 0
|
|
1249
|
+
end
|
|
1250
|
+
|
|
1251
|
+
rubocop = resolve_rubocop
|
|
1252
|
+
|
|
1253
|
+
# Silent on-demand install: running `tina4ruby lint` is the consent to add
|
|
1254
|
+
# rubocop as a DEV dependency of the project. --no-install opts out (CI /
|
|
1255
|
+
# offline) and falls through to the zero-dependency baseline. Needs bundler
|
|
1256
|
+
# AND a Gemfile to mutate.
|
|
1257
|
+
if rubocop.nil? && !no_install
|
|
1258
|
+
bundle = which_executable("bundle")
|
|
1259
|
+
if bundle && File.file?("Gemfile")
|
|
1260
|
+
puts " · rubocop not found -- adding it as a dev dependency (bundle add rubocop --group development)..."
|
|
1261
|
+
if system(bundle, "add", "rubocop", "--group", "development")
|
|
1262
|
+
rubocop = resolve_rubocop
|
|
1263
|
+
else
|
|
1264
|
+
puts " · could not install rubocop -- using the zero-dependency syntax baseline."
|
|
1265
|
+
end
|
|
1266
|
+
end
|
|
1267
|
+
end
|
|
1268
|
+
|
|
1269
|
+
if rubocop
|
|
1270
|
+
label = fix ? "rubocop -a" : "rubocop"
|
|
1271
|
+
command = rubocop + (fix ? ["-a"] : []) + rb_files
|
|
1272
|
+
ok = system(*command) # inherits stdio — rubocop prints its own report
|
|
1273
|
+
if ok
|
|
1274
|
+
puts " ✓ lint clean -- #{rb_files.length} file(s) [#{label}]"
|
|
1275
|
+
exit 0
|
|
1276
|
+
end
|
|
1277
|
+
puts " ✗ lint failed -- #{rb_files.length} file(s) [#{label}]"
|
|
1278
|
+
exit 1
|
|
1279
|
+
end
|
|
1280
|
+
|
|
1281
|
+
# Zero-dependency baseline: the built-in `ruby -c` syntax parse. No
|
|
1282
|
+
# execution, nothing written, no dependency added.
|
|
1283
|
+
puts " · syntax baseline has no autofix (--no-install, or rubocop unavailable)." if fix
|
|
1284
|
+
|
|
1285
|
+
syntax_errors = 0
|
|
1286
|
+
rb_files.each do |rel|
|
|
1287
|
+
output, status = Open3.capture2e("ruby", "-c", rel)
|
|
1288
|
+
next if status.success? # "Syntax OK"
|
|
1289
|
+
|
|
1290
|
+
syntax_errors += 1
|
|
1291
|
+
# `ruby -c` names the file+line in its first diagnostic line
|
|
1292
|
+
# (e.g. "ruby: src/broken.rb:2: syntax errors found (SyntaxError)"); drop
|
|
1293
|
+
# the redundant leading "ruby: " so the shape matches the Python master's
|
|
1294
|
+
# "file:lineno: msg".
|
|
1295
|
+
first_line = output.strip.sub(/\Aruby:\s*/, "").lines.first.to_s.rstrip
|
|
1296
|
+
first_line = "#{rel}: syntax error" if first_line.empty?
|
|
1297
|
+
puts " ✗ #{first_line}"
|
|
1298
|
+
end
|
|
1299
|
+
|
|
1300
|
+
if syntax_errors.positive?
|
|
1301
|
+
puts " ✗ lint failed -- #{syntax_errors} syntax error(s) in #{rb_files.length} file(s) [ruby -c]"
|
|
1302
|
+
exit 1
|
|
1303
|
+
end
|
|
1304
|
+
puts " ✓ lint clean -- #{rb_files.length} file(s) [ruby -c, syntax only]"
|
|
1305
|
+
exit 0
|
|
1306
|
+
end
|
|
1307
|
+
|
|
1308
|
+
# Resolve a runnable rubocop for the rich lint pass. Returns the argv PREFIX
|
|
1309
|
+
# to invoke it (`[<bundle>, "exec", "rubocop"]` when the project's Gemfile
|
|
1310
|
+
# bundles it — the version the project pins — or `[<rubocop-path>]` on PATH),
|
|
1311
|
+
# or nil to fall back to the `ruby -c` baseline. Mirrors the Python master's
|
|
1312
|
+
# `_resolve_ruff` (resolve the project's OWN linter). The bundler probe
|
|
1313
|
+
# (`bundle exec rubocop --version`) is a REAL resolve check: exit 0 only when
|
|
1314
|
+
# the gem is actually in the bundle. Bundler is preferred over a bare PATH
|
|
1315
|
+
# rubocop because inside a bundled project the pinned version is the correct
|
|
1316
|
+
# one (and a bare `rubocop` may trip Bundler's "not part of the bundle").
|
|
1317
|
+
def resolve_rubocop
|
|
1318
|
+
# Only the PROJECT's own bundled rubocop counts -- linting must be
|
|
1319
|
+
# reproducible from the project's pinned dev dependency, NOT a stray global
|
|
1320
|
+
# rubocop on PATH (which would also make the on-demand install
|
|
1321
|
+
# non-deterministic: a global tool would preempt it). When the project has
|
|
1322
|
+
# none, the caller installs it dev-only via `bundle add`.
|
|
1323
|
+
bundle = which_executable("bundle")
|
|
1324
|
+
if bundle && File.file?("Gemfile") &&
|
|
1325
|
+
system(bundle, "exec", "rubocop", "--version", out: File::NULL, err: File::NULL)
|
|
1326
|
+
return [bundle, "exec", "rubocop"]
|
|
1327
|
+
end
|
|
1328
|
+
nil
|
|
1329
|
+
end
|
|
1330
|
+
|
|
1209
1331
|
# Locate an executable on PATH — the stdlib-only equivalent of Python's
|
|
1210
1332
|
# shutil.which (no new gem). Honours PATHEXT on Windows.
|
|
1211
1333
|
def which_executable(cmd)
|
|
@@ -230,18 +230,24 @@ module Tina4
|
|
|
230
230
|
rows.map { |row| decode_blobs(symbolize_keys(row)) }
|
|
231
231
|
end
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
233
|
+
# Translate SQLite-canonical SQL to Firebird at APPLY time, mirroring the
|
|
234
|
+
# Python master's firebird.py _translate_sql: strip AUTOINCREMENT (Firebird
|
|
235
|
+
# uses generators), bare TRUE/FALSE -> 1/0 (Firebird has no boolean type; a
|
|
236
|
+
# TRUE/FALSE INSIDE a string literal is data and is left untouched), rewrite
|
|
237
|
+
# the types Firebird rejects -- TEXT -> BLOB SUB_TYPE TEXT (-607), REAL ->
|
|
238
|
+
# DOUBLE PRECISION -- and drop CREATE TABLE IF NOT EXISTS. Every helper only
|
|
239
|
+
# touches DDL keywords or bare boolean tokens, so a plain
|
|
240
|
+
# INSERT/UPDATE/DELETE/SELECT is returned untouched. This is what lets a
|
|
241
|
+
# REALLY-generated migration (id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
242
|
+
# bio TEXT, price REAL) apply on Firebird instead of raising -607/-104.
|
|
243
|
+
def translate_sql(sql)
|
|
243
244
|
sql = Tina4::SQLTranslator.auto_increment_syntax(sql, "firebird")
|
|
244
|
-
sql = Tina4::SQLTranslator.
|
|
245
|
+
sql = Tina4::SQLTranslator.boolean_to_int(sql)
|
|
246
|
+
Tina4::SQLTranslator.ddl_types(sql, "firebird")
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def execute(sql, params = [])
|
|
250
|
+
sql = translate_sql(sql)
|
|
245
251
|
result = with_reconnect do
|
|
246
252
|
if params.empty?
|
|
247
253
|
@connection.execute(sql)
|
|
@@ -58,15 +58,21 @@ module Tina4
|
|
|
58
58
|
rows
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
# Translate SQLite-canonical SQL to T-SQL at APPLY time, mirroring the
|
|
62
|
+
# Python master's mssql.py _translate_sql: AUTOINCREMENT -> IDENTITY(1,1),
|
|
63
|
+
# bare TRUE/FALSE -> 1/0 (MSSQL has BIT, not a boolean type; a TRUE/FALSE
|
|
64
|
+
# INSIDE a string literal is data and is left untouched), strip CREATE TABLE
|
|
65
|
+
# IF NOT EXISTS (not T-SQL) and map TIMESTAMP -> DATETIME2 (MSSQL's TIMESTAMP
|
|
66
|
+
# is a rowversion). Every helper only touches DDL keywords or bare boolean
|
|
67
|
+
# tokens, so a plain INSERT/UPDATE/DELETE/SELECT is returned untouched.
|
|
68
|
+
def translate_sql(sql)
|
|
68
69
|
sql = Tina4::SQLTranslator.auto_increment_syntax(sql, "mssql")
|
|
69
|
-
sql = Tina4::SQLTranslator.
|
|
70
|
+
sql = Tina4::SQLTranslator.boolean_to_int(sql)
|
|
71
|
+
Tina4::SQLTranslator.ddl_types(sql, "mssql")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def execute(sql, params = [])
|
|
75
|
+
sql = translate_sql(sql)
|
|
70
76
|
effective_sql = interpolate_params(sql, params)
|
|
71
77
|
|
|
72
78
|
# Capture the generated IDENTITY AT WRITE TIME — mirror of the Python
|
data/lib/tina4/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tina4ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.13.
|
|
4
|
+
version: 3.13.128
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tina4 Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rack
|
|
@@ -276,20 +276,6 @@ dependencies:
|
|
|
276
276
|
- - "~>"
|
|
277
277
|
- !ruby/object:Gem::Version
|
|
278
278
|
version: '3.12'
|
|
279
|
-
- !ruby/object:Gem::Dependency
|
|
280
|
-
name: rubocop
|
|
281
|
-
requirement: !ruby/object:Gem::Requirement
|
|
282
|
-
requirements:
|
|
283
|
-
- - "~>"
|
|
284
|
-
- !ruby/object:Gem::Version
|
|
285
|
-
version: '1.50'
|
|
286
|
-
type: :development
|
|
287
|
-
prerelease: false
|
|
288
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
289
|
-
requirements:
|
|
290
|
-
- - "~>"
|
|
291
|
-
- !ruby/object:Gem::Version
|
|
292
|
-
version: '1.50'
|
|
293
279
|
- !ruby/object:Gem::Dependency
|
|
294
280
|
name: openapi3_parser
|
|
295
281
|
requirement: !ruby/object:Gem::Requirement
|