aris 1.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +342 -0
  4. data/lib/aris/adapters/base.rb +25 -0
  5. data/lib/aris/adapters/joys_integration.rb +94 -0
  6. data/lib/aris/adapters/mock/adapter.rb +141 -0
  7. data/lib/aris/adapters/mock/request.rb +81 -0
  8. data/lib/aris/adapters/mock/response.rb +17 -0
  9. data/lib/aris/adapters/rack/adapter.rb +117 -0
  10. data/lib/aris/adapters/rack/request.rb +66 -0
  11. data/lib/aris/adapters/rack/response.rb +16 -0
  12. data/lib/aris/core.rb +931 -0
  13. data/lib/aris/discovery.rb +312 -0
  14. data/lib/aris/locale_injector.rb +39 -0
  15. data/lib/aris/pipeline_runner.rb +100 -0
  16. data/lib/aris/plugins/api_key_auth.rb +61 -0
  17. data/lib/aris/plugins/basic_auth.rb +68 -0
  18. data/lib/aris/plugins/bearer_auth.rb +64 -0
  19. data/lib/aris/plugins/cache.rb +120 -0
  20. data/lib/aris/plugins/compression.rb +96 -0
  21. data/lib/aris/plugins/cookies.rb +46 -0
  22. data/lib/aris/plugins/cors.rb +81 -0
  23. data/lib/aris/plugins/csrf.rb +48 -0
  24. data/lib/aris/plugins/etag.rb +90 -0
  25. data/lib/aris/plugins/flash.rb +124 -0
  26. data/lib/aris/plugins/form_parser.rb +46 -0
  27. data/lib/aris/plugins/health_check.rb +62 -0
  28. data/lib/aris/plugins/json.rb +32 -0
  29. data/lib/aris/plugins/multipart.rb +160 -0
  30. data/lib/aris/plugins/rate_limiter.rb +60 -0
  31. data/lib/aris/plugins/request_id.rb +38 -0
  32. data/lib/aris/plugins/request_logger.rb +43 -0
  33. data/lib/aris/plugins/security_headers.rb +99 -0
  34. data/lib/aris/plugins/session.rb +175 -0
  35. data/lib/aris/plugins.rb +23 -0
  36. data/lib/aris/response_helpers.rb +156 -0
  37. data/lib/aris/route_helpers.rb +141 -0
  38. data/lib/aris/utils/redirects.rb +44 -0
  39. data/lib/aris/utils/sitemap.rb +84 -0
  40. data/lib/aris/version.rb +3 -0
  41. data/lib/aris.rb +35 -0
  42. metadata +151 -0
