leanweb 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da54199f1863128e9bb23baf728dff6be60123b9cf44947370293222816da122
4
- data.tar.gz: 60e11d76704347858cdffbde24d17b5d0edf1061ea91ef158dd5a6928e48bc4c
3
+ metadata.gz: d4e80c5813858b838b39fea56bb96b2fb28f7749d22f02ab04694662a8595d96
4
+ data.tar.gz: c2fdc8ebf92d647516db11352e5a6cb7bc36f1489dfabe69c095e7090f1b7970
5
5
  SHA512:
6
- metadata.gz: a3b3abccd00de5d76a06513c021b409d19073c911f3d236b7c7412c42e723bad9e616514a02e278e55271d5faf5709f94fb283af6fa93aa873e2144ff0d3211b
7
- data.tar.gz: a366dce05e7a8108e7b3ca4096112a3567e8082a0934e64d323401a938dceb24bfdc6c03dd53193827800b04a05c1c4c9ab53189cc45453e7925422dfb58535b
6
+ metadata.gz: 1bed8458a93a2f0139609f17fe3402f69065182607b6b6664cc0c6d90ab3815f4dca7cd08c165dab17e5448d9c3f7d3c44ef8e1f28468cd3e4c42a66b096c8b8
7
+ data.tar.gz: 320c619aedd70c9bd0ed5e698ef456e293d3cf754bbe3ca939a7669b45ad117a371089db15737edf0c5c271ad638b84784e6ee3a730093a82a16843828a1074c
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 Hacktivista
7
- # General Public License version 0.1 or (at your option) any later version. You
8
- # should have received a copy of this license along with the software. If not,
9
- # see <https://hacktivista.org/licenses/>.
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', '~> 0.2'
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[build]
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/main.rb',
66
+ filename: 'src/controllers/main_controller.rb',
67
67
  content: <<~RUBY
68
68
  # frozen_string_literal: true
69
69
 
