tina4ruby 0.5.2 → 3.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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/README.md +434 -544
  4. data/exe/{tina4 → tina4ruby} +1 -0
  5. data/lib/tina4/ai.rb +312 -0
  6. data/lib/tina4/auth.rb +44 -3
  7. data/lib/tina4/auto_crud.rb +163 -0
  8. data/lib/tina4/cli.rb +389 -97
  9. data/lib/tina4/constants.rb +46 -0
  10. data/lib/tina4/cors.rb +74 -0
  11. data/lib/tina4/database/sqlite3_adapter.rb +139 -0
  12. data/lib/tina4/database.rb +144 -7
  13. data/lib/tina4/debug.rb +4 -79
  14. data/lib/tina4/dev_admin.rb +1162 -0
  15. data/lib/tina4/dev_mailbox.rb +191 -0
  16. data/lib/tina4/dev_reload.rb +9 -9
  17. data/lib/tina4/drivers/firebird_driver.rb +19 -3
  18. data/lib/tina4/drivers/mssql_driver.rb +3 -3
  19. data/lib/tina4/drivers/mysql_driver.rb +4 -4
  20. data/lib/tina4/drivers/postgres_driver.rb +9 -2
  21. data/lib/tina4/drivers/sqlite_driver.rb +1 -1
  22. data/lib/tina4/env.rb +42 -2
  23. data/lib/tina4/error_overlay.rb +252 -0
  24. data/lib/tina4/events.rb +90 -0
  25. data/lib/tina4/field_types.rb +4 -0
  26. data/lib/tina4/frond.rb +1497 -0
  27. data/lib/tina4/gallery/auth/meta.json +1 -0
  28. data/lib/tina4/gallery/auth/src/routes/api/gallery_auth.rb +114 -0
  29. data/lib/tina4/gallery/database/meta.json +1 -0
  30. data/lib/tina4/gallery/database/src/routes/api/gallery_db.rb +43 -0
  31. data/lib/tina4/gallery/error-overlay/meta.json +1 -0
  32. data/lib/tina4/gallery/error-overlay/src/routes/api/gallery_crash.rb +17 -0
  33. data/lib/tina4/gallery/orm/meta.json +1 -0
  34. data/lib/tina4/gallery/orm/src/routes/api/gallery_products.rb +16 -0
  35. data/lib/tina4/gallery/queue/meta.json +1 -0
  36. data/lib/tina4/gallery/queue/src/routes/api/gallery_queue.rb +325 -0
  37. data/lib/tina4/gallery/rest-api/meta.json +1 -0
  38. data/lib/tina4/gallery/rest-api/src/routes/api/gallery_hello.rb +14 -0
  39. data/lib/tina4/gallery/templates/meta.json +1 -0
  40. data/lib/tina4/gallery/templates/src/routes/gallery_page.rb +12 -0
  41. data/lib/tina4/gallery/templates/src/templates/gallery_page.twig +257 -0
  42. data/lib/tina4/health.rb +39 -0
  43. data/lib/tina4/html_element.rb +148 -0
  44. data/lib/tina4/localization.rb +2 -2
  45. data/lib/tina4/log.rb +203 -0
  46. data/lib/tina4/messenger.rb +562 -0
  47. data/lib/tina4/migration.rb +132 -29
  48. data/lib/tina4/orm.rb +463 -35
  49. data/lib/tina4/public/css/tina4.css +178 -1
  50. data/lib/tina4/public/css/tina4.min.css +1 -2
  51. data/lib/tina4/public/favicon.ico +0 -0
  52. data/lib/tina4/public/images/logo.svg +5 -0
  53. data/lib/tina4/public/images/tina4-logo-icon.webp +0 -0
  54. data/lib/tina4/public/js/frond.min.js +420 -0
  55. data/lib/tina4/public/js/tina4-dev-admin.min.js +367 -0
  56. data/lib/tina4/public/js/tina4.min.js +93 -0
  57. data/lib/tina4/public/swagger/index.html +90 -0
  58. data/lib/tina4/public/swagger/oauth2-redirect.html +63 -0
  59. data/lib/tina4/queue.rb +162 -6
  60. data/lib/tina4/queue_backends/lite_backend.rb +88 -0
  61. data/lib/tina4/rack_app.rb +331 -27
  62. data/lib/tina4/rate_limiter.rb +123 -0
  63. data/lib/tina4/request.rb +61 -15
  64. data/lib/tina4/response.rb +54 -24
  65. data/lib/tina4/response_cache.rb +551 -0
  66. data/lib/tina4/router.rb +90 -15
  67. data/lib/tina4/scss_compiler.rb +2 -2
  68. data/lib/tina4/seeder.rb +56 -61
  69. data/lib/tina4/service_runner.rb +303 -0
  70. data/lib/tina4/session.rb +85 -0
  71. data/lib/tina4/session_handlers/mongo_handler.rb +1 -1
  72. data/lib/tina4/session_handlers/valkey_handler.rb +43 -0
  73. data/lib/tina4/shutdown.rb +84 -0
  74. data/lib/tina4/sql_translation.rb +295 -0
  75. data/lib/tina4/template.rb +36 -6
  76. data/lib/tina4/templates/base.twig +2 -2
  77. data/lib/tina4/templates/errors/302.twig +14 -0
  78. data/lib/tina4/templates/errors/401.twig +9 -0
  79. data/lib/tina4/templates/errors/403.twig +22 -15
  80. data/lib/tina4/templates/errors/404.twig +22 -15
  81. data/lib/tina4/templates/errors/500.twig +31 -15
  82. data/lib/tina4/templates/errors/502.twig +9 -0
  83. data/lib/tina4/templates/errors/503.twig +12 -0
  84. data/lib/tina4/templates/errors/base.twig +37 -0
  85. data/lib/tina4/version.rb +1 -1
  86. data/lib/tina4/webserver.rb +28 -18
  87. data/lib/tina4.rb +118 -21
  88. metadata +68 -8
  89. data/lib/tina4/public/js/tina4.js +0 -134
  90. data/lib/tina4/public/js/tina4helper.js +0 -387
