its-showtime 0.1.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 (36) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +179 -0
  4. data/bin/showtime +47 -0
  5. data/lib/showtime/app.rb +399 -0
  6. data/lib/showtime/charts.rb +229 -0
  7. data/lib/showtime/component_registry.rb +38 -0
  8. data/lib/showtime/components/Components.md +309 -0
  9. data/lib/showtime/components/alerts.rb +83 -0
  10. data/lib/showtime/components/base.rb +63 -0
  11. data/lib/showtime/components/charts.rb +119 -0
  12. data/lib/showtime/components/data.rb +328 -0
  13. data/lib/showtime/components/inputs.rb +390 -0
  14. data/lib/showtime/components/layout.rb +135 -0
  15. data/lib/showtime/components/media.rb +73 -0
  16. data/lib/showtime/components/sidebar.rb +130 -0
  17. data/lib/showtime/components/text.rb +156 -0
  18. data/lib/showtime/components.rb +18 -0
  19. data/lib/showtime/compute_tracker.rb +21 -0
  20. data/lib/showtime/helpers.rb +53 -0
  21. data/lib/showtime/logger.rb +143 -0
  22. data/lib/showtime/public/.vite/manifest.json +34 -0
  23. data/lib/showtime/public/assets/antd-3aDVoXqG.js +447 -0
  24. data/lib/showtime/public/assets/charts-iowb_sWQ.js +3858 -0
  25. data/lib/showtime/public/assets/index-B2b3lWS5.js +43 -0
  26. data/lib/showtime/public/assets/index-M6NVamDM.css +1 -0
  27. data/lib/showtime/public/assets/react-BE6xecJX.js +32 -0
  28. data/lib/showtime/public/index.html +19 -0
  29. data/lib/showtime/public/letter.png +0 -0
  30. data/lib/showtime/public/logo.png +0 -0
  31. data/lib/showtime/release.rb +108 -0
  32. data/lib/showtime/session.rb +131 -0
  33. data/lib/showtime/version.rb +3 -0
  34. data/lib/showtime/views/index.erb +32 -0
  35. data/lib/showtime.rb +157 -0
  36. metadata +300 -0