@@ -0,0 +1,62 @@
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
32
+ uri = URI("#{ENDPOINT}/gateways/payment-methods/purchase")
33
+ JSON.parse(Net::HTTP.get(uri))
34
+ end
35
+
36
+ def gateway_schema(gateway, query_params, schema = 'purchase')
37
+ uri = URI("#{ENDPOINT}/gateways/#{gateway}/schemas/#{schema}")
38
+ uri.query = URI.encode_www_form(query_params)
39
+ JSON.parse(Net::HTTP.get(uri))
40
+ end
41
+
42
+ def purchase(gateway, fields)
43
+ fields[:origin] = ORIGIN
44
+ uri = URI("#{ENDPOINT}/gateways/#{gateway}/purchase")
45
+ uri.query = URI.encode_www_form(return_url: RETURN_URL)
46
+ response = Net::HTTP.post(
47
+ uri,
48
+ fields.to_json,
49
+ 'Content-Type' => 'application/json'
50
+ )
51
+ JSON.parse(response.body)
52
+ end
53
+
54
+ def purchase_from_schema(gateway, schema)
55
+ fields = {}
56
+ schema['properties'].each do |property, contents|
57
+ fields[property] = contents['default'] if contents.include?('default')
58
+ end
59
+ purchase(gateway, fields)
60
+ end
61
+ end
62
+ 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,170 @@
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
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: {},
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
+ protected
56
+
57
+ # @param path [String]
58
+ # @param layout [String]
59
+ # @param options [Hash] Check `Tilt::EmacsOrg` options.
60
+ # @option options [String] :setupfile can be absolute or relative to
61
+ # {VIEW_PATH}.
62
+ def render_org(path, layout: nil, options: {})
63
+ options[:setupfile] = "#{VIEW_PATH}/#{options[:setupfile]}" \
64
+ if options.include?(:setupfile) && options[:setupfile][0] != '/'
65
+
66
+ org_template = create_template(path, options)
67
+
68
+ @content_for[:title] = org_template.title \
69
+ unless @content_for.include?(:title)
70
+
71
+ if layout
72
+ create_template(layout).render(self){ org_template.render(self) }
73
+ else
74
+ org_template.render(self)
75
+ end
76
+ end
77
+
78
+ # @param path [String]
79
+ # @param layout [String]
80
+ # @param options [Hash] Check `RedCarpet` `render_options`.
81
+ # @option options [true] :toc To place an HTML based table of contents on
82
+ # `@content_for[:toc]` and render with option `:with_toc_data`.
83
+ def render_markdown(path, layout: nil, options: {})
84
+ maybe_render_markdown_toc!(path, options)
85
+ markdown_template = create_template(path, options)
86
+ if layout
87
+ create_template(layout).render(self){ markdown_template.render(self) }
88
+ else
89
+ markdown_template.render(self)
90
+ end
91
+ end
92
+
93
+ # If `options[:toc]` is set, deletes options `:toc` and adds
94
+ # `:with_toc_data`. Also adds `@content_for[:toc]`.
95
+ def maybe_render_markdown_toc!(path, options)
96
+ return unless options.include?(:toc)
97
+
98
+ require('redcarpet')
99
+ @content_for[:toc] = create_template(
100
+ path, { renderer: Redcarpet::Render::HTML_TOC }
101
+ ).render
102
+
103
+ options.delete(:toc)
104
+ options[:with_toc_data] = true
105
+ end
106
+
107
+ # @param path [String]
108
+ # @param layout [String]
109
+ # @param options [Hash] Check `Haml` options.
110
+ # @yield If block given.
111
+ def render_other(path, layout: nil, options: {})
112
+ template = create_template(path, options)
113
+ if layout
114
+ create_template(layout).render(self) do
115
+ template.render(self){ yield if block_given? }
116
+ end
117
+ else
118
+ template.render(self){ yield if block_given? }
119
+ end
120
+ end
121
+
122
+ def render_by_extension(path, layout, options, &block)
123
+ case File.extname(path)
124
+ when '.org'
125
+ render_org(path, layout: layout, options: options)
126
+ when '.md'
127
+ render_markdown(path, layout: layout, options: options)
128
+ else
129
+ render_other(path, layout: layout, options: options, &block)
130
+ end
131
+ end
132
+
133
+ def prepare_head(js: nil, jsm: nil, css: nil, raw: nil)
134
+ head = String.new
135
+ head << prepare_head_js(js) unless js.nil?
136
+ head << prepare_head_js(jsm, 'module') unless jsm.nil?
137
+ head << prepare_head_css(css) unless css.nil?
138
+ head << prepare_head_raw(raw) unless raw.nil?
139
+ @content_for[:head] = head
140
+ end
141
+
142
+ def prepare_head_js(js, type = 'application/javascript')
143
+ head = String.new
144
+ js = [js] if js.instance_of?(String)
145
+ js.each do |src|
146
+ head << "<script type='#{type}' src='#{src}'"
147
+ head << ' defer' if type != 'module'
148
+ head << "></script>\n"
149
+ end
150
+ head
151
+ end
152
+
153
+ def prepare_head_css(css)
154
+ head = String.new
155
+ css = [css] if css.instance_of?(String)
156
+ css.each do |src|
157
+ head << "<link rel='stylesheet' type='text/css' href='#{src}'>\n"
158
+ end
159
+ head
160
+ end
161
+
162
+ def prepare_head_raw(raw)
163
+ head = String.new
164
+ raw = [raw] if raw.instance_of?(String)
165
+ raw.each{ |src| head << "#{src}\n" }
166
+ head
167
+ end
168
+ end
169
+ end
170
+ 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 Hacktivista
6
- # General Public License version 0.1 or (at your option) any later version. You
7
- # should have received a copy of this license along with the software. If not,
8
- # see <https://hacktivista.org/licenses/>.
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
 
@@ -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 Hacktivista
6
- # General Public License version 0.1 or (at your option) any later version. You
7
- # should have received a copy of this license along with the software. If not,
8
- # see <https://hacktivista.org/licenses/>.
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
@@ -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 Hacktivista
6
- # General Public License version 0.1 or (at your option) any later version. You
7
- # should have received a copy of this license along with the software. If not,
8
- # see <https://hacktivista.org/licenses/>.
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
- # String to camelCase.
18
- # @param pascal [Boolean] If true first letter is uppercase.
19
- def camelize(pascal: false)
20
- str = gsub(/[-_\s]+(.?)/){ |match| match[1].upcase }
21
- str[0] = pascal ? str[0].upcase : str[0].downcase
22
- str
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 Hacktivista
6
- # General Public License version 0.1 or (at your option) any later version. You
7
- # should have received a copy of this license along with the software. If not,
8
- # see <https://hacktivista.org/licenses/>.
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(:file, :controller, :action, keyword_init: true)
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
- # - A full hash `{ file: 'val', controller: 'val', action: 'val' }`.
31
- # - A hash with `{ 'file' => 'action' }` only (which has a controller
32
- # class name `{File}Controller`).
33
- # - A simple string (which will consider file `main.rb` and controller
34
- # `MainController`). Defaults to "{#path_basename}_{#method}", (ex:
35
- # `index_get`).
36
- # - It can also be a `Proc`.
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
@@ -108,42 +110,45 @@ module LeanWeb
108
110
 
