primrose 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7bd154f7df76d53c3db2057c499b67edf417e621e0ff44c537382e9c78d946f6
4
+ data.tar.gz: b1c996a5c456ca5f0407189656d99352918812559139ab2a9b35a82b44ffbab3
5
+ SHA512:
6
+ metadata.gz: dd085723441f9d29c8713a20f9bb3767771c0e26d3377e6c360bdd0407902446d88462a4940e73c984d09e4aa6718903d3d4fdeb2ccb392a91a6d4c31df3b444
7
+ data.tar.gz: f124f56efebe490b8cab7d368f3187ffcff27ce80fba1030579131eb2097c18108ea7a9448fdfbc618c99c50877bb5a7d92f78a65eae008edbf39dc03f385573
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Daniel M. Matongo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # Primrose Framework Documentation
2
+
3
+ ## Overview
4
+
5
+ Primrose is a Ruby library designed for building component-based web applications. It provides a collection of modules and classes to manage the state, routing, rendering, and event handling for a modern web application. Primrose offers the following key features:
6
+
7
+ - State Management using `Primrose::Store`
8
+ - Component-based UI building blocks via `Primrose::Rose` subclasses
9
+ - Routing capabilities with `Primrose::Router`
10
+ - Observable state via `Primrose::Observable`
11
+ - Built-in helper methods and utility functions
12
+ - Template-based rendering with ERB
13
+
14
+ ---
15
+
16
+ ## Table of Contents
17
+ - [Installation](#installation)
18
+ - [Usage](#usage)
19
+ - [State Management](#state-management)
20
+ - [Routing](#routing)
21
+ - [Components](#components)
22
+ - [Event Handling](#event-handling)
23
+ - [Examples](#examples)
24
+ - [Contributing](#contributing)
25
+ - [License](#license)
26
+
27
+ ---
28
+
29
+ ## Installation
30
+
31
+ You can install the library by adding it to your `Gemfile`:
32
+
33
+ ```ruby
34
+ gem 'primrose', '~> 0.0.1'
35
+ ```
36
+
37
+ Then run `bundle install` to install the dependency.
38
+
39
+ ---
40
+
41
+ ## Usage
42
+
43
+ ### State Management
44
+
45
+ ```ruby
46
+ store = Primrose::Store.new({ counter: 0 })
47
+
48
+ store.dispatch({
49
+ type: 'INCREMENT',
50
+ updates: {
51
+ counter: -> (current_value) { current_value + 1 }
52
+ }
53
+ })
54
+ ```
55
+
56
+ ### Routing
57
+
58
+ ```ruby
59
+ router = Primrose::Router.new
60
+
61
+ router.route('/home') { puts "You're at home" }
62
+ router.route('/about') { puts "About page" }
63
+
64
+ router.navigate('/home') # Outputs "You're at home"
65
+ ```
66
+
67
+ ### Components
68
+
69
+ Creating a custom component is easy:
70
+
71
+ ```ruby
72
+ class MyComponent < Primrose::Rose
73
+ def render
74
+ "Hello, world!"
75
+ end
76
+ end
77
+ ```
78
+
79
+ ### Event Handling
80
+
81
+ You can attach event handlers to your components:
82
+
83
+ ```ruby
84
+ component = MyComponent.new
85
+ component.on(:click) { puts 'Component clicked!' }
86
+ component.trigger(:click) # Outputs "Component clicked!"
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Examples
92
+
93
+ Below are some example templates to showcase how components can be structured.
94
+
95
+ - Button Component:
96
+
97
+ ```erb
98
+ <button onclick="<%= @action %>">
99
+ <%= @label %>
100
+ </button>
101
+ ```
102
+
103
+ - Text Field Component:
104
+
105
+ ```erb
106
+ <input type="text" placeholder="<%= @placeholder %>">
107
+ ```
108
+
109
+ - Checkbox Component:
110
+
111
+ ```erb
112
+ <div class="checkbox">
113
+ <input type="checkbox">
114
+ <label><%= @label %></label>
115
+ </div>
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Contributing
121
+
122
+ Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
123
+
124
+ ---
125
+
126
+ ## License
127
+
128
+ This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.
129
+
130
+ ---
131
+
132
+ For more detailed usage and full API documentation, please refer to the inline code comments and accompanying documentation.
133
+
134
+ Feel free to raise an issue for any bugs, feature requests, or questions.
135
+
136
+ **Note**: This documentation assumes that you are familiar with Ruby and ERB (Embedded Ruby).
@@ -0,0 +1,28 @@
1
+ require_relative '../prim'
2
+
3
+ module Primrose
4
+ module Components
5
+ class Button < Rose
6
+ def initialize(label:, action:, style: nil, disabled: false, loading: false)
7
+ @label = label
8
+ @action = action
9
+ @style = style
10
+ @disabled = disabled
11
+ @loading = loading
12
+ super()
13
+ end
14
+
15
+ def render
16
+ Prim.render('templates/components/button.prim.erb', self)
17
+ end
18
+
19
+ def set_disabled(value)
20
+ @state.alter({ disabled: value })
21
+ end
22
+
23
+ def set_loading(value)
24
+ @state.alter({ loading: value })
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ require_relative '../prim'
2
+
3
+ module Primrose
4
+ module Components
5
+ class Checkbox < Rose
6
+ def initialize(label:, checked: false, id: nil, name: nil, js: nil)
7
+ @label = label
8
+ @checked = checked
9
+ @id = id || label.downcase.gsub(/\s+/, "_") # Use label as id if id is not provided
10
+ @name = name || @id # Use id as name if name is not provided
11
+ @js = js
12
+ super()
13
+ end
14
+
15
+ def render
16
+ Prim.render('templates/partials/_checkbox.prim.erb', self)
17
+ end
18
+
19
+ def set_checked(value)
20
+ @state.alter({ checked: value })
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ require_relative '../prim'
2
+
3
+ module Primrose
4
+ module Components
5
+ class Navbar < Rose
6
+ def initialize(*args, links:, active_link: nil, sticky: false, brand: nil)
7
+ # puts "Non-keyword args: #{args.inspect}"
8
+ # puts "Initializing Navbar with #{links.inspect}, #{active_link}, #{sticky}, #{brand}"
9
+ @links = links
10
+ @active_link = active_link
11
+ @sticky = sticky
12
+ @brand = brand
13
+ super()
14
+ end
15
+
16
+ def render
17
+ Prim.render('templates/components/navbar.prim.erb', self)
18
+ end
19
+
20
+ def set_active_link(new_active_link)
21
+ @state.alter({ active_link: new_active_link })
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ require_relative '../prim'
2
+
3
+ module Primrose
4
+ module Components
5
+ class TextField < Rose
6
+ def initialize(placeholder:, name: nil, label: nil, min_length: nil, max_length: nil, read_only: false, default_value: nil, style_class: nil)
7
+ @placeholder = placeholder
8
+ @name = name
9
+ @label = label
10
+ @min_length = min_length
11
+ @max_length = max_length
12
+ @read_only = read_only
13
+ @default_value = default_value
14
+ @style_class = style_class
15
+ super()
16
+ end
17
+
18
+ def render
19
+ Prim.render('templates/components/text_field.prim.erb', self)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ require_relative 'components/button'
2
+ require_relative 'components/navbar'
3
+ require_relative 'components/text_field'
4
+ require_relative 'components/checkbox'
5
+
6
+ module Primrose
7
+ module Helpers
8
+ # Load a component dynamically based on the name
9
+ def component(name, **args, &block)
10
+ klass = Object.const_get("Primrose::Components::#{name}")
11
+ begin
12
+ instance = klass.new(**args)
13
+ rescue ArgumentError => e
14
+ puts "Error while initializing #{klass}: #{e.message}"
15
+ puts "Backtrace: #{e.backtrace.join("\n")}"
16
+ raise e
17
+ end
18
+ block&.call(instance)
19
+ instance.render
20
+ end
21
+
22
+ # Specific helpers
23
+ def button(label:, action:, **opts)
24
+ component('Button', label: label, action: action, **opts)
25
+ end
26
+
27
+ def navbar(links:, **opts)
28
+ component('Navbar', links: links, **opts)
29
+ end
30
+
31
+ def text_field(placeholder:, **opts)
32
+ component('TextField', placeholder: placeholder, **opts)
33
+ end
34
+
35
+ def checkbox(label:, checked: false, **opts)
36
+ component('Checkbox', label: label, checked: checked, **opts)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,59 @@
1
+ require_relative 'utils/deep_clone'
2
+
3
+ module Primrose
4
+ class Observable
5
+ def initialize(value)
6
+ @value = value
7
+ @listeners = []
8
+ @previous_value = deep_clone(value)
9
+ @events = {}
10
+ end
11
+
12
+ def follow(&block)
13
+ @listeners << block
14
+ block.call(@value, @previous_value)
15
+ end
16
+
17
+ def on(event_name, &block)
18
+ (@events[event_name] ||= []) << block
19
+ end
20
+
21
+ def trigger(event_name, *args)
22
+ (@events[event_name] || []).each do |listener|
23
+ listener.call(*args)
24
+ end
25
+ end
26
+
27
+ # State Persistence
28
+ def save_state(key)
29
+ File.write("#{key}.json", @value.to_json)
30
+ end
31
+
32
+ def load_state(key)
33
+ if File.exist?("#{key}.json")
34
+ @value = JSON.parse(File.read("#{key}.json"))
35
+ notify_observers
36
+ end
37
+ end
38
+
39
+ def alter(new_value)
40
+ @previous_value = deep_clone(@value)
41
+ @value = new_value
42
+ notify_observers
43
+ end
44
+
45
+ def value
46
+ @value
47
+ end
48
+
49
+ private
50
+
51
+ def deep_clone(object)
52
+ Primrose::Utils.deep_clone(object)
53
+ end
54
+
55
+ def notify_observers
56
+ @listeners.each { |listener| listener.call(@value, @previous_value) }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,30 @@
1
+ require 'erb'
2
+ require 'logger'
3
+
4
+ module Primrose
5
+ class Prim
6
+ @template_cache = {}
7
+ @logger = Logger.new(STDOUT)
8
+
9
+ def self.render(template_path, context)
10
+ template = read_template(template_path)
11
+ erb = ERB.new(template)
12
+ rendered = erb.result(context.get_binding)
13
+
14
+ # replace partial tags with actual content
15
+ rendered.gsub(/<%= partial "(.*?)" %>/) do
16
+ partial_path = File.join('templates/partials', "#{$1}.prim.erb")
17
+ render(partial_path, context)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def self.read_template(template_path)
24
+ @template_cache[template_path] ||= File.read(template_path)
25
+ rescue => e
26
+ @logger.error("Could not read template: #{e.message}")
27
+ raise
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,90 @@
1
+ module Primrose
2
+ class Rose
3
+ attr_reader :state, :event_handlers, :children
4
+
5
+ def initialize
6
+ @state = Observable.new({})
7
+ @event_handlers = {}
8
+ @children = []
9
+ lifecycle(:initialize)
10
+ end
11
+
12
+ def lifecycle(method)
13
+ send(method) if respond_to?(method)
14
+ end
15
+
16
+ def render
17
+ begin
18
+ lifecycle(:before_render)
19
+ lifecycle(:after_render)
20
+ rescue StandardError => e
21
+ handle_error(e)
22
+ end
23
+ end
24
+
25
+ def on(event, &block)
26
+ @event_handlers[event] ||= []
27
+ @event_handlers[event] << block
28
+ end
29
+
30
+ def trigger(event, *args)
31
+ return unless @event_handlers[event]
32
+
33
+ @event_handlers[event].each do |handler|
34
+ handler.call(*args)
35
+ end
36
+ end
37
+
38
+ def add_child(child)
39
+ @children << child
40
+ end
41
+
42
+ def render_children
43
+ @children.map { |child| child.render }.join("\n")
44
+ end
45
+
46
+ # Error Handling
47
+ def handle_error(error)
48
+ puts "An error occurred: #{error.message}"
49
+ # Log to a file or send to monitoring service
50
+ # You can add more specific logic here based on the type of error
51
+ error.backtrace.each { |line| puts line }
52
+ end
53
+
54
+ # Setup Hook
55
+ def setup
56
+ # Placeholder for logic to run only once during the object's lifetime,
57
+ # prior to the `initialize` lifecycle method
58
+ end
59
+
60
+ # Additional Lifecycle Hooks
61
+ def before_mount
62
+ # Placeholder for logic to run before the component is added to the DOM
63
+ end
64
+
65
+ def after_mount
66
+ # Placeholder for logic to run after the component is added to the DOM
67
+ end
68
+
69
+ def before_unmount
70
+ # Placeholder for logic to run before the component is removed from the DOM
71
+ end
72
+
73
+ def after_unmount
74
+ # Placeholder for logic to run after the component is removed from the DOM
75
+ end
76
+
77
+ # Existing hooks
78
+ def before_render
79
+ # Placeholder for logic to run immediately before the render method
80
+ end
81
+
82
+ def after_render
83
+ # Placeholder for logic to run immediately after the render method
84
+ end
85
+
86
+ def get_binding
87
+ binding
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,17 @@
1
+ module Primrose
2
+ class Router
3
+ def initialize
4
+ @routes = {}
5
+ end
6
+
7
+ def route(path, &block)
8
+ @routes[path] = block
9
+ end
10
+
11
+ def navigate(path)
12
+ return unless @routes[path]
13
+
14
+ @routes[path].call
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module Primrose
2
+ class Store
3
+ attr_reader :state
4
+
5
+ def initialize(initial_state = {})
6
+ @state = Observable.new(initial_state)
7
+ end
8
+
9
+ def dispatch(action)
10
+ new_state = @state.value.dup # Start with a copy of the current state
11
+ puts "Dispatching action: #{action[:type]}"
12
+
13
+ action[:updates].each do |key, value|
14
+ puts "Updating #{key} with #{value}"
15
+ new_state[key] = value.call(new_state[key]) # Apply update function to corresponding value in state
16
+ puts "New value: #{new_state[key]}"
17
+ end
18
+
19
+ @state.alter(new_state) # Update state
20
+ puts "New state: #{@state}"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ module Primrose
2
+ module Utils
3
+ def self.deep_clone(object)
4
+ Marshal.load(Marshal.dump(object))
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module Primrose
2
+ VERSION = "0.0.1"
3
+ end
data/lib/primrose.rb ADDED
@@ -0,0 +1,7 @@
1
+ require_relative 'primrose/version'
2
+ require_relative 'primrose/observable'
3
+ require_relative 'primrose/rose'
4
+ require_relative 'primrose/store'
5
+ require_relative 'primrose/router'
6
+ require_relative 'primrose/prim'
7
+ require_relative 'primrose/helpers'
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: primrose
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Daniel M. Matongo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-10-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: erb
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ description: A gem for observable data structures and components
28
+ email: mmatongo_@hotmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - LICENSE
34
+ - README.md
35
+ - lib/primrose.rb
36
+ - lib/primrose/components/button.rb
37
+ - lib/primrose/components/checkbox.rb
38
+ - lib/primrose/components/navbar.rb
39
+ - lib/primrose/components/text_field.rb
40
+ - lib/primrose/helpers.rb
41
+ - lib/primrose/observable.rb
42
+ - lib/primrose/prim.rb
43
+ - lib/primrose/rose.rb
44
+ - lib/primrose/router.rb
45
+ - lib/primrose/store.rb
46
+ - lib/primrose/utils/deep_clone.rb
47
+ - lib/primrose/version.rb
48
+ homepage: https://github.com/mmatongo/primrose.rb
49
+ licenses:
50
+ - MIT
51
+ metadata: {}
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubygems_version: 3.4.21
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: A gem for observable data structures and components
71
+ test_files: []