primrose 0.0.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.
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: []