data/lib/tina4/cors.rb ADDED
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tina4
4
+ module CorsMiddleware
5
+ class << self
6
+ def config
7
+ @config ||= load_config
8
+ end
9
+
10
+ def reset!
11
+ @config = nil
12
+ end
13
+
14
+ # Handle OPTIONS preflight request, returns a Rack response array
15
+ def preflight_response(env = {})
16
+ origin = resolve_origin(env)
17
+ [
18
+ 204,
19
+ {
20
+ "access-control-allow-origin" => origin,
21
+ "access-control-allow-methods" => config[:methods],
22
+ "access-control-allow-headers" => config[:headers],
23
+ "access-control-max-age" => config[:max_age],
24
+ "access-control-allow-credentials" => config[:credentials]
25
+ },
26
+ [""]
27
+ ]
28
+ end
29
+
30
+ # Apply CORS headers to a response headers hash
31
+ def apply_headers(response_headers, env = {})
32
+ origin = resolve_origin(env)
33
+ response_headers["access-control-allow-origin"] = origin
34
+ response_headers["access-control-allow-methods"] = config[:methods]
35
+ response_headers["access-control-allow-headers"] = config[:headers]
36
+ response_headers["access-control-max-age"] = config[:max_age]
37
+ response_headers["access-control-allow-credentials"] = config[:credentials] if config[:credentials] == "true"
38
+ response_headers
39
+ end
40
+
41
+ # Check if a given origin is allowed
42
+ def origin_allowed?(origin)
43
+ return true if config[:origins] == "*"
44
+
45
+ allowed = config[:origins].split(",").map(&:strip)
46
+ allowed.include?(origin)
47
+ end
48
+
49
+ private
50
+
51
+ def load_config
52
+ {
53
+ origins: ENV["TINA4_CORS_ORIGINS"] || "*",
54
+ methods: ENV["TINA4_CORS_METHODS"] || "GET, POST, PUT, PATCH, DELETE, OPTIONS",
55
+ headers: ENV["TINA4_CORS_HEADERS"] || "Content-Type, Authorization, Accept",
56
+ max_age: ENV["TINA4_CORS_MAX_AGE"] || "86400",
57
+ credentials: ENV["TINA4_CORS_CREDENTIALS"] || "false"
58
+ }.freeze
59
+ end
60
+
61
+ def resolve_origin(env)
62
+ request_origin = env["HTTP_ORIGIN"] || env["HTTP_REFERER"]
63
+
64
+ if config[:origins] == "*"
65
+ "*"
66
+ elsif request_origin && origin_allowed?(request_origin.chomp("/"))
67
+ request_origin.chomp("/")
68
+ else
69
+ config[:origins].split(",").first&.strip || "*"
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tina4
4
+ module Adapters
5
+ class Sqlite3Adapter
6
+ attr_reader :connection, :db_path
7
+
8
+ def initialize(connection_string = nil)
9
+ @connection = nil
10
+ @db_path = nil
11
+ @in_transaction = false
12
+ connect(connection_string) if connection_string
13
+ end
14
+
15
+ def connect(connection_string)
16
+ require "sqlite3"
17
+ @db_path = connection_string.to_s.sub(/^sqlite3?:\/\//, "").sub(/^sqlite3?:/, "")
18
+ @connection = SQLite3::Database.new(@db_path)
19
+ @connection.results_as_hash = true
20
+ @connection.execute("PRAGMA journal_mode=WAL")
21
+ @connection.execute("PRAGMA foreign_keys=ON")
22
+ self
23
+ end
24
+
25
+ def close
26
+ @connection.close if @connection
27
+ @connection = nil
28
+ end
29
+
30
+ def connected?
31
+ !@connection.nil? && !@connection.closed?
32
+ end
33
+
34
+ # Execute a query and return rows as array of symbol-keyed hashes
35
+ def query(sql, params = [])
36
+ results = @connection.execute(sql, params)
37
+ results.map { |row| symbolize_keys(row) }
38
+ end
39
+
40
+ # Paginated fetch
41
+ def fetch(sql, limit = nil, offset = nil)
42
+ effective_sql = sql
43
+ if limit
44
+ effective_sql = "#{sql} LIMIT #{limit}"
45
+ effective_sql += " OFFSET #{offset}" if offset && offset > 0
46
+ end
47
+ query(effective_sql)
48
+ end
49
+
50
+ # Execute DDL or DML without returning rows
51
+ def exec(sql, params = [])
52
+ @connection.execute(sql, params)
53
+ { affected_rows: @connection.changes }
54
+ end
55
+
56
+ # Check if a table exists
57
+ def table_exists?(table)
58
+ rows = query(
59
+ "SELECT name FROM sqlite_master WHERE type='table' AND name = ?",
60
+ [table.to_s]
61
+ )
62
+ !rows.empty?
63
+ end
64
+
65
+ # Get column metadata for a table
66
+ def columns(table)
67
+ query("PRAGMA table_info(#{table})").map do |r|
68
+ {
69
+ name: r[:name],
70
+ type: r[:type],
71
+ nullable: r[:notnull] == 0,
72
+ default: r[:dflt_value],
73
+ primary_key: r[:pk] == 1
74
+ }
75
+ end
76
+ end
77
+
78
+ # List all user tables
79
+ def tables
80
+ rows = query("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
81
+ rows.map { |r| r[:name] }
82
+ end
83
+
84
+ # Get last inserted row id
85
+ def last_insert_id
86
+ @connection.last_insert_row_id
87
+ end
88
+
89
+ # Transaction support
90
+ def begin_transaction
91
+ return if @in_transaction
92
+ @connection.execute("BEGIN TRANSACTION")
93
+ @in_transaction = true
94
+ end
95
+
96
+ def commit
97
+ return unless @in_transaction
98
+ @connection.execute("COMMIT")
99
+ @in_transaction = false
100
+ end
101
+
102
+ def rollback
103
+ return unless @in_transaction
104
+ @connection.execute("ROLLBACK")
105
+ @in_transaction = false
106
+ end
107
+
108
+ def transaction
109
+ begin_transaction
110
+ yield self
111
+ commit
112
+ rescue => e
113
+ rollback
114
+ raise e
115
+ end
116
+
117
+ # Convenience: placeholder for parameterized queries
118
+ def placeholder
119
+ "?"
120
+ end
121
+
122
+ def placeholders(count)
123
+ (["?"] * count).join(", ")
124
+ end
125
+
126
+ def apply_limit(sql, limit, offset = 0)
127
+ "#{sql} LIMIT #{limit} OFFSET #{offset}"
128
+ end
129
+
130
+ private
131
+
132
+ def symbolize_keys(hash)
133
+ hash.each_with_object({}) do |(k, v), h|
134
+ h[k.to_s.to_sym] = v if k.is_a?(String) || k.is_a?(Symbol)
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require "json"
3
+ require "uri"
4
+ require "digest"
3
5
 
4
6
  module Tina4
5
7
  class Database
@@ -11,26 +13,36 @@ module Tina4
11
13
  "postgres" => "Tina4::Drivers::PostgresDriver",
12
14
  "postgresql" => "Tina4::Drivers::PostgresDriver",
13
15
  "mysql" => "Tina4::Drivers::MysqlDriver",
14
- "mysql2" => "Tina4::Drivers::MysqlDriver",
15
16
  "mssql" => "Tina4::Drivers::MssqlDriver",
16
17
  "sqlserver" => "Tina4::Drivers::MssqlDriver",
17
18
  "firebird" => "Tina4::Drivers::FirebirdDriver"
18
19
  }.freeze
19
20
 
20
- def initialize(connection_string, driver_name: nil)
21
- @connection_string = connection_string
22
- @driver_name = driver_name || detect_driver(connection_string)
21
+ def initialize(connection_string = nil, username: nil, password: nil, driver_name: nil)
22
+ @connection_string = connection_string || ENV["DATABASE_URL"]
23
+ @username = username || ENV["DATABASE_USERNAME"]
24
+ @password = password || ENV["DATABASE_PASSWORD"]
25
+ @driver_name = driver_name || detect_driver(@connection_string)
23
26
  @driver = create_driver
24
27
  @connected = false
28
+
29
+ # Query cache — off by default, opt-in via TINA4_DB_CACHE=true
30
+ @cache_enabled = truthy?(ENV["TINA4_DB_CACHE"])
31
+ @cache_ttl = (ENV["TINA4_DB_CACHE_TTL"] || "30").to_i
32
+ @query_cache = {} # key => { expires_at:, value: }
33
+ @cache_hits = 0
34
+ @cache_misses = 0
35
+ @cache_mutex = Mutex.new
36
+
25
37
  connect
26
38
  end
27
39
 
28
40
  def connect
29
- @driver.connect(@connection_string)
41
+ @driver.connect(@connection_string, username: @username, password: @password)
30
42
  @connected = true
31
- Tina4::Debug.info("Database connected: #{@driver_name}")
43
+ Tina4::Log.info("Database connected: #{@driver_name}")
32
44
  rescue => e
33
- Tina4::Debug.error("Database connection failed: #{e.message}")
45
+ Tina4::Log.error("Database connection failed: #{e.message}")
34
46
  @connected = false
35
47
  end
36
48
 
@@ -39,21 +51,84 @@ module Tina4
39
51
  @connected = false
40
52
  end
41
53
 
54
+ # ── Query Cache ──────────────────────────────────────────────
55
+
56
+ def cache_stats
57
+ @cache_mutex.synchronize do
58
+ {
59
+ enabled: @cache_enabled,
60
+ hits: @cache_hits,
61
+ misses: @cache_misses,
62
+ size: @query_cache.size,
63
+ ttl: @cache_ttl
64
+ }
65
+ end
66
+ end
67
+
68
+ def cache_clear
69
+ @cache_mutex.synchronize do
70
+ @query_cache.clear
71
+ @cache_hits = 0
72
+ @cache_misses = 0
73
+ end
74
+ end
75
+
42
76
  def fetch(sql, params = [], limit: nil, skip: nil)
43
77
  effective_sql = sql
44
78
  if limit
45
79
  effective_sql = @driver.apply_limit(effective_sql, limit, skip || 0)
46
80
  end
81
+
82
+ if @cache_enabled
83
+ key = cache_key(effective_sql, params)
84
+ cached = cache_get(key)
85
+ if cached
86
+ @cache_mutex.synchronize { @cache_hits += 1 }
87
+ return cached
88
+ end
89
+ result = @driver.execute_query(effective_sql, params)
90
+ result = Tina4::DatabaseResult.new(result, sql: effective_sql)
91
+ cache_set(key, result)
92
+ @cache_mutex.synchronize { @cache_misses += 1 }
93
+ return result
94
+ end
95
+
47
96
  rows = @driver.execute_query(effective_sql, params)
48
97
  Tina4::DatabaseResult.new(rows, sql: effective_sql)
49
98
  end
50
99
 
51
100
  def fetch_one(sql, params = [])
101
+ if @cache_enabled
102
+ key = cache_key(sql + ":ONE", params)
103
+ cached = cache_get(key)
104
+ if cached
105
+ @cache_mutex.synchronize { @cache_hits += 1 }
106
+ return cached
107
+ end
108
+ result = fetch(sql, params, limit: 1)
109
+ value = result.first
110
+ cache_set(key, value)
111
+ @cache_mutex.synchronize { @cache_misses += 1 }
112
+ return value
113
+ end
114
+
52
115
  result = fetch(sql, params, limit: 1)
53
116
  result.first
54
117
  end
55
118
 
56
119
  def insert(table, data)
120
+ cache_invalidate if @cache_enabled
121
+
122
+ # List of hashes — batch insert
123
+ if data.is_a?(Array)
124
+ return { success: true, affected_rows: 0 } if data.empty?
125
+ keys = data.first.keys.map(&:to_s)
126
+ placeholders = @driver.placeholders(keys.length)
127
+ sql = "INSERT INTO #{table} (#{keys.join(', ')}) VALUES (#{placeholders})"
128
+ params_list = data.map { |row| keys.map { |k| row[k.to_sym] || row[k] } }
129
+ return execute_many(sql, params_list)
130
+ end
131
+
57
132
  columns = data.keys.map(&:to_s)
58
133
  placeholders = @driver.placeholders(columns.length)
59
134
  sql = "INSERT INTO #{table} (#{columns.join(', ')}) VALUES (#{placeholders})"
@@ -62,6 +137,8 @@ module Tina4
62
137
  end
63
138
 
64
139
  def update(table, data, filter = {})
140
+ cache_invalidate if @cache_enabled
141
+
65
142
  set_parts = data.keys.map { |k| "#{k} = #{@driver.placeholder}" }
66
143
  where_parts = filter.keys.map { |k| "#{k} = #{@driver.placeholder}" }
67
144
  sql = "UPDATE #{table} SET #{set_parts.join(', ')}"
@@ -72,6 +149,23 @@ module Tina4
72
149
  end
73
150
 
74
151
  def delete(table, filter = {})
152
+ cache_invalidate if @cache_enabled
153
+
154
+ # List of hashes — delete each row
155
+ if filter.is_a?(Array)
156
+ filter.each { |row| delete(table, row) }
157
+ return { success: true }
158
+ end
159
+
160
+ # String filter — raw WHERE clause
161
+ if filter.is_a?(String)
162
+ sql = "DELETE FROM #{table}"
163
+ sql += " WHERE #{filter}" unless filter.empty?
164
+ @driver.execute(sql)
165
+ return { success: true }
166
+ end
167
+
168
+ # Hash filter — build WHERE from keys
75
169
  where_parts = filter.keys.map { |k| "#{k} = #{@driver.placeholder}" }
76
170
  sql = "DELETE FROM #{table}"
77
171
  sql += " WHERE #{where_parts.join(' AND ')}" unless filter.empty?
@@ -80,9 +174,19 @@ module Tina4
80
174
  end
81
175
 
82
176
  def execute(sql, params = [])
177
+ cache_invalidate if @cache_enabled
83
178
  @driver.execute(sql, params)
84
179
  end
85
180
 
181
+ def execute_many(sql, params_list = [])
182
+ total_affected = 0
183
+ params_list.each do |params|
184
+ @driver.execute(sql, params)
185
+ total_affected += 1
186
+ end
187
+ { success: true, affected_rows: total_affected }
188
+ end
189
+
86
190
  def transaction
87
191
  @driver.begin_transaction
88
192
  yield self
@@ -106,6 +210,39 @@ module Tina4
106
210
 
107
211
  private
108
212
 
213
+ def truthy?(val)
214
+ %w[true 1 yes on].include?((val || "").to_s.strip.downcase)
215
+ end
216
+
217
+ def cache_key(sql, params)
218
+ Digest::SHA256.hexdigest(sql + params.to_s)
219
+ end
220
+
221
+ def cache_get(key)
222
+ @cache_mutex.synchronize do
223
+ entry = @query_cache[key]
224
+ return nil unless entry
225
+ if Process.clock_gettime(Process::CLOCK_MONOTONIC) > entry[:expires_at]
226
+ @query_cache.delete(key)
227
+ return nil
228
+ end
229
+ entry[:value]
230
+ end
231
+ end
232
+
233
+ def cache_set(key, value)
234
+ @cache_mutex.synchronize do
235
+ @query_cache[key] = {
236
+ expires_at: Process.clock_gettime(Process::CLOCK_MONOTONIC) + @cache_ttl,
237
+ value: value
238
+ }
239
+ end
240
+ end
241
+
242
+ def cache_invalidate
243
+ @cache_mutex.synchronize { @query_cache.clear }
244
+ end
245
+
109
246
  def detect_driver(conn)
110
247
  case conn.to_s.downcase
111
248
  when /\.db$/, /\.sqlite/, /sqlite/
data/lib/tina4/debug.rb CHANGED
@@ -1,83 +1,8 @@
1
1
  # frozen_string_literal: true
2
- require "logger"
3
- require "fileutils"
4
2
 
5
- module Tina4
6
- module Debug
7
- LEVELS = {
8
- "[TINA4_LOG_ALL]" => Logger::DEBUG,
9
- "[TINA4_LOG_DEBUG]" => Logger::DEBUG,
10
- "[TINA4_LOG_INFO]" => Logger::INFO,
11
- "[TINA4_LOG_WARNING]" => Logger::WARN,
12
- "[TINA4_LOG_ERROR]" => Logger::ERROR,
13
- "[TINA4_LOG_NONE]" => Logger::FATAL
14
- }.freeze
15
-
16
- COLORS = {
17
- reset: "\e[0m", red: "\e[31m", green: "\e[32m",
18
- yellow: "\e[33m", blue: "\e[34m", magenta: "\e[35m",
19
- cyan: "\e[36m", gray: "\e[90m"
20
- }.freeze
21
-
22
- class << self
23
- def setup(root_dir = Dir.pwd)
24
- log_dir = File.join(root_dir, "logs")
25
- FileUtils.mkdir_p(log_dir)
26
- log_file = File.join(log_dir, "debug.log")
27
- @file_logger = Logger.new(log_file, 10, 5 * 1024 * 1024)
28
- @file_logger.level = Logger::DEBUG
29
- @console_logger = Logger.new($stdout)
30
- @console_logger.level = resolve_level
31
- @console_logger.formatter = method(:color_formatter)
32
- @file_logger.formatter = method(:plain_formatter)
33
- @initialized = true
34
- end
35
-
36
- def info(message, *args)
37
- log(:info, message, *args)
38
- end
39
-
40
- def debug(message, *args)
41
- log(:debug, message, *args)
42
- end
43
-
44
- def warning(message, *args)
45
- log(:warn, message, *args)
46
- end
3
+ # Backward compatibility: Tina4::Debug is now Tina4::Log
4
+ require_relative "log"
47
5
 
48
- def error(message, *args)
49
- log(:error, message, *args)
50
- end
51
-
52
- private
53
-
54
- def log(level, message, *args)
55
- setup unless @initialized
56
- full_message = args.empty? ? message.to_s : "#{message} #{args.map(&:to_s).join(' ')}"
57
- @console_logger.send(level, full_message)
58
- @file_logger.send(level, full_message)
59
- end
60
-
61
- def resolve_level
62
- env_level = ENV["TINA4_DEBUG_LEVEL"] || "[TINA4_LOG_ALL]"
63
- LEVELS[env_level] || Logger::DEBUG
64
- end
65
-
66
- def color_formatter(severity, datetime, _progname, message)
67
- color = case severity
68
- when "DEBUG" then COLORS[:gray]
69
- when "INFO" then COLORS[:green]
70
- when "WARN" then COLORS[:yellow]
71
- when "ERROR" then COLORS[:red]
72
- else COLORS[:reset]
73
- end
74
- ts = datetime.strftime("%Y-%m-%d %H:%M:%S")
75
- "#{COLORS[:gray]}[#{ts}]#{COLORS[:reset]} #{color}[#{severity}]#{COLORS[:reset]} #{message}\n"
76
- end
77
-
78
- def plain_formatter(severity, datetime, _progname, message)
79
- "[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] [#{severity}] #{message}\n"
80
- end
81
- end
82
- end
6
+ module Tina4
7
+ Debug = Log
83
8
  end