joys 0.1.1 → 0.1.3

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.
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+ module Joys
3
+ def self.html(&block)
4
+ renderer = Object.new
5
+ renderer.extend(Render::Helpers)
6
+ renderer.extend(Tags)
7
+ renderer.instance_variable_set(:@bf, String.new)
8
+ renderer.instance_variable_set(:@current_page, "page_standalone")
9
+ renderer.instance_variable_set(:@used_components, Set.new)
10
+ renderer.instance_variable_set(:@slots, {})
11
+ renderer.instance_eval(&block)
12
+ renderer.instance_variable_get(:@bf)
13
+ end
14
+ module Config
15
+ class << self
16
+ attr_accessor :env,:pages,:layouts,:components,:css_parts,:helpers,:markup_parser
17
+ def markup_parser
18
+ @markup_parser ||= ->(content) { content.to_s }
19
+ end
20
+ def env;@env||=ENV['JOYS_ENV']||(defined?(::Rails) ? ::Rails.env.to_s : "development");end
21
+ def dev?;env!="production";end
22
+ def pages;@pages||=path("pages");end
23
+ def layouts;@layouts||=path("layouts");end
24
+ def components;@components||=path("components");end
25
+ def css_parts;@css_parts||=path("css");end
26
+ def helpers;@helpers||=path("helpers");end
27
+ private
28
+ def path(t);defined?(::Rails) ? ::Rails.root.join("app/views/joys/#{t}").to_s : "views/joys/#{t}";end
29
+ end
30
+ end
31
+ module Adapters
32
+ class Base
33
+ def integrate!;raise NotImplementedError;end
34
+ def inject_helpers!;raise NotImplementedError;end
35
+ def controller_context(controller);{};end
36
+ end
37
+ class Rails < Base
38
+ def integrate!
39
+ return unless defined?(ActionController::Base)
40
+ ActionController::Base.include(ControllerMethods)
41
+ inject_helpers!
42
+ ::Rails.application.config.to_prepare do
43
+ Joys.preload! if ::Rails.env.production?
44
+ Joys::Render::Helpers.include(::Rails.application.helpers)
45
+ end
46
+ end
47
+ def inject_helpers!
48
+ Joys::Render::Helpers.include(ActionView::Helpers)
49
+ Joys::Render::Helpers.module_eval do
50
+ def request;Thread.current[:joys_request];end
51
+ def params;Thread.current[:joys_params];end
52
+ def current_user;Thread.current[:joys_current_user];end
53
+ def session;Thread.current[:joys_session];end
54
+ def _(content);raw(content);end
55
+ end
56
+ end
57
+ def controller_context(controller)
58
+ {
59
+ joys_request: controller.request,
60
+ joys_params: controller.params,
61
+ joys_current_user: (controller.current_user if controller.respond_to?(:current_user)),
62
+ joys_session: controller.session
63
+ }
64
+ end
65
+ module ControllerMethods
66
+ def render_joy(path,**locals)
67
+ context=Joys.adapter.controller_context(self)
68
+ context.each{|k,v|Thread.current[k]=v}
69
+ result=Joys.render_joy(path,**locals)
70
+ render html:result.html_safe,layout:false
71
+ ensure
72
+ context&.keys&.each{|k|Thread.current[k]=nil}
73
+ end
74
+ end
75
+ end
76
+ class Sinatra < Base
77
+ def integrate!;end
78
+ def inject_helpers!;end
79
+ end
80
+ class Hanami < Base
81
+ def integrate!;end
82
+ def inject_helpers!;end
83
+ end
84
+ class Roda < Base
85
+ def integrate!;end
86
+ def inject_helpers!;end
87
+ end
88
+ end
89
+ class << self
90
+ attr_accessor :adapter,:css_registry
91
+ def render_joy(path,**locals)
92
+ reload! if Config.dev?
93
+ file=File.join(Config.pages,"#{path}.rb")
94
+ raise "Template not found: #{file}" unless File.exist?(file)
95
+ locals.each{|k,v|eval("@#{k}=v",binding)}
96
+ result=eval(File.read(file),binding,file)
97
+ result.is_a?(String) ? result.freeze : ""
98
+ end
99
+ def preload!;load_helpers;load_dir(Config.layouts);load_dir(Config.components);load_css_parts;end
100
+ def load_helpers
101
+ return unless Dir.exist?(Config.helpers)
102
+ Dir.glob("#{Config.helpers}/**/*.rb").each do |f|
103
+ helper_module = Module.new
104
+ helper_module.module_eval(File.read(f), f)
105
+ Joys::Render::Helpers.include(helper_module)
106
+ end
107
+ end
108
+ def load_css_parts
109
+ return unless Dir.exist?(Config.css_parts)
110
+ Dir.glob("#{Config.css_parts}/**/*.css").each do |f|
111
+ relative_path = f.sub("#{Config.css_parts}/", '').sub('.css', '')
112
+ @css_registry[relative_path] = File.read(f).gsub(/\r\n?|\n/, '')
113
+ end
114
+ end
115
+ def detect_framework!
116
+ @css_registry={}
117
+ @adapter=if defined?(ActionController::Base)&&defined?(::Rails)
118
+ Adapters::Rails.new
119
+ elsif defined?(Sinatra::Base)
120
+ Adapters::Sinatra.new
121
+ elsif defined?(Hanami::Action)
122
+ Adapters::Hanami.new
123
+ elsif defined?(Roda)
124
+ Adapters::Roda.new
125
+ end
126
+ @adapter&.integrate!
127
+ end
128
+ private
129
+ def reload!;clear_cache!;@css_registry={};preload!;end
130
+ def load_dir(dir);Dir.exist?(dir)&&Dir.glob("#{dir}/**/*.rb").each{|f|load f};end
131
+ end
132
+ end
133
+ Joys.detect_framework!
data/lib/joys/core.rb ADDED
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+ # lib/joys/core.rb
3
+ module Joys
4
+ @css_path = "public/css";@cache={};@templates={};@compiled_styles={};@consolidated_cache={};@current_component = nil;@layouts={}
5
+ class << self
6
+ attr_reader :cache, :templates, :compiled_styles, :consolidated_cache, :layouts
7
+ attr_accessor :current_component, :current_page, :css_path,:adapter,:css_registry
8
+ # def load_css_parts
9
+ # return unless Dir.exist?(Config.css_parts)
10
+ # Dir.glob("#{Config.css_parts}/**/*.css").each do |f|
11
+ # relative_path = f.sub("#{Config.css_parts}/", '').sub('.css', '')
12
+ # @css_registry[relative_path] = File.read(f).gsub(/\r?\n/, "")
13
+ # end
14
+ # end
15
+ end
16
+ def self.make(name, *args, **kwargs, &block)
17
+ Joys.define :comp, name, *args, **kwargs, &block
18
+ end
19
+ def self.reset!
20
+ @cache.clear
21
+ @templates.clear
22
+ @compiled_styles.clear
23
+ @consolidated_cache.clear
24
+ @layouts.clear
25
+ @current_component = nil
26
+ @current_page = nil
27
+ end
28
+ def self.cache(cache_id, *args, &block);cache_key = [cache_id, args.hash];@cache[cache_key] ||= block.call;end
29
+ def self.define(type, name, &template)
30
+ full_name = "#{type}_#{name}";@templates[full_name] = template
31
+ case type
32
+ when :layout
33
+ @layouts[name] = Joys::Render.layout(&template)
34
+ define_singleton_method(full_name) { @layouts[name] }
35
+ when :page
36
+ define_singleton_method(full_name) do |**locals|
37
+ old_component = @current_component
38
+ old_page = @current_page
39
+ @current_page = full_name
40
+ @current_component = nil
41
+ result = cache(full_name, locals.hash) do
42
+ renderer = Object.new
43
+ renderer.extend(Render::Helpers)
44
+ renderer.extend(Tags)
45
+ locals.each { |k, v| renderer.instance_variable_set("@#{k}", v) }
46
+ renderer.instance_eval do
47
+ @bf = String.new(capacity: 8192)
48
+ @slots={}
49
+ @used_components = Set.new
50
+ @current_page = full_name # Pass page context to renderer
51
+ end
52
+ renderer.instance_exec(&template)
53
+ renderer.instance_variable_get(:@bf)
54
+ end
55
+ @current_component = old_component
56
+ @current_page = old_page
57
+ result.freeze
58
+ end
59
+ else
60
+ define_singleton_method(full_name) do |*args|
61
+ old_component = @current_component
62
+ @current_component = full_name if type == :comp
63
+ result = cache(full_name, *args) do
64
+ # This call now returns a hash, and the hash will be cached.
65
+ Joys::Render.compile(full_name) { instance_exec(*args, &template) }
66
+ end
67
+ @current_component = old_component
68
+ result
69
+ end
70
+ end
71
+ end
72
+ def self.clear_cache!
73
+ @cache.clear
74
+ @compiled_styles.clear
75
+ @consolidated_cache.clear
76
+ end
77
+ def self.page(name, **locals)
78
+ page_name = "page_#{name}"
79
+ renderer = Object.new
80
+ renderer.extend(Render::Helpers)
81
+ renderer.extend(Tags)
82
+ locals.each { |k, v| renderer.instance_variable_set("@#{k}", v) }
83
+ renderer.instance_eval do
84
+ @bf = String.new(capacity: 8192)
85
+ @slots={}
86
+ @used_components = Set.new
87
+ @current_page = page_name
88
+ @used_components.add(page_name) if Joys.compiled_styles[page_name]
89
+ end
90
+ page_template = @templates[page_name]
91
+ raise "No page template defined for #{name}" unless page_template
92
+ old_page = @current_page
93
+ @current_page = page_name
94
+ renderer.instance_exec(&page_template)
95
+ @current_page = old_page
96
+ renderer.instance_variable_get(:@bf).freeze
97
+ end
98
+ def self.comp(name, *args, **locals, &block)
99
+ renderer = Object.new
100
+ renderer.extend(Render::Helpers)
101
+ renderer.extend(Tags)
102
+ locals.each { |k, v| renderer.instance_variable_set("@#{k}", v) }
103
+ renderer.instance_eval do
104
+ @bf = String.new(capacity: 8192)
105
+ @slots = {}
106
+ @used_components = Set.new
107
+ end
108
+
109
+ comp_template = @templates["comp_#{name}"]
110
+ raise "No comp template defined for #{name}" unless comp_template
111
+
112
+ old_component = @current_component
113
+ @current_component = "comp_#{name}"
114
+
115
+ # Pass the block as a regular argument to the template
116
+ renderer.instance_exec(*args, block, &comp_template)
117
+
118
+ @current_component = old_component
119
+ renderer.instance_variable_get(:@bf).freeze
120
+ end
121
+ def self.html(&block)
122
+ # Clear any previous tracking
123
+ clear_tracked_components
124
+
125
+ renderer = Object.new
126
+ renderer.extend(Render::Helpers)
127
+ renderer.extend(Tags)
128
+ renderer.instance_variable_set(:@bf, String.new)
129
+ renderer.instance_variable_set(:@current_page, "page_standalone")
130
+ renderer.instance_variable_set(:@used_components, Set.new)
131
+ renderer.instance_variable_set(:@slots, {})
132
+
133
+ renderer.instance_eval(&block)
134
+
135
+ # Merge globally tracked components
136
+ tracked = get_tracked_components
137
+ used = renderer.instance_variable_get(:@used_components)
138
+ used.merge(tracked) if tracked
139
+
140
+ # Clear tracking after use
141
+ clear_tracked_components
142
+
143
+ renderer.instance_variable_get(:@bf)
144
+ end
145
+ module Render
146
+ def self.compile(context_name = nil, &block)
147
+ context = Object.new; context.extend(Helpers); context.extend(Tags)
148
+ context.instance_eval { @bf = String.new(capacity: 8192); @slots={}; @used_components = Set.new }
149
+ context.instance_variable_set(:@current_page, context_name) if context_name
150
+ context.instance_eval(&block)
151
+
152
+ # CHANGE: Return a hash containing both html and the used components set
153
+ {
154
+ html: context.instance_variable_get(:@bf).freeze,
155
+ components: context.instance_variable_get(:@used_components)
156
+ }
157
+ end
158
+ def self.layout(&layout_block)
159
+ template = Object.new;template.extend(Helpers);template.extend(Tags)
160
+ template.instance_eval { @bf = String.new;@slots={};@used_components = Set.new }
161
+ template.define_singleton_method(:pull) { |name = :main| @bf << "<!--SLOT:#{name}-->"; nil }
162
+ template.instance_eval(&layout_block)
163
+ precompiled = template.instance_variable_get(:@bf).freeze
164
+ ->(context, &content_block) {
165
+ context.instance_eval(&content_block) if content_block
166
+ slots = context.instance_variable_get(:@slots) || {}
167
+ used_components = context.instance_variable_get(:@used_components)
168
+ if used_components && !used_components.empty?
169
+ auto_styles = Joys::Styles.render_consolidated_styles(used_components)
170
+ else
171
+ auto_styles = ""
172
+ end
173
+ slot_regex = /<!--SLOT:(.*?)-->/.freeze
174
+ result = precompiled.gsub(slot_regex) do |match|
175
+ slot_name = $1.to_sym
176
+ slots[slot_name] || ""
177
+ end
178
+ if result.include?('<!--STYLES-->')
179
+ result = result.gsub(/<!--STYLES-->/, auto_styles)
180
+ end
181
+ if result.include?('<!--EXTERNAL_STYLES-->')
182
+ external_styles = Joys::Styles.render_external_styles(used_components)
183
+ result = result.gsub(/<!--EXTERNAL_STYLES-->/, external_styles)
184
+ end
185
+ result
186
+ }
187
+ end
188
+ end
189
+ end