@@ -0,0 +1,130 @@
1
+ module Showtime
2
+ module Components
3
+ class Sidebar < BaseComponent
4
+ def initialize
5
+ super
6
+ end
7
+
8
+ def to_h
9
+ super.merge({})
10
+ end
11
+
12
+ def title(text)
13
+ title = Title.new(text, false, nil, false)
14
+ Showtime::session.with_sidebar do
15
+ Showtime::session.add_element(title)
16
+ end
17
+ title
18
+ end
19
+
20
+ def header(text)
21
+ header = Header.new(text, false, nil, false)
22
+ Showtime::session.with_sidebar do
23
+ Showtime::session.add_element(header)
24
+ end
25
+ header
26
+ end
27
+
28
+ def subheader(text)
29
+ subheader = Subheader.new(text, false, nil, false)
30
+ Showtime::session.with_sidebar do
31
+ Showtime::session.add_element(subheader)
32
+ end
33
+ subheader
34
+ end
35
+
36
+ def write(text)
37
+ text = Text.new(text)
38
+ Showtime::session.with_sidebar do
39
+ Showtime::session.add_element(text)
40
+ end
41
+ text
42
+ end
43
+
44
+ def text(text)
45
+ text = Text.new(text)
46
+ Showtime::session.with_sidebar do
47
+ Showtime::session.add_element(text)
48
+ end
49
+ text
50
+ end
51
+
52
+ def checkbox(label, value: false, key: nil, help: nil)
53
+ checkbox = Checkbox.new(label, value: value, key: key, help: help)
54
+ Showtime::session.with_sidebar do
55
+ Showtime::session.add_element(checkbox)
56
+ end
57
+ checkbox
58
+ end
59
+
60
+ def button(label, key: nil, help: nil)
61
+ button = Button.new(label, key: key, help: help)
62
+ Showtime::session.with_sidebar do
63
+ Showtime::session.add_element(button)
64
+ end
65
+ button
66
+ end
67
+
68
+ def text_input(label, value: "", key: nil, help: nil)
69
+ text_input = TextInput.new(label, value: value, key: key, help: help)
70
+ Showtime::session.with_sidebar do
71
+ Showtime::session.add_element(text_input)
72
+ end
73
+ text_input
74
+ end
75
+
76
+ def number_input(label, min_value: nil, max_value: nil, value: nil, step: 1, key: nil, help: nil)
77
+ number_input = NumberInput.new(label, min_value: min_value, max_value: max_value, value: value, step: step, key: key, help: help)
78
+ Showtime::session.with_sidebar do
79
+ Showtime::session.add_element(number_input)
80
+ end
81
+ number_input
82
+ end
83
+
84
+ def date_input(label, value: nil, min_value: nil, max_value: nil, key: nil, help: nil)
85
+ date_input = DateInput.new(label, value: value, min_value: min_value, max_value: max_value, key: key, help: help)
86
+ Showtime::session.with_sidebar do
87
+ Showtime::session.add_element(date_input)
88
+ end
89
+ date_input
90
+ end
91
+
92
+ def selectbox(label, options, index: 0, key: nil, help: nil)
93
+ selectbox = Selectbox.new(label, options, index: index, key: key, help: help)
94
+ Showtime::session.with_sidebar do
95
+ Showtime::session.add_element(selectbox)
96
+ end
97
+ selectbox
98
+ end
99
+
100
+ def file_uploader(label, type: nil, accept_multiple_files: false, max_size: Showtime::Components::FileUploader::DEFAULT_MAX_SIZE, key: nil, help: nil, wide: false)
101
+ file_uploader = FileUploader.new(label, type: type, accept_multiple_files: accept_multiple_files, max_size: max_size, key: key, help: help, wide: wide)
102
+ Showtime::session.with_sidebar do
103
+ Showtime::session.add_element(file_uploader)
104
+ end
105
+ file_uploader
106
+ end
107
+
108
+ def image(src, caption: nil, width: nil, use_column_width: false, key: nil, help: nil)
109
+ image = Image.new(src, caption: caption, width: width, use_column_width: use_column_width, key: key, help: help)
110
+ Showtime::session.with_sidebar do
111
+ Showtime::session.add_element(image)
112
+ end
113
+ image
114
+ end
115
+
116
+ def markdown(content)
117
+ markdown = Markdown.new(content)
118
+ Showtime::session.with_sidebar do
119
+ Showtime::session.add_element(markdown)
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ module St
126
+ def self.sidebar
127
+ Showtime::Components::Sidebar.new
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,156 @@
1
+ require_relative 'base'
2
+
3
+ module Showtime
4
+ module Components
5
+ class Title < BaseComponent
6
+ attr_reader :text, :divider, :help, :anchor
7
+
8
+ def initialize(text, divider = false, help = nil, anchor = true)
9
+ super()
10
+ @text = text
11
+ @divider = divider
12
+ @help = help
13
+ @anchor = anchor
14
+ end
15
+
16
+ def to_h
17
+ super.merge({
18
+ text: @text,
19
+ divider: @divider,
20
+ help: @help,
21
+ anchor: @anchor
22
+ })
23
+ end
24
+ end
25
+
26
+ class Header < BaseComponent
27
+ attr_reader :text, :divider, :help, :anchor
28
+
29
+ def initialize(text, divider = false, help = nil, anchor = true)
30
+ super()
31
+ @text = text
32
+ @divider = divider
33
+ @help = help
34
+ @anchor = anchor
35
+ end
36
+
37
+ def to_h
38
+ super.merge({
39
+ text: @text,
40
+ divider: @divider,
41
+ help: @help,
42
+ anchor: @anchor
43
+ })
44
+ end
45
+ end
46
+
47
+ class Subheader < BaseComponent
48
+ attr_reader :text, :divider, :help, :anchor
49
+
50
+ def initialize(text, divider = false, help = nil, anchor = true)
51
+ super()
52
+ @text = text
53
+ @divider = divider
54
+ @help = help
55
+ @anchor = anchor
56
+ end
57
+
58
+ def to_h
59
+ super.merge({
60
+ text: @text,
61
+ divider: @divider,
62
+ help: @help,
63
+ anchor: @anchor
64
+ })
65
+ end
66
+ end
67
+
68
+ class Text < BaseComponent
69
+ attr_reader :text, :divider, :help
70
+
71
+ def initialize(text)
72
+ super()
73
+ @text = text
74
+ end
75
+
76
+ def to_h
77
+ super.merge({
78
+ text: @text,
79
+ })
80
+ end
81
+ end
82
+
83
+ class Markdown < BaseComponent
84
+ attr_reader :content
85
+
86
+ def initialize(content)
87
+ super()
88
+ @content = content
89
+ end
90
+
91
+ def to_h
92
+ super.merge({
93
+ content: @content
94
+ })
95
+ end
96
+ end
97
+
98
+ class Metric < BaseComponent
99
+ attr_reader :label, :value, :delta, :prefix, :suffix
100
+
101
+ def initialize(label, value:, delta: nil, prefix: nil, suffix: nil, key: nil, help: nil)
102
+ super(key: key, help: help)
103
+ @label = label
104
+ @value = value
105
+ @delta = delta
106
+ @prefix = prefix
107
+ @suffix = suffix
108
+ end
109
+
110
+ def to_h
111
+ super.merge({
112
+ label: @label,
113
+ value: @value,
114
+ delta: @delta,
115
+ prefix: @prefix,
116
+ suffix: @suffix
117
+ })
118
+ end
119
+ end
120
+ end
121
+
122
+ module St
123
+ def self.title(text, divider: false, help: nil, anchor: true)
124
+ anchor_enabled = anchor && !Showtime::session.in_sidebar?
125
+ Showtime::session.add_element(Showtime::Components::Title.new(text, divider, help, anchor_enabled))
126
+ end
127
+
128
+ def self.header(text, divider: false, help: nil, anchor: true)
129
+ anchor_enabled = anchor && !Showtime::session.in_sidebar?
130
+ Showtime::session.add_element(Showtime::Components::Header.new(text, divider, help, anchor_enabled))
131
+ end
132
+
133
+ def self.subheader(text, divider: false, help: nil, anchor: true)
134
+ anchor_enabled = anchor && !Showtime::session.in_sidebar?
135
+ Showtime::session.add_element(Showtime::Components::Subheader.new(text, divider, help, anchor_enabled))
136
+ end
137
+
138
+ def self.write(text)
139
+ self.text(text)
140
+ end
141
+
142
+ def self.text(text)
143
+ Showtime::session.add_element(Showtime::Components::Text.new(text))
144
+ end
145
+
146
+ def self.markdown(content)
147
+ Showtime::session.add_element(Showtime::Components::Markdown.new(content))
148
+ end
149
+
150
+ def self.metric(label, value:, delta: nil, prefix: nil, suffix: nil, key: nil, help: nil)
151
+ Showtime::session.add_element(
152
+ Showtime::Components::Metric.new(label, value: value, delta: delta, prefix: prefix, suffix: suffix, key: key, help: help)
153
+ )
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,18 @@
1
+ require 'json'
2
+ require 'base64'
3
+ require 'securerandom'
4
+
5
+ require_relative 'components/base'
6
+ require_relative 'components/text'
7
+ require_relative 'components/alerts'
8
+ require_relative 'components/charts'
9
+ require_relative 'components/layout'
10
+ require_relative 'components/data'
11
+ require_relative 'components/media'
12
+ require_relative 'components/inputs'
13
+ require_relative 'components/sidebar'
14
+
15
+ module Showtime
16
+ module Components
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ module Showtime
2
+ # Tracks value access during computation
3
+ class ComputeTracker
4
+ def initialize(&block)
5
+ @accessed_keys = Set.new
6
+ @result = track_access(&block)
7
+ end
8
+
9
+ attr_reader :accessed_keys, :result
10
+
11
+ private
12
+
13
+ def track_access(&block)
14
+ old_tracker = Thread.current[:compute_tracker]
15
+ Thread.current[:compute_tracker] = self
16
+ yield
17
+ ensure
18
+ Thread.current[:compute_tracker] = old_tracker
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,53 @@
1
+ require 'json'
2
+
3
+ module Showtime
4
+ module Helpers
5
+ def self.update_component_counter(component)
6
+ # Get the next counter directly from the session
7
+ Showtime.current_session.next_counter_for(component)
8
+ end
9
+
10
+ def self.absolute_path(relative_path)
11
+ # Prioritize using the main_script_path from the current session
12
+ session = Showtime.current_session
13
+ if session&.main_script_path
14
+ base_dir = File.dirname(session.main_script_path)
15
+ resolved_path = File.expand_path(relative_path, base_dir)
16
+ return resolved_path
17
+ else
18
+ # Fallback: resolve relative to CWD if no suitable caller found or path is relative
19
+ # and not handled above.
20
+ return File.expand_path(relative_path, Dir.pwd)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ module Vite
27
+ module Helpers
28
+ def vite_asset_path(chunk_name)
29
+ entry_point = vite_manifest_entry(chunk_name)
30
+ # entry_point['file'] is something like 'assets/index-123.js'
31
+ # We want it to be like '/assets/index-123.js' before prefixing
32
+ base_vite_path = "/#{entry_point['file']}"
33
+
34
+ script_name_prefix = ""
35
+ if defined?(request) && request.respond_to?(:script_name) && request.script_name
36
+ script_name_prefix = request.script_name.to_s
37
+ end
38
+
39
+ # Combine and clean up slashes
40
+ # If script_name_prefix = "/myapp", base_vite_path = "/vite/foo.js" -> "/myapp/vite/foo.js" (after squeeze)
41
+ # If script_name_prefix = "", base_vite_path = "/vite/foo.js" -> "/vite/foo.js"
42
+ (script_name_prefix + base_vite_path).squeeze('/')
43
+ end
44
+
45
+ def vite_manifest_entry(chunk_name)
46
+ manifest_path = File.join(settings.public_folder, '.vite/manifest.json')
47
+ manifest = JSON.parse(File.read(manifest_path))
48
+ entry_point = manifest.values.find { |entry| entry['name'] == chunk_name }
49
+ raise "#{chunk_name} entry point not found in Vite manifest" unless entry_point
50
+ entry_point
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,143 @@
1
+ require 'logger'
2
+ require 'json'
3
+ require 'fileutils'
4
+ require 'pastel'
5
+
6
+ module Showtime
7
+ class Logger
8
+ class << self
9
+ attr_accessor :logger, :config, :pastel
10
+
11
+ def configure
12
+ @config ||= default_config
13
+ yield(@config) if block_given?
14
+ @pastel = Pastel.new
15
+ setup_logger
16
+ end
17
+
18
+ def default_config
19
+ {
20
+ level: ENV.fetch('SHOWTIME_LOG_LEVEL', 'INFO').upcase,
21
+ format: ENV.fetch('SHOWTIME_LOG_FORMAT', 'text'),
22
+ output: ENV.fetch('SHOWTIME_LOG_OUTPUT', 'both'), # 'file', 'stdout', or 'both'
23
+ directory: ENV.fetch('SHOWTIME_LOG_DIR', nil),
24
+ file_name: ENV.fetch('SHOWTIME_LOG_FILE', 'showtime.log'),
25
+ rotation: ENV.fetch('SHOWTIME_LOG_ROTATION', 'daily'),
26
+ structured: ENV.fetch('SHOWTIME_LOG_STRUCTURED', 'false') == 'true'
27
+ }
28
+ end
29
+
30
+ def setup_logger
31
+ @logger = if @config[:custom_logger]
32
+ @config[:custom_logger]
33
+ else
34
+ create_logger
35
+ end
36
+
37
+ # Set log level
38
+ @logger.level = ::Logger.const_get(@config[:level])
39
+
40
+ # Set formatter
41
+ @logger.formatter = @config[:structured] ? method(:structured_formatter) : method(:text_formatter)
42
+ end
43
+
44
+ def create_logger
45
+ outputs = []
46
+
47
+ # Add file output if configured
48
+ if ['file', 'both'].include?(@config[:output])
49
+ log_dir = @config[:directory] || default_log_directory
50
+ FileUtils.mkdir_p(log_dir)
51
+ log_path = File.join(log_dir, @config[:file_name])
52
+ # Set up log rotation based on configuration
53
+ shift_age = @config[:rotation] || 'daily'
54
+ shift_size = 1024 * 1024 # 1MB
55
+
56
+ outputs << ::Logger.new(log_path, shift_age, shift_size)
57
+ end
58
+
59
+ # Add stdout output if configured
60
+ if ['stdout', 'both'].include?(@config[:output])
61
+ outputs << ::Logger.new($stdout)
62
+ end
63
+
64
+ # Return multi-logger if multiple outputs, otherwise single logger
65
+ outputs.size > 1 ? MultiLogger.new(outputs) : outputs.first
66
+ end
67
+
68
+ def default_log_directory
69
+ if ENV['SHOWTIME_APP_DIR']
70
+ File.join(ENV['SHOWTIME_APP_DIR'], 'log')
71
+ else
72
+ File.join(Dir.pwd, 'log')
73
+ end
74
+ end
75
+
76
+ def structured_formatter(severity, time, progname, msg)
77
+ log_entry = {
78
+ timestamp: time.iso8601,
79
+ level: severity,
80
+ message: msg
81
+ }
82
+ log_entry[:progname] = progname if progname
83
+ "#{log_entry.to_json}\n"
84
+ end
85
+
86
+ def text_formatter(severity, time, progname, msg)
87
+ timestamp = time.strftime('%Y-%m-%d %H:%M:%S.%L')
88
+ progname_str = progname ? " [#{progname}]" : ""
89
+
90
+ # Color schemes for different log levels
91
+ colors = {
92
+ 'DEBUG' => ->(s) { @pastel.bright_blue(s) },
93
+ 'INFO' => ->(s) { @pastel.bright_green(s) },
94
+ 'WARN' => ->(s) { @pastel.bright_yellow(s) },
95
+ 'ERROR' => ->(s) { @pastel.bright_red(s) },
96
+ 'FATAL' => ->(s) { @pastel.bold.bright_red(s) }
97
+ }
98
+
99
+ # Format timestamp in dim color
100
+ colored_timestamp = @pastel.dim("[#{timestamp}]")
101
+
102
+ # Format severity with appropriate color
103
+ colored_severity = colors[severity]&.call(severity) || severity
104
+
105
+ # Only use colors when outputting to terminal
106
+ if @config[:output] == 'stdout' || (@config[:output] == 'both' && @logger.is_a?(MultiLogger))
107
+ "#{colored_timestamp} #{colored_severity}#{progname_str}: #{msg}\n"
108
+ else
109
+ "[#{timestamp}] #{severity}#{progname_str}: #{msg}\n"
110
+ end
111
+ end
112
+
113
+ # Delegate logging methods to the logger instance
114
+ %i[debug info warn error fatal].each do |level|
115
+ define_method(level) do |*args, &block|
116
+ configure if logger.nil? # Ensures logger is initialized
117
+ logger.send(level, *args, &block)
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ # Handle multiple log outputs
124
+ class MultiLogger
125
+ def initialize(loggers)
126
+ @loggers = loggers
127
+ end
128
+
129
+ def level=(level)
130
+ @loggers.each { |logger| logger.level = level }
131
+ end
132
+
133
+ def formatter=(formatter)
134
+ @loggers.each { |logger| logger.formatter = formatter }
135
+ end
136
+
137
+ %i[debug info warn error fatal].each do |level|
138
+ define_method(level) do |*args, &block|
139
+ @loggers.each { |logger| logger.send(level, *args, &block) }
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,34 @@
1
+ {
2
+ "_antd-3aDVoXqG.js": {
3
+ "file": "assets/antd-3aDVoXqG.js",
4
+ "name": "antd",
5
+ "imports": [
6
+ "_react-BE6xecJX.js"
7
+ ]
8
+ },
9
+ "_charts-iowb_sWQ.js": {
10
+ "file": "assets/charts-iowb_sWQ.js",
11
+ "name": "charts",
12
+ "imports": [
13
+ "_react-BE6xecJX.js"
14
+ ]
15
+ },
16
+ "_react-BE6xecJX.js": {
17
+ "file": "assets/react-BE6xecJX.js",
18
+ "name": "react"
19
+ },
20
+ "index.html": {
21
+ "file": "assets/index-B2b3lWS5.js",
22
+ "name": "index",
23
+ "src": "index.html",
24
+ "isEntry": true,
25
+ "imports": [
26
+ "_react-BE6xecJX.js",
27
+ "_antd-3aDVoXqG.js",
28
+ "_charts-iowb_sWQ.js"
29
+ ],
30
+ "css": [
31
+ "assets/index-M6NVamDM.css"
32
+ ]
33
+ }
34
+ }