leanweb 0.2.0 → 0.4.0
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 +4 -4
- data/bin/leanweb +15 -9
- data/contrib/lib/hawese.rb +76 -0
- data/contrib/lib/lean_mail.rb +125 -0
- data/contrib/lib/leanweb/controller_mixins/render_with_layout.rb +205 -0
- data/contrib/lib/rack/session/json_file.rb +84 -0
- data/lib/leanweb/app.rb +4 -4
- data/lib/leanweb/controller.rb +31 -11
- data/lib/leanweb/helpers.rb +26 -16
- data/lib/leanweb/route.rb +80 -38
- data/lib/leanweb/version.rb +13 -0
- data/lib/leanweb.rb +7 -9
- metadata +54 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9c2c8b263119a2bfb7a4a812f4ffeb66bc9444bfc65a5ea0ab99d627d227e307
|
|
4
|
+
data.tar.gz: bbb016308a12268ee78694e378e931c016475ca8cf9ff4264d017f79664911f3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 52c6e7bdd02a5c2797d20cd6979574cec8a4d8333ab659f3332fa6b4e3bce906a69f950124453621e12dee38c0c585ac3abace0a8041c54d444524c0a21bbba3
|
|
7
|
+
data.tar.gz: 64ec617d144264edce4922fa2258173f6cc139a20102a9ba4c4290adb6ec06d13be4b5f265af715fdcd82005ae03c66ac447c7dd79bc30dce0c3235d4a4f8e22
|
data/bin/leanweb
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
|
5
5
|
#
|
|
6
|
-
# This file is part of "LeanWeb" and licensed under the terms of the
|
|
7
|
-
# General Public License version
|
|
8
|
-
# should have received a copy of this
|
|
9
|
-
# see <https://
|
|
6
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
|
7
|
+
# General Public License version 3 with permissions for compatibility with the
|
|
8
|
+
# Hacktivista General Public License. You should have received a copy of this
|
|
9
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
|
10
10
|
|
|
11
11
|
require 'fileutils'
|
|
12
12
|
require 'leanweb'
|
|
@@ -16,15 +16,15 @@ require 'leanweb'
|
|
|
16
16
|
files = [
|
|
17
17
|
{
|
|
18
18
|
filename: 'Gemfile',
|
|
19
|
-
# TODO: Use {LeanWeb::VERSION} when v1 is reached.
|
|
20
19
|
content: <<~RUBY
|
|
21
20
|
# frozen_string_literal: true
|
|
22
21
|
|
|
23
22
|
source 'https://rubygems.org'
|
|
24
23
|
|
|
25
|
-
gem 'leanweb', '~>
|
|
26
|
-
gem 'haml'
|
|
24
|
+
gem 'leanweb', '~> #{LeanWeb::VERSION}'
|
|
25
|
+
gem 'haml', '~> 5.2.2'
|
|
27
26
|
gem 'rake'
|
|
27
|
+
gem 'puma'
|
|
28
28
|
RUBY
|
|
29
29
|
}, {
|
|
30
30
|
filename: 'config.ru',
|
|
@@ -55,7 +55,7 @@ files = [
|
|
|
55
55
|
|
|
56
56
|
require 'leanweb'
|
|
57
57
|
|
|
58
|
-
task default: %w[
|
|
58
|
+
task default: %w[build_static]
|
|
59
59
|
|
|
60
60
|
task :build_static do
|
|
61
61
|
require_relative 'routes'
|
|
@@ -63,7 +63,7 @@ files = [
|
|
|
63
63
|
end
|
|
64
64
|
RUBY
|
|
65
65
|
}, {
|
|
66
|
-
filename: 'src/controllers/
|
|
66
|
+
filename: 'src/controllers/main_controller.rb',
|
|
67
67
|
content: <<~RUBY
|
|
68
68
|
# frozen_string_literal: true
|
|
69
69
|
|
|
@@ -88,6 +88,12 @@ files = [
|
|
|
88
88
|
%body
|
|
89
89
|
%h1 It works!
|
|
90
90
|
HAML
|
|
91
|
+
}, {
|
|
92
|
+
filename: '.gitignore'
|
|
93
|
+
content: <<~GITIGNORE
|
|
94
|
+
/.bundle
|
|
95
|
+
/public/**/*.html
|
|
96
|
+
GITIGNORE
|
|
91
97
|
}
|
|
92
98
|
]
|
|
93
99
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
|
4
|
+
#
|
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
|
9
|
+
|
|
10
|
+
require 'json'
|
|
11
|
+
require 'net/http'
|
|
12
|
+
require 'openssl'
|
|
13
|
+
require 'tilt'
|
|
14
|
+
require 'uri'
|
|
15
|
+
|
|
16
|
+
# Hawese client.
|
|
17
|
+
#
|
|
18
|
+
# **Environment variables**
|
|
19
|
+
#
|
|
20
|
+
# - HAWESE_RETURN_URL defaults to `LEANWEB_ENDPOINT/checkout`.
|
|
21
|
+
# - HAWESE_ENDPOINT
|
|
22
|
+
# - HAWESE_ORIGIN
|
|
23
|
+
class Hawese
|
|
24
|
+
class << self
|
|
25
|
+
ENDPOINT = ENV.fetch('HAWESE_ENDPOINT')
|
|
26
|
+
RETURN_URL = ENV.fetch('HAWESE_RETURN_URL') do
|
|
27
|
+
"#{ENV.fetch('LEANWEB_ENDPOINT')}/checkout"
|
|
28
|
+
end
|
|
29
|
+
ORIGIN = ENV.fetch('HAWESE_ORIGIN')
|
|
30
|
+
|
|
31
|
+
def payment_methods(country = '*', currency = nil)
|
|
32
|
+
endpoint = String.new(
|
|
33
|
+
"#{ENDPOINT}/gateways/payment-methods/purchase?country=#{country}"
|
|
34
|
+
)
|
|
35
|
+
endpoint << "¤cy=#{currency}" if currency
|
|
36
|
+
uri = URI(endpoint)
|
|
37
|
+
JSON.parse(Net::HTTP.get(uri), symbolize_names: true)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def gateway_schema(gateway, query_params, schema = 'purchase')
|
|
41
|
+
uri = URI("#{ENDPOINT}/gateways/#{gateway}/schemas/#{schema}")
|
|
42
|
+
uri.query = URI.encode_www_form(query_params)
|
|
43
|
+
JSON.parse(Net::HTTP.get(uri), symbolize_names: true)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def purchase(gateway, fields)
|
|
47
|
+
fields[:origin] = ORIGIN
|
|
48
|
+
uri = URI("#{ENDPOINT}/gateways/#{gateway}/purchase")
|
|
49
|
+
uri.query = URI.encode_www_form(return_url: RETURN_URL)
|
|
50
|
+
response = Net::HTTP.post(
|
|
51
|
+
uri,
|
|
52
|
+
fields.to_json,
|
|
53
|
+
'Content-Type' => 'application/json'
|
|
54
|
+
)
|
|
55
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def purchase_from_schema(gateway, schema)
|
|
59
|
+
fields = {}
|
|
60
|
+
schema[:properties].each do |property, contents|
|
|
61
|
+
fields[property] = contents[:default] if contents.include?(:default)
|
|
62
|
+
end
|
|
63
|
+
purchase(gateway, fields)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def payment(uuid)
|
|
67
|
+
uri = URI("#{ENDPOINT}/payments/#{uuid}")
|
|
68
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
|
69
|
+
req = Net::HTTP::Get.new(uri)
|
|
70
|
+
req['Authorization'] = "Bearer #{ENV.fetch('HAWESE_AUTH_TOKEN')}"
|
|
71
|
+
response = http.request(req)
|
|
72
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
|
4
|
+
#
|
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
|
9
|
+
|
|
10
|
+
require 'net/smtp'
|
|
11
|
+
require 'securerandom'
|
|
12
|
+
require 'socket'
|
|
13
|
+
require 'time'
|
|
14
|
+
|
|
15
|
+
# Send an email with SMTP easily.
|
|
16
|
+
#
|
|
17
|
+
# Environment variables:
|
|
18
|
+
#
|
|
19
|
+
# - SMTP_HOST: Where to connect to.
|
|
20
|
+
# - SMTP_PORT: Port, optional (default: 25).
|
|
21
|
+
# - SMTP_SECURITY: `tls` or `starttls`, optional (default: `nil`).
|
|
22
|
+
# - SMTP_USER: User, optional.
|
|
23
|
+
# - SMTP_PASSWORD: Password, optional.
|
|
24
|
+
# - SMTP_FROM: In the format `Name <user@mail>` or `user@mail`.
|
|
25
|
+
#
|
|
26
|
+
# @example Single address
|
|
27
|
+
# LeanMail.deliver('to@mail', 'subject', 'body')
|
|
28
|
+
#
|
|
29
|
+
# @example Multiple addresses
|
|
30
|
+
# LeanMail.deliver(['to@mail', 'to2@mail'], 'subject', 'body')
|
|
31
|
+
module LeanMail
|
|
32
|
+
# RFC 2821 message data representation.
|
|
33
|
+
class Data
|
|
34
|
+
attr_reader :from, :to, :subject, :message
|
|
35
|
+
|
|
36
|
+
def initialize(from, to, subject, message)
|
|
37
|
+
@from = from
|
|
38
|
+
@to = to
|
|
39
|
+
@subject = subject
|
|
40
|
+
@message = message
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_s
|
|
44
|
+
<<~MAIL
|
|
45
|
+
From: #{@from}
|
|
46
|
+
To: #{to.instance_of?(Array) ? to.join(', ') : to}
|
|
47
|
+
Subject: #{subject}
|
|
48
|
+
Date: #{Time.new.rfc2822}
|
|
49
|
+
Message-Id: <#{SecureRandom.uuid}@#{Socket.gethostname}>
|
|
50
|
+
|
|
51
|
+
#{@message}
|
|
52
|
+
MAIL
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Email deliverer.
|
|
57
|
+
class Deliver
|
|
58
|
+
attr_reader :data
|
|
59
|
+
|
|
60
|
+
def initialize
|
|
61
|
+
@user = ENV.fetch('SMTP_USER', nil)
|
|
62
|
+
@password = ENV.fetch('SMTP_PASSWORD', nil)
|
|
63
|
+
@from = ENV.fetch('SMTP_FROM')
|
|
64
|
+
|
|
65
|
+
@host = ENV.fetch('SMTP_HOST')
|
|
66
|
+
@port = ENV.fetch('SMTP_PORT', 25)
|
|
67
|
+
@security = ENV.fetch('SMTP_SECURITY', nil)
|
|
68
|
+
|
|
69
|
+
initialize_smtp
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Send email.
|
|
73
|
+
#
|
|
74
|
+
# @param to [Array, String] In the format `Name <user@mail>` or `user@mail`.
|
|
75
|
+
# @param subject [String]
|
|
76
|
+
# @param body [String]
|
|
77
|
+
# @return [Deliver]
|
|
78
|
+
def call(to, subject, body)
|
|
79
|
+
@data = Data.new(@from, to, subject, body)
|
|
80
|
+
|
|
81
|
+
@smtp.start(Socket.gethostname, @user, @password, :plain) do |smtp|
|
|
82
|
+
smtp.send_message(@data.to_s, extract_addr(@from), extract_addrs(to))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
self
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
protected
|
|
89
|
+
|
|
90
|
+
def initialize_smtp
|
|
91
|
+
@smtp = Net::SMTP.new(@host, @port)
|
|
92
|
+
maybe_enable_security(@security)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def maybe_enable_security(security)
|
|
96
|
+
case security
|
|
97
|
+
when 'tls' then @smtp.enable_tls
|
|
98
|
+
when 'starttls' then @smtp.enable_starttls
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def extract_addr(str)
|
|
103
|
+
match = str.match(%r{<([^/]+)>})
|
|
104
|
+
return match[1] if match
|
|
105
|
+
|
|
106
|
+
str
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def extract_addrs(to)
|
|
110
|
+
return to.map{ |addr| extract_addr(addr) } if to.instance_of?(Array)
|
|
111
|
+
|
|
112
|
+
extract_addr(to)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Deliver email.
|
|
117
|
+
#
|
|
118
|
+
# @param to [Array, String] In the format `Name <user@mail>` or `user@mail`.
|
|
119
|
+
# @param subject [String]
|
|
120
|
+
# @param body [String]
|
|
121
|
+
# @return [Deliver]
|
|
122
|
+
def self.deliver(to, subject, body)
|
|
123
|
+
LeanMail::Deliver.new.call(to, subject, body)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
|
4
|
+
#
|
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
module LeanWeb
|
|
12
|
+
module ControllerMixins
|
|
13
|
+
# {render_with_layout} for {Controller} with ERB, HAML, Tilt::EmacsOrg and
|
|
14
|
+
# Redcarpet Markdown support.
|
|
15
|
+
module RenderWithLayout # rubocop:disable Metrics/ModuleLength
|
|
16
|
+
# Render a response with a layout.
|
|
17
|
+
#
|
|
18
|
+
# Depending on the `path` file contents and options `@content_for` might
|
|
19
|
+
# be filled. These contents must be inserted on the `head` or `body` of
|
|
20
|
+
# your layout file.
|
|
21
|
+
#
|
|
22
|
+
# All paths used on parameters can be relative to {VIEW_PATH} or absolute.
|
|
23
|
+
#
|
|
24
|
+
# @param path [String] Path to file to rendered.
|
|
25
|
+
# @param title [String] `@content_for[:title]` content.
|
|
26
|
+
# @param head [Hash] Things to be inserted on `@content_for[:head]`.
|
|
27
|
+
# @option head [String|Array] :js Javascript file path(s).
|
|
28
|
+
# @option head [String|Array] :jsm Javascript module file path (s).
|
|
29
|
+
# @option head [String|Array] :css CSS file path(s).
|
|
30
|
+
# @option head [String|Array] :raw Raw headers in HTML format.
|
|
31
|
+
# @param layout [String] Layout file path.
|
|
32
|
+
# @param sub_layout [String] Layout file to be inserted within layout.
|
|
33
|
+
# @param options [Hash] Options hash, to be given to the template engine
|
|
34
|
+
# based on file extension: `Tilt::EmacsOrg` for `.org` and `Redcarpet`
|
|
35
|
+
# for `.md`.
|
|
36
|
+
# @option options [String] :setupfile For `.org`.
|
|
37
|
+
# @option options [true] :toc For `.md`. To fill `@content_for[:toc]`
|
|
38
|
+
# render with option `:with_toc_data`.
|
|
39
|
+
# @yield On dynamic templates (such as Haml and ERB) you can yield.
|
|
40
|
+
def render_with_layout( # rubocop:disable Metrics/ParameterLists
|
|
41
|
+
path,
|
|
42
|
+
title: nil,
|
|
43
|
+
head: nil,
|
|
44
|
+
layout: 'layout.haml',
|
|
45
|
+
sub_layout: nil,
|
|
46
|
+
options: nil,
|
|
47
|
+
&block
|
|
48
|
+
)
|
|
49
|
+
@content_for[:title] = title unless title.nil?
|
|
50
|
+
content = render_by_extension(path, sub_layout, options, &block)
|
|
51
|
+
prepare_head(**head) unless head.nil?
|
|
52
|
+
render_response(layout, 'text/html'){ content }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Render response for missing static action methods. Called from
|
|
56
|
+
# {Route#respond}.
|
|
57
|
+
def default_static_action(view_path)
|
|
58
|
+
render_with_layout(view_path)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
protected
|
|
62
|
+
|
|
63
|
+
# @param path [String]
|
|
64
|
+
# @param layout [String]
|
|
65
|
+
# @param options [Hash] Check `Tilt::EmacsOrg` options.
|
|
66
|
+
# @option options [String] :setupfile can be absolute or relative to
|
|
67
|
+
# {VIEW_PATH}.
|
|
68
|
+
def render_org(path, layout: nil, options: {})
|
|
69
|
+
options[:setupfile] = absolute_view_path(options[:setupfile]) \
|
|
70
|
+
if options.include?(:setupfile)
|
|
71
|
+
|
|
72
|
+
org_template = create_template(path, options)
|
|
73
|
+
|
|
74
|
+
@content_for[:title] = org_template.title \
|
|
75
|
+
unless @content_for.include?(:title)
|
|
76
|
+
|
|
77
|
+
if layout
|
|
78
|
+
create_template(layout).render(self){ org_template.render(self) }
|
|
79
|
+
else
|
|
80
|
+
org_template.render(self)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @param path [String]
|
|
85
|
+
# @param layout [String]
|
|
86
|
+
# @param options [Hash] Check `RedCarpet` `render_options`.
|
|
87
|
+
# @option options [true] :toc To place an HTML based table of contents on
|
|
88
|
+
# `@content_for[:toc]` and render with option `:with_toc_data`.
|
|
89
|
+
def render_markdown(path, layout: nil, options: {})
|
|
90
|
+
maybe_render_markdown_toc!(path, options)
|
|
91
|
+
markdown_template = create_template(path, options)
|
|
92
|
+
|
|
93
|
+
@content_for[:title] = find_title('md', path) \
|
|
94
|
+
unless @content_for.include?(:title)
|
|
95
|
+
|
|
96
|
+
if layout
|
|
97
|
+
create_template(layout).render(self){ markdown_template.render(self) }
|
|
98
|
+
else
|
|
99
|
+
markdown_template.render(self)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# If `options[:toc]` is set, deletes options `:toc` and adds
|
|
104
|
+
# `:with_toc_data`. Also adds `@content_for[:toc]`.
|
|
105
|
+
def maybe_render_markdown_toc!(path, options)
|
|
106
|
+
return unless options.include?(:toc)
|
|
107
|
+
|
|
108
|
+
require('redcarpet')
|
|
109
|
+
@content_for[:toc] = create_template(
|
|
110
|
+
path, { renderer: Redcarpet::Render::HTML_TOC }
|
|
111
|
+
).render
|
|
112
|
+
|
|
113
|
+
options.delete(:toc)
|
|
114
|
+
options[:with_toc_data] = true
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def find_title(ext, path)
|
|
118
|
+
regex = find_title_ext_regex(ext)
|
|
119
|
+
title = nil
|
|
120
|
+
File.foreach(absolute_view_path(path)) do |line|
|
|
121
|
+
matches = line.match(regex)
|
|
122
|
+
(title = matches[1]) && break if matches
|
|
123
|
+
end
|
|
124
|
+
title
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def find_title_ext_regex(ext)
|
|
128
|
+
case ext
|
|
129
|
+
when 'md' then /#(?!#)\s?(.+)/
|
|
130
|
+
when 'haml' then /%h1[^\s]*\s(.+)/
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @param path [String]
|
|
135
|
+
# @param layout [String]
|
|
136
|
+
# @param options [Hash] Check `Haml` options.
|
|
137
|
+
# @yield If block given.
|
|
138
|
+
def render_other(path, layout: nil, options: {})
|
|
139
|
+
template = create_template(path, options)
|
|
140
|
+
if layout
|
|
141
|
+
create_template(layout).render(self) do
|
|
142
|
+
template.render(self){ yield if block_given? }
|
|
143
|
+
end
|
|
144
|
+
else
|
|
145
|
+
template.render(self){ yield if block_given? }
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# rubocop:disable Metrics/MethodLength
|
|
150
|
+
def render_by_extension(path, layout, options, &block)
|
|
151
|
+
ext = File.extname(path)[1..]
|
|
152
|
+
options = options || template_defaults[ext] || {}
|
|
153
|
+
|
|
154
|
+
case ext
|
|
155
|
+
when 'org'
|
|
156
|
+
return render_org(path, layout: layout, options: options)
|
|
157
|
+
when 'md'
|
|
158
|
+
return render_markdown(path, layout: layout, options: options)
|
|
159
|
+
when 'haml'
|
|
160
|
+
@content_for[:title] = find_title('haml', path) \
|
|
161
|
+
unless @content_for.include?(:title)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
render_other(path, layout: layout, options: options, &block)
|
|
165
|
+
end
|
|
166
|
+
# rubocop:enable Metrics/MethodLength
|
|
167
|
+
|
|
168
|
+
def prepare_head(js: nil, jsm: nil, css: nil, raw: nil)
|
|
169
|
+
head = String.new
|
|
170
|
+
head << prepare_head_js(js) unless js.nil?
|
|
171
|
+
head << prepare_head_js(jsm, 'module') unless jsm.nil?
|
|
172
|
+
head << prepare_head_css(css) unless css.nil?
|
|
173
|
+
head << prepare_head_raw(raw) unless raw.nil?
|
|
174
|
+
@content_for[:head] = head
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def prepare_head_js(js, type = 'application/javascript')
|
|
178
|
+
head = String.new
|
|
179
|
+
js = [js] if js.instance_of?(String)
|
|
180
|
+
js.each do |src|
|
|
181
|
+
head << "<script type='#{type}' src='#{src}'"
|
|
182
|
+
head << ' defer' if type != 'module'
|
|
183
|
+
head << "></script>\n"
|
|
184
|
+
end
|
|
185
|
+
head
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def prepare_head_css(css)
|
|
189
|
+
head = String.new
|
|
190
|
+
css = [css] if css.instance_of?(String)
|
|
191
|
+
css.each do |src|
|
|
192
|
+
head << "<link rel='stylesheet' type='text/css' href='#{src}'>\n"
|
|
193
|
+
end
|
|
194
|
+
head
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def prepare_head_raw(raw)
|
|
198
|
+
head = String.new
|
|
199
|
+
raw = [raw] if raw.instance_of?(String)
|
|
200
|
+
raw.each{ |src| head << "#{src}\n" }
|
|
201
|
+
head
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
|
4
|
+
#
|
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
|
9
|
+
|
|
10
|
+
require 'date'
|
|
11
|
+
require 'fileutils'
|
|
12
|
+
require 'json'
|
|
13
|
+
require 'rack/session/abstract/id'
|
|
14
|
+
|
|
15
|
+
module Rack
|
|
16
|
+
module Session
|
|
17
|
+
# A JSON File based session storage.
|
|
18
|
+
class JsonFile < Abstract::PersistedSecure
|
|
19
|
+
# Cleans session files that have not been used.
|
|
20
|
+
# @param to [Time|Date] Time from which file has not been accessed.
|
|
21
|
+
# Defaults to 00:00 hrs 1 month ago.
|
|
22
|
+
# @param dir [String] Directory from which to clean files. Defaults to
|
|
23
|
+
# `/tmp/rack.session`.
|
|
24
|
+
def self.clean(
|
|
25
|
+
to = Date.today.prev_month,
|
|
26
|
+
dir = "#{Dir.tmpdir}/rack.session"
|
|
27
|
+
)
|
|
28
|
+
to = to.to_time if to.instance_of?(Date)
|
|
29
|
+
Dir["#{dir}/*"].each do |file|
|
|
30
|
+
FileUtils.rm(file, force: true) if ::File.atime(file) < to
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param options [Hash] Accepts a :session_dir path which defaults to
|
|
35
|
+
# `/tmp/rack.session`.
|
|
36
|
+
def initialize(app, options = {})
|
|
37
|
+
super(
|
|
38
|
+
app,
|
|
39
|
+
{
|
|
40
|
+
secure: ENV['RACK_ENV'] != 'development', # HTTPS unless in dev env
|
|
41
|
+
same_site: 'Strict' # don't allow cross-site sessions
|
|
42
|
+
}.merge!(options)
|
|
43
|
+
)
|
|
44
|
+
@session_dir = "#{Dir.tmpdir}/#{@key}" || @default_options[:session_dir]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Array] [self, Hash].
|
|
48
|
+
def find_session(_req, sid)
|
|
49
|
+
sid = generate_sid if sid.nil?
|
|
50
|
+
|
|
51
|
+
[sid, JSON.parse(::File.read("#{@session_dir}/#{sid}"))]
|
|
52
|
+
rescue Errno::ENOENT
|
|
53
|
+
[generate_sid, {}]
|
|
54
|
+
rescue JSON::ParserError
|
|
55
|
+
[sid, {}]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @param sid [String] Only known ids are allowed, to avoid session
|
|
59
|
+
# fixation attacks.
|
|
60
|
+
# @return [self|false] session_id or false.
|
|
61
|
+
def write_session(_req, sid, session, _options)
|
|
62
|
+
return false unless ::File.exist?("#{@session_dir}/#{sid}")
|
|
63
|
+
|
|
64
|
+
::File.open("#{@session_dir}/#{sid}", 'w', 0o600) do |f|
|
|
65
|
+
f << JSON.generate(session)
|
|
66
|
+
end
|
|
67
|
+
sid
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [self|nil] new session id or nil if options[:drop].
|
|
71
|
+
def delete_session(_req, sid, options)
|
|
72
|
+
FileUtils.rm("#{@session_dir}/#{sid}", force: true)
|
|
73
|
+
options&.include?(:drop) ? nil : generate_sid
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def generate_sid(*)
|
|
77
|
+
sid = super
|
|
78
|
+
FileUtils.mkdir_p(@session_dir, mode: 0o700)
|
|
79
|
+
::File.new("#{@session_dir}/#{sid}", 'w', 0o600).close
|
|
80
|
+
sid
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/leanweb/app.rb
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
|
4
4
|
#
|
|
5
|
-
# This file is part of "LeanWeb" and licensed under the terms of the
|
|
6
|
-
# General Public License version
|
|
7
|
-
# should have received a copy of this
|
|
8
|
-
# see <https://
|
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
|
9
9
|
|
|
10
10
|
require 'rack'
|
|
11
11
|
|
data/lib/leanweb/controller.rb
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
|
4
4
|
#
|
|
5
|
-
# This file is part of "LeanWeb" and licensed under the terms of the
|
|
6
|
-
# General Public License version
|
|
7
|
-
# should have received a copy of this
|
|
8
|
-
# see <https://
|
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
|
9
9
|
|
|
10
10
|
require 'rack'
|
|
11
11
|
require 'tilt'
|
|
@@ -24,6 +24,7 @@ module LeanWeb
|
|
|
24
24
|
@route = route
|
|
25
25
|
@request = request
|
|
26
26
|
@response = Rack::Response.new(nil, 200)
|
|
27
|
+
@content_for = {}
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
# Render magic. Supports every file extension that Tilt supports. Defaults
|
|
@@ -58,6 +59,12 @@ module LeanWeb
|
|
|
58
59
|
Tilt[ext].new(path, 1, options || template_defaults[ext] || {})
|
|
59
60
|
end
|
|
60
61
|
|
|
62
|
+
# Render response for missing static action methods. Called from
|
|
63
|
+
# {Route#respond}.
|
|
64
|
+
def default_static_action(view_path)
|
|
65
|
+
render_response(view_path)
|
|
66
|
+
end
|
|
67
|
+
|
|
61
68
|
# Relative route to path from public directory considering current route.
|
|
62
69
|
#
|
|
63
70
|
# @param path [String] path from public directory, never begins with `/`.
|
|
@@ -69,9 +76,28 @@ module LeanWeb
|
|
|
69
76
|
@base_url + path
|
|
70
77
|
end
|
|
71
78
|
|
|
79
|
+
# Get absolute path for a file within {VIEW_PATH}.
|
|
80
|
+
# @param path [String] Can be:
|
|
81
|
+
# - A full path, starts with `/`.
|
|
82
|
+
# - A path relative to {VIEW_PATH}.
|
|
83
|
+
# - A path relative to current @route.path directory, starts with `./`.
|
|
84
|
+
# @return [String] Absolute path.
|
|
85
|
+
def absolute_view_path(path)
|
|
86
|
+
return path if path.start_with?('/')
|
|
87
|
+
|
|
88
|
+
view_path = String.new(LeanWeb::VIEW_PATH)
|
|
89
|
+
|
|
90
|
+
if path.start_with?('./')
|
|
91
|
+
view_path << @route.path.sub(%r{/[^/]*$}, '')
|
|
92
|
+
path = path[2..]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
path == '' ? view_path : "#{view_path}/#{path}"
|
|
96
|
+
end
|
|
97
|
+
|
|
72
98
|
# Request params.
|
|
73
99
|
def params
|
|
74
|
-
@request
|
|
100
|
+
@request&.params
|
|
75
101
|
end
|
|
76
102
|
|
|
77
103
|
protected
|
|
@@ -80,11 +106,5 @@ module LeanWeb
|
|
|
80
106
|
def template_defaults
|
|
81
107
|
{}
|
|
82
108
|
end
|
|
83
|
-
|
|
84
|
-
# @param path [String]
|
|
85
|
-
# @return [String] Full path.
|
|
86
|
-
def absolute_view_path(path)
|
|
87
|
-
path[0] == '/' ? path : "#{LeanWeb::VIEW_PATH}/#{path}"
|
|
88
|
-
end
|
|
89
109
|
end
|
|
90
110
|
end
|
data/lib/leanweb/helpers.rb
CHANGED
|
@@ -2,23 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
|
4
4
|
#
|
|
5
|
-
# This file is part of "LeanWeb" and licensed under the terms of the
|
|
6
|
-
# General Public License version
|
|
7
|
-
# should have received a copy of this
|
|
8
|
-
# see <https://
|
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
|
9
9
|
|
|
10
|
-
# String helpers.
|
|
11
|
-
class String
|
|
12
|
-
# String to PascalCase.
|
|
13
|
-
def pascalize
|
|
14
|
-
camelize(pascal: true)
|
|
15
|
-
end
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
module LeanWeb
|
|
12
|
+
# String refinements.
|
|
13
|
+
module StringRefinements
|
|
14
|
+
refine ::String do
|
|
15
|
+
# String to PascalCase.
|
|
16
|
+
def pascalize
|
|
17
|
+
camelize(pascal: true)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# String to camelCase.
|
|
21
|
+
# @param pascal [Boolean] If true first letter is uppercase.
|
|
22
|
+
def camelize(pascal: false)
|
|
23
|
+
str = gsub(/[-_\s]+(.?)/){ |match| match[1].upcase }
|
|
24
|
+
str[0] = pascal ? str[0].upcase : str[0].downcase
|
|
25
|
+
str
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# String to snake_case
|
|
29
|
+
def snakeize
|
|
30
|
+
gsub(/[[:upper:]]/){ |match| "_#{match.downcase}" }.delete_prefix('_')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
23
33
|
end
|
|
24
34
|
end
|
data/lib/leanweb/route.rb
CHANGED
|
@@ -2,20 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
|
4
4
|
#
|
|
5
|
-
# This file is part of "LeanWeb" and licensed under the terms of the
|
|
6
|
-
# General Public License version
|
|
7
|
-
# should have received a copy of this
|
|
8
|
-
# see <https://
|
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
|
9
9
|
|
|
10
10
|
require 'rack/mock'
|
|
11
11
|
|
|
12
12
|
module LeanWeb
|
|
13
13
|
# Action for {Route#action}.
|
|
14
|
-
Action = Struct.new(:
|
|
14
|
+
Action = Struct.new(:controller, :action, keyword_init: true)
|
|
15
15
|
|
|
16
16
|
# A single route which routes with the {#respond} method. It can also {#build}
|
|
17
17
|
# an static file.
|
|
18
18
|
class Route
|
|
19
|
+
using StringRefinements
|
|
20
|
+
|
|
19
21
|
attr_reader :path, :method, :action, :static
|
|
20
22
|
|
|
21
23
|
# A new instance of Route.
|
|
@@ -27,13 +29,13 @@ module LeanWeb
|
|
|
27
29
|
# @param action [Proc, Hash, String, nil] References a Method/Proc to be
|
|
28
30
|
# triggered. It can be:
|
|
29
31
|
#
|
|
30
|
-
# -
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# - A
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
32
|
+
# - Nothing, defaults to :"{#path_basename}_{#method}" (such as
|
|
33
|
+
# `:index_get`) on `:MainController`.
|
|
34
|
+
# - A full hash `{ controller: 'val', action: 'val' }`.
|
|
35
|
+
# - A hash with `{ Controller: :action }` only.
|
|
36
|
+
# - A simple string for an action on `:MainController`.
|
|
37
|
+
# - It can also be a `Proc`. Will be executed in a `:MainController`
|
|
38
|
+
# instance context.
|
|
37
39
|
#
|
|
38
40
|
# @param static [Boolean|Array] Defines a route as static. Defaults true for
|
|
39
41
|
# GET method, false otherwise. Set to `false` to say it can only work
|
|
@@ -61,12 +63,20 @@ module LeanWeb
|
|
|
61
63
|
str_path[-1] == '/' ? 'index' : File.basename(str_path)
|
|
62
64
|
end
|
|
63
65
|
|
|
66
|
+
# Respond with a proc, controller method, or in case of true static routes
|
|
67
|
+
# a rendering of {VIEW_PATH}/{path} with any file extension.
|
|
68
|
+
#
|
|
64
69
|
# @param request [Rack::Request]
|
|
65
70
|
# @return [Array] a valid rack response.
|
|
66
71
|
def respond(request)
|
|
67
72
|
return respond_proc(request) if @action.instance_of?(Proc)
|
|
68
73
|
|
|
69
74
|
respond_method(request)
|
|
75
|
+
rescue NoMethodError
|
|
76
|
+
raise unless @static == true && (view_path = guess_view_path)
|
|
77
|
+
|
|
78
|
+
controller = default_controller_class.new(self)
|
|
79
|
+
controller.default_static_action(view_path)
|
|
70
80
|
end
|
|
71
81
|
|
|
72
82
|
# String path, independent if {#path} is Regexp or String.
|
|
@@ -108,42 +118,52 @@ module LeanWeb
|
|
|
108
118
|
|
|
109
119
|
# Assign value to `@action`.
|
|
110
120
|
def action=(value)
|
|
111
|
-
@action =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
121
|
+
@action =
|
|
122
|
+
if value.instance_of?(Proc)
|
|
123
|
+
value
|
|
124
|
+
else
|
|
125
|
+
Action.new(**prepare_action_hash(value))
|
|
126
|
+
end
|
|
116
127
|
end
|
|
117
128
|
|
|
118
129
|
# @param src_value [Hash, String, nil] Check {#initialize} action param for
|
|
119
130
|
# valid input.
|
|
120
131
|
# @return [Hash] valid hash for {Action}.
|
|
121
|
-
def prepare_action_hash(src_value)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
else
|
|
126
|
-
value = {}
|
|
127
|
-
value[:file], value[:action] = src_value.first
|
|
128
|
-
end
|
|
129
|
-
rescue NoMethodError
|
|
130
|
-
value = { action: src_value }
|
|
131
|
-
end
|
|
132
|
-
value[:file] ||= 'main'
|
|
133
|
-
value[:controller] ||= "#{value[:file].pascalize}Controller"
|
|
134
|
-
value[:action] ||= default_action_name
|
|
132
|
+
def prepare_action_hash(src_value)
|
|
133
|
+
value = prepare_prepare_action_hash(src_value)
|
|
134
|
+
value[:controller] = value[:controller]&.to_sym || DEFAULT_CONTROLLER
|
|
135
|
+
value[:action] = value[:action]&.to_sym || default_action_action
|
|
135
136
|
value
|
|
136
137
|
end
|
|
137
138
|
|
|
138
|
-
def
|
|
139
|
-
|
|
139
|
+
def prepare_prepare_action_hash(src_value)
|
|
140
|
+
if %i[controller action].include?(src_value.keys.first)
|
|
141
|
+
src_value
|
|
142
|
+
else
|
|
143
|
+
value = {}
|
|
144
|
+
value[:controller], value[:action] = src_value.first
|
|
145
|
+
value
|
|
146
|
+
end
|
|
147
|
+
rescue NoMethodError
|
|
148
|
+
{ action: src_value }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def default_action_action
|
|
152
|
+
"#{path_basename.gsub(/[.-]/, '_')}_#{@method.downcase}".to_sym
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def default_controller_class
|
|
156
|
+
require_relative("#{CONTROLLER_PATH}/#{DEFAULT_CONTROLLER.to_s.snakeize}")
|
|
157
|
+
Object.const_get(DEFAULT_CONTROLLER)
|
|
158
|
+
rescue LoadError
|
|
159
|
+
Controller
|
|
140
160
|
end
|
|
141
161
|
|
|
142
162
|
# @param request [Rack::Request]
|
|
143
163
|
# @return [Array] a valid Rack response.
|
|
144
164
|
def respond_method(request)
|
|
145
165
|
params = action_params(request.path)
|
|
146
|
-
require_relative("#{
|
|
166
|
+
require_relative("#{CONTROLLER_PATH}/#{@action.controller.to_s.snakeize}")
|
|
147
167
|
controller = Object.const_get(@action.controller).new(self, request)
|
|
148
168
|
return controller.public_send(@action.action, **params) \
|
|
149
169
|
if params.instance_of?(Hash)
|
|
@@ -155,9 +175,11 @@ module LeanWeb
|
|
|
155
175
|
# @return [Array] a valid Rack response.
|
|
156
176
|
def respond_proc(request)
|
|
157
177
|
params = action_params(request.path)
|
|
158
|
-
|
|
178
|
+
controller = default_controller_class.new(self, request)
|
|
179
|
+
return controller.instance_exec(**params, &@action) \
|
|
180
|
+
if params.instance_of?(Hash)
|
|
159
181
|
|
|
160
|
-
|
|
182
|
+
controller.instance_exec(*params, &@action)
|
|
161
183
|
end
|
|
162
184
|
|
|
163
185
|
# @param request_path [String]
|
|
@@ -178,8 +200,28 @@ module LeanWeb
|
|
|
178
200
|
# @param content_type [String]
|
|
179
201
|
# @return [String] absolute route to path + extension based on content_type.
|
|
180
202
|
def output_path(path, content_type)
|
|
181
|
-
|
|
182
|
-
|
|
203
|
+
out_path =
|
|
204
|
+
if path[-1] == '/'
|
|
205
|
+
String.new("#{PUBLIC_PATH}#{path}index")
|
|
206
|
+
else
|
|
207
|
+
String.new("#{PUBLIC_PATH}#{path}")
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
unless path.end_with?(MEDIA_EXTENSIONS[content_type])
|
|
211
|
+
out_path << MEDIA_EXTENSIONS[content_type]
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
out_path
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def guess_view_path
|
|
218
|
+
return if @path.instance_of?(Regexp)
|
|
219
|
+
|
|
220
|
+
view_path = String.new("#{LeanWeb::VIEW_PATH}#{@path}")
|
|
221
|
+
view_path << 'index' if @path[-1] == '/' # add index if is index
|
|
222
|
+
view_path.sub!(%r{\.[^/]+$}, '') # drop static file extension if set
|
|
223
|
+
|
|
224
|
+
Dir["#{view_path}.*"].first # return first file match or nil
|
|
183
225
|
end
|
|
184
226
|
end
|
|
185
227
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
|
4
|
+
#
|
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
module LeanWeb
|
|
12
|
+
VERSION = '0.4.0'
|
|
13
|
+
end
|
data/lib/leanweb.rb
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
|
4
4
|
#
|
|
5
|
-
# This file is part of "LeanWeb" and licensed under the terms of the
|
|
6
|
-
# General Public License version
|
|
7
|
-
# should have received a copy of this
|
|
8
|
-
# see <https://
|
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
|
9
9
|
|
|
10
10
|
# LeanWeb is a minimal hybrid static / dynamic web framework.
|
|
11
11
|
module LeanWeb
|
|
12
|
-
|
|
12
|
+
require_relative 'leanweb/version'
|
|
13
13
|
|
|
14
14
|
ROOT_PATH = ENV.fetch('LEANWEB_ROOT_PATH', Dir.pwd)
|
|
15
15
|
CONTROLLER_PATH = "#{ROOT_PATH}/src/controllers"
|
|
@@ -26,13 +26,11 @@ module LeanWeb
|
|
|
26
26
|
'text/csv' => '.csv'
|
|
27
27
|
}.freeze
|
|
28
28
|
|
|
29
|
+
DEFAULT_CONTROLLER = :MainController
|
|
30
|
+
|
|
29
31
|
autoload :Route, 'leanweb/route.rb'
|
|
30
32
|
autoload :Controller, 'leanweb/controller.rb'
|
|
31
33
|
autoload :App, 'leanweb/app.rb'
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
require_relative 'leanweb/helpers'
|
|
35
|
-
|
|
36
|
-
# TODO: Remove if/when tilt-emacs_org gets merged on a Tilt release.
|
|
37
|
-
require 'tilt'
|
|
38
|
-
Tilt.register_lazy(:EmacsOrgTemplate, 'tilt/emacs_org', 'org')
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leanweb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Felix Freeman
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-
|
|
11
|
+
date: 2022-11-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rack
|
|
@@ -30,14 +30,14 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: 2.0.
|
|
33
|
+
version: 2.0.11
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: 2.0.
|
|
40
|
+
version: 2.0.11
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: haml
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -66,6 +66,20 @@ dependencies:
|
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: 5.16.2
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: minitest-sprint
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 1.2.2
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 1.2.2
|
|
69
83
|
- !ruby/object:Gem::Dependency
|
|
70
84
|
name: rake
|
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -122,6 +136,34 @@ dependencies:
|
|
|
122
136
|
- - "~>"
|
|
123
137
|
- !ruby/object:Gem::Version
|
|
124
138
|
version: 0.20.1
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: tilt-emacs_org
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - "~>"
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: 0.1.1
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: 0.1.1
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: webmock
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - "~>"
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '3.18'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - "~>"
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '3.18'
|
|
125
167
|
- !ruby/object:Gem::Dependency
|
|
126
168
|
name: yard
|
|
127
169
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -145,14 +187,19 @@ extensions: []
|
|
|
145
187
|
extra_rdoc_files: []
|
|
146
188
|
files:
|
|
147
189
|
- bin/leanweb
|
|
190
|
+
- contrib/lib/hawese.rb
|
|
191
|
+
- contrib/lib/lean_mail.rb
|
|
192
|
+
- contrib/lib/leanweb/controller_mixins/render_with_layout.rb
|
|
193
|
+
- contrib/lib/rack/session/json_file.rb
|
|
148
194
|
- lib/leanweb.rb
|
|
149
195
|
- lib/leanweb/app.rb
|
|
150
196
|
- lib/leanweb/controller.rb
|
|
151
197
|
- lib/leanweb/helpers.rb
|
|
152
198
|
- lib/leanweb/route.rb
|
|
199
|
+
- lib/leanweb/version.rb
|
|
153
200
|
homepage: https://leanweb.hacktivista.org
|
|
154
201
|
licenses:
|
|
155
|
-
-
|
|
202
|
+
- AGPL-3.0-only
|
|
156
203
|
metadata:
|
|
157
204
|
homepage_uri: https://leanweb.hacktivista.org
|
|
158
205
|
source_code_uri: https://git.hacktivista.org/leanweb
|
|
@@ -163,6 +210,7 @@ post_install_message:
|
|
|
163
210
|
rdoc_options: []
|
|
164
211
|
require_paths:
|
|
165
212
|
- lib
|
|
213
|
+
- contrib/lib
|
|
166
214
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
167
215
|
requirements:
|
|
168
216
|
- - ">="
|
|
@@ -174,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
174
222
|
- !ruby/object:Gem::Version
|
|
175
223
|
version: '0'
|
|
176
224
|
requirements: []
|
|
177
|
-
rubygems_version: 3.
|
|
225
|
+
rubygems_version: 3.3.23
|
|
178
226
|
signing_key:
|
|
179
227
|
specification_version: 4
|
|
180
228
|
summary: LeanWeb is a minimal hybrid static / dynamic web framework
|