@@ -0,0 +1,156 @@
1
+ # lib/aris/response_helpers.rb
2
+ require 'json'
3
+
4
+ module Aris
5
+ module ResponseHelpers
6
+ # JSON response
7
+ def json(data, status: 200)
8
+ self.status = status
9
+ self.headers['content-type'] = 'application/json'
10
+ self.body = [data.to_json]
11
+ self
12
+ end
13
+
14
+ # HTML response
15
+ def html(content, status: 200)
16
+ self.status = status
17
+ self.headers['content-type'] = 'text/html; charset=utf-8'
18
+ self.body = [content.to_s]
19
+ self
20
+ end
21
+
22
+
23
+ def negotiate(format_preference, status: 200, &block)
24
+ format_to_use = case format_preference.to_s
25
+ when 'json', 'application/json' then :json
26
+ when 'xml', 'application/xml' then :xml
27
+ when 'html', 'text/html' then :html
28
+ else :json
29
+ end
30
+
31
+ content = block.call(format_to_use) if block_given?
32
+
33
+ case format_to_use
34
+ when :json
35
+ if content.is_a?(String) && content.start_with?('{', '[')
36
+ self.status = status
37
+ self.headers['content-type'] = 'application/json'
38
+ self.body = [content]
39
+ else
40
+ json(content || {}, status: status)
41
+ end
42
+ when :xml
43
+ xml(content || '', status: status)
44
+ when :html
45
+ html(content || '', status: status)
46
+ end
47
+
48
+ self
49
+ end
50
+
51
+ # Plain text response
52
+ def text(content, status: 200)
53
+ self.status = status
54
+ self.headers['content-type'] = 'text/plain; charset=utf-8'
55
+ self.body = [content.to_s]
56
+ self
57
+ end
58
+
59
+ # Redirect to URL
60
+ def redirect(url, status: 302)
61
+ self.status = status
62
+ self.headers['location'] = url
63
+ self.headers['content-type'] = 'text/plain; charset=utf-8'
64
+ self.body = ["Redirecting to #{url}"]
65
+ self
66
+ end
67
+
68
+ # Redirect to named route
69
+ def redirect_to(route_name, status: 302, **params)
70
+ # Get domain from Thread context (set by adapter)
71
+ domain = Thread.current[:aris_current_domain]
72
+ path = Aris.path(domain, route_name, **params)
73
+ redirect(path, status: status)
74
+ end
75
+
76
+ # No content response
77
+ def no_content
78
+ self.status = 204
79
+ self.headers.delete('content-type')
80
+ self.body = []
81
+ self
82
+ end
83
+
84
+ # XML response
85
+ def xml(data, status: 200)
86
+ self.status = status
87
+ self.headers['content-type'] = 'application/xml; charset=utf-8'
88
+ self.body = [data.to_s]
89
+ self
90
+ end
91
+
92
+ # Send file
93
+ def send_file(file_path, filename: nil, type: nil, disposition: 'attachment')
94
+ unless File.exist?(file_path)
95
+ raise ArgumentError, "File not found: #{file_path}"
96
+ end
97
+
98
+ self.status = 200
99
+
100
+ # Determine content type
101
+ content_type = type || detect_content_type(file_path)
102
+ self.headers['content-type'] = content_type
103
+
104
+ # Set filename
105
+ download_filename = filename || File.basename(file_path)
106
+ self.headers['content-disposition'] = "#{disposition}; filename=\"#{download_filename}\""
107
+
108
+ # Set content length
109
+ self.headers['content-length'] = File.size(file_path).to_s
110
+
111
+ # Read file
112
+ self.body = [File.read(file_path)]
113
+
114
+ self
115
+ end
116
+
117
+ private
118
+
119
+ def detect_content_type(file_path)
120
+ # Handle Tempfile paths that may have random extensions
121
+ ext = File.extname(file_path).downcase
122
+ ext = ext.split('.').last if ext.include?('.')
123
+ ext = ".#{ext}" unless ext.start_with?('.')
124
+
125
+ MIME_TYPES[ext] || 'application/octet-stream'
126
+ end
127
+
128
+ MIME_TYPES = {
129
+ '.html' => 'text/html',
130
+ '.htm' => 'text/html',
131
+ '.txt' => 'text/plain',
132
+ '.css' => 'text/css',
133
+ '.js' => 'application/javascript',
134
+ '.json' => 'application/json',
135
+ '.xml' => 'application/xml',
136
+ '.pdf' => 'application/pdf',
137
+ '.zip' => 'application/zip',
138
+ '.tar' => 'application/x-tar',
139
+ '.gz' => 'application/gzip',
140
+ '.jpg' => 'image/jpeg',
141
+ '.jpeg' => 'image/jpeg',
142
+ '.png' => 'image/png',
143
+ '.gif' => 'image/gif',
144
+ '.svg' => 'image/svg+xml',
145
+ '.webp' => 'image/webp',
146
+ '.ico' => 'image/x-icon',
147
+ '.mp3' => 'audio/mpeg',
148
+ '.mp4' => 'video/mp4',
149
+ '.webm' => 'video/webm',
150
+ '.woff' => 'font/woff',
151
+ '.woff2' => 'font/woff2',
152
+ '.ttf' => 'font/ttf',
153
+ '.otf' => 'font/otf'
154
+ }.freeze
155
+ end
156
+ end
@@ -0,0 +1,141 @@
1
+ # lib/aris/route_helpers.rb
2
+ module Aris
3
+ module RouteHelpers
4
+ # Declare localized paths (for file discovery)
5
+ # @example
6
+ # localized es: 'acerca', en: 'about'
7
+ def localized(**paths)
8
+ @_localized_paths = paths
9
+ end
10
+
11
+ def localized_paths
12
+ @_localized_paths || {}
13
+ end
14
+
15
+ # Load localized data - SUPPORTS MULTIPLE FORMATS (.rb, .json, .yml)
16
+ # @param locale [Symbol] The locale to load data for (:es, :en, etc.)
17
+ # @return [Hash] The parsed data for the locale
18
+ # @raise [Aris::Router::LocaleError] If no data file found for locale
19
+ def load_localized_data(locale)
20
+ base_path = File.dirname(caller_locations.first.path)
21
+
22
+ # Try multiple formats in priority order
23
+ [
24
+ ["data_#{locale}.rb", :ruby],
25
+ ["data_#{locale}.json", :json],
26
+ ["data_#{locale}.yml", :yaml],
27
+ ["data_#{locale}.yaml", :yaml],
28
+ ["data/#{locale}.json", :json],
29
+ ["data/#{locale}.yml", :yaml],
30
+ ["data/#{locale}.yaml", :yaml]
31
+ ].each do |filename, format|
32
+ file_path = File.join(base_path, filename)
33
+ next unless File.exist?(file_path)
34
+
35
+ return case format
36
+ when :ruby
37
+ # Ruby file just returns a hash
38
+ eval(File.read(file_path), binding, file_path)
39
+ when :json
40
+ require 'json'
41
+ JSON.parse(File.read(file_path), symbolize_names: true)
42
+ when :yaml
43
+ require 'yaml'
44
+ # Ruby 2.6+ compatibility
45
+ if YAML.respond_to?(:load_file)
46
+ if RUBY_VERSION >= '2.6'
47
+ YAML.load_file(file_path, symbolize_names: true)
48
+ else
49
+ YAML.load_file(file_path)
50
+ end
51
+ else
52
+ YAML.load(File.read(file_path))
53
+ end
54
+ end
55
+ end
56
+
57
+ raise Aris::Router::LocaleError,
58
+ "No data file found for locale :#{locale} in #{base_path}"
59
+ end
60
+
61
+ # Load template file from handler directory
62
+ # @param name [String] Template filename (default: 'template.html')
63
+ # @return [String] The template content
64
+ # @raise [Aris::Router::LocaleError] If template not found
65
+ def load_template(name = 'template.html')
66
+ file_path = File.join(
67
+ File.dirname(caller_locations.first.path),
68
+ name
69
+ )
70
+
71
+ if File.exist?(file_path)
72
+ File.read(file_path)
73
+ else
74
+ raise Aris::Router::LocaleError, "Template not found: #{file_path}"
75
+ end
76
+ end
77
+
78
+ # Render template with data using specified engine
79
+ # @param template_name [String] Name of template file
80
+ # @param data [Hash] Data to interpolate into template
81
+ # @param engine [Symbol] Template engine (:simple or :erb)
82
+ # @return [String] Rendered template
83
+ # @raise [Aris::Router::LocaleError] If template not found or engine unknown
84
+ #
85
+ # NOTE: This is a convenience helper. Handlers can use any template engine.
86
+ def render_template(template_name, data, engine: :simple)
87
+ template_path = File.join(
88
+ File.dirname(caller_locations.first.path),
89
+ template_name
90
+ )
91
+ template = File.read(template_path)
92
+
93
+ case engine
94
+ when :erb
95
+ require 'erb'
96
+ # Ruby 2.6+ uses result_with_hash, earlier versions need binding
97
+ if ERB.instance_method(:result).parameters.any?
98
+ ERB.new(template).result(binding)
99
+ else
100
+ # For ERB that supports result_with_hash (Ruby 2.5+)
101
+ begin
102
+ ERB.new(template).result_with_hash(data)
103
+ rescue NoMethodError
104
+ # Fallback for older Ruby
105
+ ERB.new(template).result(binding)
106
+ end
107
+ end
108
+ when :simple
109
+ # Simple {{key}} interpolation for basic cases
110
+ template.gsub(/\{\{(\w+)\}\}/) { data[$1.to_sym] || data[$1.to_s] }
111
+ else
112
+ raise Aris::Router::LocaleError, "Unknown template engine: #{engine}"
113
+ end
114
+ rescue Errno::ENOENT
115
+ raise Aris::Router::LocaleError, "Template not found: #{template_path}"
116
+ end
117
+
118
+ # Declare sitemap metadata (existing method)
119
+ def sitemap(priority: 0.5, changefreq: 'monthly', lastmod: nil, &dynamic)
120
+ @_sitemap_metadata = {
121
+ priority: priority,
122
+ changefreq: changefreq,
123
+ lastmod: lastmod,
124
+ dynamic: dynamic
125
+ }
126
+ end
127
+
128
+ def sitemap_metadata
129
+ @_sitemap_metadata
130
+ end
131
+
132
+ # Declare redirect sources (existing method)
133
+ def redirects_from(*paths, status: 301)
134
+ @_redirect_paths = { paths: paths, status: status }
135
+ end
136
+
137
+ def redirect_metadata
138
+ @_redirect_paths
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,44 @@
1
+ # lib/aris/utils/redirects.rb
2
+ module Aris
3
+ module Utils
4
+ module Redirects
5
+ class << self
6
+ attr_reader :redirects
7
+
8
+ def reset!
9
+ @redirects = {}
10
+ end
11
+
12
+ def register(from_paths:, to_path:, status: 301)
13
+ @redirects ||= {}
14
+ Array(from_paths).each do |from_path|
15
+ @redirects[from_path] = { to: to_path, status: status }
16
+ end
17
+ end
18
+
19
+ def find(path)
20
+ @redirects&.[](path)
21
+ end
22
+
23
+ def all
24
+ @redirects || {}
25
+ end
26
+ end
27
+
28
+ reset!
29
+ end
30
+ end
31
+ end
32
+
33
+ # Extend RouteHelpers
34
+ module Aris
35
+ module RouteHelpers
36
+ def redirects_from(*paths, status: 301)
37
+ @_redirect_paths = { paths: paths, status: status }
38
+ end
39
+
40
+ def redirect_metadata
41
+ @_redirect_paths
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,84 @@
1
+ # lib/aris/utils/sitemap.rb
2
+ module Aris
3
+ module Utils
4
+ module Sitemap
5
+ class << self
6
+ attr_reader :routes
7
+
8
+ def reset!
9
+ @routes = []
10
+ end
11
+
12
+ def register(domain:, path:, method:, metadata:)
13
+ @routes ||= []
14
+ @routes << {
15
+ domain: domain,
16
+ path: path,
17
+ method: method,
18
+ metadata: metadata
19
+ }
20
+ end
21
+
22
+ def generate(base_url:, domain: nil)
23
+ xml = ['<?xml version="1.0" encoding="UTF-8"?>']
24
+ xml << '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
25
+
26
+ sitemap_routes(domain).each do |route|
27
+ route[:urls].each do |url_data|
28
+ xml << ' <url>'
29
+ xml << " <loc>#{base_url}#{url_data[:path]}</loc>"
30
+ xml << " <lastmod>#{url_data[:lastmod]}</lastmod>" if url_data[:lastmod]
31
+ xml << " <changefreq>#{url_data[:changefreq]}</changefreq>"
32
+ xml << " <priority>#{url_data[:priority]}</priority>"
33
+ xml << ' </url>'
34
+ end
35
+ end
36
+
37
+ xml << '</urlset>'
38
+ xml.join("\n")
39
+ end
40
+
41
+ private
42
+
43
+ def sitemap_routes(domain_filter = nil)
44
+ (@routes || []).select { |r|
45
+ r[:metadata] && (domain_filter.nil? || r[:domain] == domain_filter)
46
+ }.map { |route|
47
+ urls = if route[:metadata][:dynamic]
48
+ route[:metadata][:dynamic].call
49
+ else
50
+ [{
51
+ path: route[:path],
52
+ priority: route[:metadata][:priority] || 0.5,
53
+ changefreq: route[:metadata][:changefreq] || 'monthly',
54
+ lastmod: route[:metadata][:lastmod]
55
+ }]
56
+ end
57
+
58
+ route.merge(urls: urls)
59
+ }.sort_by { |r| -(r[:urls].first[:priority] rescue 0.5) }
60
+ end
61
+ end
62
+
63
+ reset!
64
+ end
65
+ end
66
+ end
67
+
68
+ # Extend RouteHelpers
69
+ module Aris
70
+ module RouteHelpers
71
+ def sitemap(priority: 0.5, changefreq: 'monthly', lastmod: nil, &dynamic)
72
+ @_sitemap_metadata = {
73
+ priority: priority,
74
+ changefreq: changefreq,
75
+ lastmod: lastmod,
76
+ dynamic: dynamic
77
+ }
78
+ end
79
+
80
+ def sitemap_metadata
81
+ @_sitemap_metadata
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,3 @@
1
+ module Aris
2
+ VERSION = "1.3.0"
3
+ end
data/lib/aris.rb ADDED
@@ -0,0 +1,35 @@
1
+ # lib/aris.rb
2
+ # Main Aris loader - requires all components in correct order
3
+
4
+ # Core functionality
5
+ require_relative 'aris/version'
6
+ require_relative 'aris/core'
7
+ require_relative 'aris/pipeline_runner'
8
+ require_relative 'aris/locale_injector'
9
+ require_relative 'aris/route_helpers'
10
+ require_relative 'aris/response_helpers'
11
+ require_relative 'aris/discovery'
12
+ require_relative 'aris/plugins'
13
+
14
+ # Adapters
15
+ require_relative 'aris/adapters/base'
16
+ require_relative 'aris/adapters/joys_integration'
17
+ require_relative 'aris/adapters/rack/response'
18
+ require_relative 'aris/adapters/rack/request'
19
+ require_relative 'aris/adapters/rack/adapter'
20
+ require_relative 'aris/adapters/mock/response'
21
+ require_relative 'aris/adapters/mock/request'
22
+ require_relative 'aris/adapters/mock/adapter'
23
+
24
+ # Utils (if available)
25
+ begin
26
+ require_relative 'aris/utils/sitemap'
27
+ require_relative 'aris/utils/redirects'
28
+ rescue LoadError
29
+ # Utils not available, that's ok
30
+ end
31
+
32
+ # Plugins (optional)
33
+ Dir[File.join(__dir__, 'aris/plugins/**/*.rb')].sort.each do |file|
34
+ require file
35
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aris
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Steven Garcia
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rack
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: benchmark-ips
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: benchmark-memory
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.2'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.2'
82
+ description: Aris is a lightweight, high-performance web framework for Ruby with powerful
83
+ routing, middleware pipeline, and seamless integrations.
84
+ email:
85
+ - stevendgarcia@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - LICENSE.txt
91
+ - README.md
92
+ - lib/aris.rb
93
+ - lib/aris/adapters/base.rb
94
+ - lib/aris/adapters/joys_integration.rb
95
+ - lib/aris/adapters/mock/adapter.rb
96
+ - lib/aris/adapters/mock/request.rb
97
+ - lib/aris/adapters/mock/response.rb
98
+ - lib/aris/adapters/rack/adapter.rb
99
+ - lib/aris/adapters/rack/request.rb
100
+ - lib/aris/adapters/rack/response.rb
101
+ - lib/aris/core.rb
102
+ - lib/aris/discovery.rb
103
+ - lib/aris/locale_injector.rb
104
+ - lib/aris/pipeline_runner.rb
105
+ - lib/aris/plugins.rb
106
+ - lib/aris/plugins/api_key_auth.rb
107
+ - lib/aris/plugins/basic_auth.rb
108
+ - lib/aris/plugins/bearer_auth.rb
109
+ - lib/aris/plugins/cache.rb
110
+ - lib/aris/plugins/compression.rb
111
+ - lib/aris/plugins/cookies.rb
112
+ - lib/aris/plugins/cors.rb
113
+ - lib/aris/plugins/csrf.rb
114
+ - lib/aris/plugins/etag.rb
115
+ - lib/aris/plugins/flash.rb
116
+ - lib/aris/plugins/form_parser.rb
117
+ - lib/aris/plugins/health_check.rb
118
+ - lib/aris/plugins/json.rb
119
+ - lib/aris/plugins/multipart.rb
120
+ - lib/aris/plugins/rate_limiter.rb
121
+ - lib/aris/plugins/request_id.rb
122
+ - lib/aris/plugins/request_logger.rb
123
+ - lib/aris/plugins/security_headers.rb
124
+ - lib/aris/plugins/session.rb
125
+ - lib/aris/response_helpers.rb
126
+ - lib/aris/route_helpers.rb
127
+ - lib/aris/utils/redirects.rb
128
+ - lib/aris/utils/sitemap.rb
129
+ - lib/aris/version.rb
130
+ homepage: https://github.com/activestylus/aris
131
+ licenses:
132
+ - MIT
133
+ metadata: {}
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: 3.0.0
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubygems_version: 3.6.9
149
+ specification_version: 4
150
+ summary: Fast, elegant Ruby web framework
151
+ test_files: []