109
111
  # Assign value to `@action`.
110
112
  def action=(value)
111
- @action = if value.instance_of?(Proc)
112
- value
113
- else
114
- Action.new(**prepare_action_hash(value))
115
- end
113
+ @action =
114
+ if value.instance_of?(Proc)
115
+ value
116
+ else
117
+ Action.new(**prepare_action_hash(value))
118
+ end
116
119
  end
117
120
 
118
121
  # @param src_value [Hash, String, nil] Check {#initialize} action param for
119
122
  # valid input.
120
123
  # @return [Hash] valid hash for {Action}.
121
- def prepare_action_hash(src_value) # rubocop:disable Metrics/MethodLength
122
- begin
123
- if %i[file controller action].include?(src_value.keys.first)
124
- value = src_value
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
124
+ def prepare_action_hash(src_value)
125
+ value = prepare_prepare_action_hash(src_value)
126
+ value[:controller] = value[:controller]&.to_sym || DEFAULT_CONTROLLER
127
+ value[:action] = value[:action]&.to_sym || default_action_action
135
128
  value
136
129
  end
137
130
 
138
- def default_action_name
139
- "#{path_basename.gsub('-', '_')}_#{@method.downcase}"
131
+ def prepare_prepare_action_hash(src_value)
132
+ if %i[controller action].include?(src_value.keys.first)
133
+ src_value
134
+ else
135
+ value = {}
136
+ value[:controller], value[:action] = src_value.first
137
+ value
138
+ end
139
+ rescue NoMethodError
140
+ { action: src_value }
141
+ end
142
+
143
+ def default_action_action
144
+ "#{path_basename.gsub('-', '_')}_#{@method.downcase}".to_sym
140
145
  end
141
146
 
142
147
  # @param request [Rack::Request]
143
148
  # @return [Array] a valid Rack response.
144
149
  def respond_method(request)
145
150
  params = action_params(request.path)
146
- require_relative("#{LeanWeb::CONTROLLER_PATH}/#{@action.file}")
151
+ require_relative("#{CONTROLLER_PATH}/#{@action.controller.to_s.snakeize}")
147
152
  controller = Object.const_get(@action.controller).new(self, request)
148
153
  return controller.public_send(@action.action, **params) \
149
154
  if params.instance_of?(Hash)
@@ -155,9 +160,12 @@ module LeanWeb
155
160
  # @return [Array] a valid Rack response.
156
161
  def respond_proc(request)
157
162
  params = action_params(request.path)
158
- return @action.call(**params) if params.instance_of?(Hash)
163
+ require_relative("#{CONTROLLER_PATH}/#{DEFAULT_CONTROLLER.to_s.snakeize}")
164
+ controller = Object.const_get(DEFAULT_CONTROLLER).new(self, request)
165
+ return controller.instance_exec(**params, &@action) \
166
+ if params.instance_of?(Hash)
159
167
 
160
- @action.call(*params)
168
+ controller.instance_exec(*params, &@action)
161
169
  end
162
170
 
163
171
  # @param request_path [String]
@@ -178,8 +186,18 @@ module LeanWeb
178
186
  # @param content_type [String]
179
187
  # @return [String] absolute route to path + extension based on content_type.
180
188
  def output_path(path, content_type)
181
- path += 'index' if path[-1] == '/'
182
- "#{LeanWeb::PUBLIC_PATH}#{path}#{LeanWeb::MEDIA_EXTENSIONS[content_type]}"
189
+ out_path =
190
+ if path[-1] == '/'
191
+ String.new("#{PUBLIC_PATH}#{path}/index")
192
+ else
193
+ String.new("#{PUBLIC_PATH}#{path}")
194
+ end
195
+
196
+ unless path.end_with?(MEDIA_EXTENSIONS[content_type])
197
+ out_path << MEDIA_EXTENSIONS[content_type]
198
+ end
199
+
200
+ out_path
183
201
  end
184
202
  end
185
203
  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.3.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 Hacktivista
6
- # General Public License version 0.1 or (at your option) any later version. You
7
- # should have received a copy of this license along with the software. If not,
8
- # see <https://hacktivista.org/licenses/>.
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
- VERSION = '0.2.0'
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.2.0
4
+ version: 0.3.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-08-01 00:00:00.000000000 Z
11
+ date: 2022-10-14 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.10
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.10
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
- - LicenseRef-LICENSE
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.2.3
225
+ rubygems_version: 3.3.21
178
226
  signing_key:
179
227
  specification_version: 4
180
228
  summary: LeanWeb is a minimal hybrid static / dynamic web framework