jun 0.0.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +13 -0
  3. data/Gemfile +3 -1
  4. data/LICENSE.txt +1 -1
  5. data/README.md +18 -20
  6. data/Rakefile +13 -3
  7. data/bin/console +3 -9
  8. data/exe/jun +7 -0
  9. data/jun.gemspec +28 -18
  10. data/lib/jun/action_controller/base.rb +16 -0
  11. data/lib/jun/action_controller/callbacks.rb +46 -0
  12. data/lib/jun/action_controller/metal.rb +22 -0
  13. data/lib/jun/action_controller/redirecting.rb +17 -0
  14. data/lib/jun/action_controller/rendering.rb +84 -0
  15. data/lib/jun/action_dispatch/routing/mapper.rb +54 -0
  16. data/lib/jun/action_dispatch/routing/route_set.rb +112 -0
  17. data/lib/jun/action_dispatch/routing/welcome.html.erb +41 -0
  18. data/lib/jun/action_view/base.rb +26 -0
  19. data/lib/jun/action_view/helpers/url_helper.rb +13 -0
  20. data/lib/jun/action_view/helpers.rb +11 -0
  21. data/lib/jun/active_record/base.rb +52 -0
  22. data/lib/jun/active_record/migration.rb +76 -0
  23. data/lib/jun/active_record/migrator.rb +80 -0
  24. data/lib/jun/active_record/persistence.rb +60 -0
  25. data/lib/jun/active_record/relation.rb +42 -0
  26. data/lib/jun/active_support/core_ext/array/access.rb +27 -0
  27. data/lib/jun/active_support/core_ext/array/conversion.rb +10 -0
  28. data/lib/jun/active_support/core_ext/hash/transformation.rb +19 -0
  29. data/lib/jun/active_support/core_ext/string/access.rb +18 -0
  30. data/lib/jun/active_support/core_ext/string/inflector.rb +119 -0
  31. data/lib/jun/active_support/core_ext.rb +5 -0
  32. data/lib/jun/active_support/dependencies.rb +31 -0
  33. data/lib/jun/application.rb +45 -0
  34. data/lib/jun/cli/commands/base.rb +17 -0
  35. data/lib/jun/cli/commands/db/create.rb +35 -0
  36. data/lib/jun/cli/commands/db/drop.rb +18 -0
  37. data/lib/jun/cli/commands/db/migrate.rb +15 -0
  38. data/lib/jun/cli/commands/db/rollback.rb +15 -0
  39. data/lib/jun/cli/commands/db/schema/dump.rb +24 -0
  40. data/lib/jun/cli/commands/db/schema/load.rb +43 -0
  41. data/lib/jun/cli/commands/db/seed.rb +19 -0
  42. data/lib/jun/cli/commands/generate/migration.rb +27 -0
  43. data/lib/jun/cli/commands/new.rb +62 -0
  44. data/lib/jun/cli/commands/server.rb +17 -0
  45. data/lib/jun/cli/commands/version.rb +13 -0
  46. data/lib/jun/cli/generator_templates/migration.rb.erb +11 -0
  47. data/lib/jun/cli/generator_templates/new_app/Gemfile.erb +9 -0
  48. data/lib/jun/cli/generator_templates/new_app/README.md.erb +3 -0
  49. data/lib/jun/cli/generator_templates/new_app/app/controllers/application_controller.rb.erb +4 -0
  50. data/lib/jun/cli/generator_templates/new_app/app/helpers/application_helper.rb.erb +4 -0
  51. data/lib/jun/cli/generator_templates/new_app/app/models/application_record.rb.erb +4 -0
  52. data/lib/jun/cli/generator_templates/new_app/app/views/layouts/application.html.erb.erb +11 -0
  53. data/lib/jun/cli/generator_templates/new_app/bin/console.erb +8 -0
  54. data/lib/jun/cli/generator_templates/new_app/config/application.rb.erb +12 -0
  55. data/lib/jun/cli/generator_templates/new_app/config/environment.rb.erb +7 -0
  56. data/lib/jun/cli/generator_templates/new_app/config/routes.rb.erb +4 -0
  57. data/lib/jun/cli/generator_templates/new_app/config.ru.erb +6 -0
  58. data/lib/jun/cli/generator_templates/new_app/db/seeds.rb.erb +9 -0
  59. data/lib/jun/cli.rb +33 -0
  60. data/lib/jun/connection_adapters/sqlite_adapter.rb +19 -0
  61. data/lib/jun/version.rb +3 -1
  62. data/lib/jun.rb +50 -2
  63. metadata +129 -24
  64. data/.gitignore +0 -10
  65. data/.rspec +0 -2
  66. data/.travis.yml +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a0443a05754d92c8facf0a4497603884ed58833f
4
- data.tar.gz: ea40d29cd50879cb73bbdd3130ec84684980d097
2
+ SHA256:
3
+ metadata.gz: 70a404db6997b3aa8f1a682cfaac1d401e5a78aa2c6344645aed5819f9a7c617
4
+ data.tar.gz: 6ef4729b48e1f780a2004515c00a2ee6efbea77cade3522d942d05f602b509b8
5
5
  SHA512:
6
- metadata.gz: c282aabb2f3057321548c32de8058e65b13d433f885e71fc5a3ca405351cb609a8a98bc288162f0c8a67591ac2106d63c55d121cd6fdf6c3bc376e0d3f2001c5
7
- data.tar.gz: 8fdd848aad1a8cdafd410e0090b4b85a6a09f7b63e0dee7fbd072ed08890e0b2ceebd989a1aca5665458cda3e8a7ac0c2292c78cd0ee6f7ff236888f9ef604ed
6
+ metadata.gz: fa512bde19df4fb617e5d49f6336433cff2ed31ba3dcfba942ed4473b8136a943ff92afcf0fb0a9dcf3f9ebc795f796393e0340a23bbfec183f7987cab90b841
7
+ data.tar.gz: 5bf62a8d2e2cfaedfce5435b44bc54388f2792b48459b028436c4baf40c8971a9bb6a53c3e63eda0126087142da5742cee470cb69890c50c2d23af228f883cad
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
- source 'https://rubygems.org'
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
2
4
 
3
5
  gemspec
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2016 Zoran Pesic
3
+ Copyright (c) 2021 Zoran Pesic
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,37 +1,35 @@
1
1
  # Jun
2
2
 
3
- Jun is a simple web application framework inspired by Rails, built with the hopes of learning much more about Rails internals in the process.
3
+ [![Gem Version](https://badge.fury.io/rb/jun.svg)](https://badge.fury.io/rb/jun)
4
4
 
5
- ## Installation
5
+ Jun is a simple, [Rails](https://github.com/rails/rails)-inspired web application framework. This is a rough implementation, built with the goal of learning more about Rails internals. Not meant for production use.
6
6
 
7
- Add this line to your application's Gemfile:
7
+ ## Getting Started
8
8
 
9
- ```ruby
10
- gem 'jun'
11
- ```
9
+ Install the gem:
12
10
 
13
- And then execute:
11
+ ```
12
+ $ gem install jun
13
+ ```
14
14
 
15
- $ bundle
15
+ Then, create a new Jun application:
16
16
 
17
- Or install it yourself as:
17
+ ```
18
+ $ jun new my_app
19
+ ```
18
20
 
19
- $ gem install jun
21
+ Change directory into `my_app` and start up the server:
20
22
 
21
- ## Usage
23
+ ```
24
+ $ cd my_app
25
+ $ bin/jun server
26
+ ```
22
27
 
23
- TODO: Write usage instructions here
28
+ Visit `http://localhost:6291` to view your app.
24
29
 
25
30
  ## Development
26
31
 
27
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
28
-
29
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
30
-
31
- ## Contributing
32
-
33
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/jun.
34
-
32
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can run `bin/console` for an interactive prompt that will allow you to experiment.
35
33
 
36
34
  ## License
37
35
 
data/Rakefile CHANGED
@@ -1,6 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
3
13
 
4
- RSpec::Core::RakeTask.new(:spec)
14
+ RuboCop::RakeTask.new
5
15
 
6
- task :default => :spec
16
+ task default: %i[test rubocop]
data/bin/console CHANGED
@@ -1,14 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "bundler/setup"
5
+ require "pry"
4
6
  require "jun"
5
7
 
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start
8
+ Pry.start
data/exe/jun ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "jun"
5
+ require "jun/cli"
6
+
7
+ Jun::CLI.process_command(ARGV)
data/jun.gemspec CHANGED
@@ -1,25 +1,35 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'jun/version'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/jun/version"
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = "jun"
8
- spec.version = Jun::VERSION
9
- spec.authors = ["Zoran"]
10
- spec.email = ["zoran1991@gmail.com"]
6
+ spec.name = "jun"
7
+ spec.version = Jun::VERSION
8
+ spec.authors = ["Zoran"]
9
+ spec.email = ["zspesic@gmail.com"]
10
+
11
+ spec.summary = "A simple Ruby web framework."
12
+ spec.description = "A simple web framework inspired by Rails. Not meant for production use."
13
+ spec.homepage = "https://github.com/zokioki/jun"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
11
16
 
12
- spec.summary = %q{A simple Ruby web framework.}
13
- spec.description = %q{A simple web framework inspired by Rails, built with the hopes of learning more about Rails internals in the process.}
14
- spec.homepage = "https://github.com/zokioki/jun"
15
- spec.license = "MIT"
17
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
18
+ `git ls-files -z`.split("\x0").reject do |f|
19
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
20
+ end
21
+ end
16
22
 
17
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
- spec.bindir = "exe"
19
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
20
25
  spec.require_paths = ["lib"]
21
26
 
22
- spec.add_development_dependency "bundler", "~> 1.11"
23
- spec.add_development_dependency "rake", "~> 10.0"
24
- spec.add_development_dependency "rspec", "~> 3.0"
27
+ spec.add_runtime_dependency "rack", "~> 2.2"
28
+ spec.add_runtime_dependency "tilt", "~> 2.0"
29
+ spec.add_runtime_dependency "sqlite3", "~> 1.4"
30
+
31
+ spec.add_development_dependency "rake", "~> 13.0"
32
+ spec.add_development_dependency "minitest", "~> 5.0"
33
+ spec.add_development_dependency "rubocop", "~> 1.21"
34
+ spec.add_development_dependency "pry", "~> 0.14"
25
35
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "metal"
4
+ require_relative "callbacks"
5
+ require_relative "rendering"
6
+ require_relative "redirecting"
7
+
8
+ module Jun
9
+ module ActionController
10
+ class Base < Metal
11
+ include Jun::ActionController::Callbacks
12
+ include Jun::ActionController::Rendering
13
+ include Jun::ActionController::Redirecting
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jun
4
+ module ActionController
5
+ module Callbacks
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ def handle_response(action)
11
+ self.class.before_actions.each do |callback|
12
+ callback.call(self) if callback.match?(action)
13
+ end
14
+
15
+ super
16
+ end
17
+
18
+ module ClassMethods
19
+ def before_action(method_name, options = {})
20
+ before_actions << Callback.new(method_name, options)
21
+ end
22
+
23
+ def before_actions
24
+ @before_actions ||= []
25
+ end
26
+ end
27
+
28
+ class Callback
29
+ def initialize(method_name, options)
30
+ @method_name = method_name
31
+ @options = options
32
+ end
33
+
34
+ def match?(action)
35
+ return true unless @options[:only]&.any?
36
+
37
+ @options[:only].include?(action.to_sym)
38
+ end
39
+
40
+ def call(controller)
41
+ controller.send(@method_name)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jun
4
+ module ActionController
5
+ class Metal
6
+ attr_accessor :request, :response
7
+
8
+ def handle_response(action)
9
+ public_send(action)
10
+ render(action) unless response_rendered?
11
+
12
+ response
13
+ end
14
+
15
+ private
16
+
17
+ def response_rendered?
18
+ !!@_response_rendered
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jun
4
+ module ActionController
5
+ module Redirecting
6
+ def redirect_to(location, options = {})
7
+ return if response_rendered?
8
+
9
+ response.location = location
10
+ response.status = options[:status] || 302
11
+ response.write("<html><body>You are being <a href=\"#{response.location}\">redirected</a>.</body></html>")
12
+
13
+ @_response_rendered = true
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tilt/erb"
4
+ require "json"
5
+
6
+ require_relative "../action_view/base"
7
+
8
+ module Jun
9
+ module ActionController
10
+ module Rendering
11
+ def render(options, extra_options = {})
12
+ return if response_rendered?
13
+
14
+ template_name = nil
15
+ if options.is_a?(Symbol) || options.is_a?(String)
16
+ template_name = options
17
+ options = extra_options
18
+ else
19
+ template_name = options[:template]
20
+ end
21
+
22
+ options[:layout] = true unless options.key?(:layout)
23
+
24
+ if content_type = options[:content_type]
25
+ response.content_type = content_type.to_s
26
+ end
27
+
28
+ if status = options[:status]
29
+ response.status = status
30
+ end
31
+
32
+ if template_name
33
+ filepath = views_path.join("#{template_name}.html.erb")
34
+ template = Tilt::ERBTemplate.new(filepath)
35
+ context = Jun::ActionView::Base.new(self)
36
+ body = template.render(context, options[:locals])
37
+
38
+ if options[:layout]
39
+ layout_name = options[:layout].is_a?(String) ? options[:layout] : "application"
40
+ layout_filepath = layouts_path.join("#{layout_name}.html.erb")
41
+ layout_template = Tilt::ERBTemplate.new(layout_filepath)
42
+
43
+ body = layout_template.render(context) { body }
44
+ end
45
+
46
+ response.write(body)
47
+ response.content_type ||= "text/html"
48
+ elsif options[:text]
49
+ response.write(options[:text])
50
+ response.content_type ||= "text/plain"
51
+ elsif options[:json]
52
+ json = options[:json].is_a?(String) ? options[:json] : JSON.generate(options[:json])
53
+ response.write(json)
54
+ response.content_type ||= "application/json"
55
+ elsif options[:nothing]
56
+ response.write("")
57
+ response.content_type ||= "text/plain"
58
+ end
59
+
60
+ @_response_rendered = true
61
+ end
62
+
63
+ def view_assigns
64
+ reserved_variables = %i[@request @response @_response_rendered]
65
+ variables = instance_variables - reserved_variables
66
+
67
+ variables.reduce({}) do |object, name|
68
+ object.merge(name[1..-1] => instance_variable_get(name))
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def views_path
75
+ dirname = self.class.name.sub(/Controller\z/, "").underscore
76
+ Jun.root.join("app/views/#{dirname}")
77
+ end
78
+
79
+ def layouts_path
80
+ Jun.root.join("app/views/layouts")
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jun
4
+ module ActionDispatch
5
+ module Routing
6
+ class Mapper
7
+ def initialize(route_set)
8
+ @route_set = route_set
9
+ end
10
+
11
+ def get(path, to:, as: nil)
12
+ add_route(:get, path, to: to, as: as)
13
+ end
14
+
15
+ def post(path, to:, as: nil)
16
+ add_route(:post, path, to: to, as: as)
17
+ end
18
+
19
+ def patch(path, to:, as: nil)
20
+ add_route(:patch, path, to: to, as: as)
21
+ end
22
+
23
+ def delete(path, to:, as: nil)
24
+ add_route(:delete, path, to: to, as: as)
25
+ end
26
+
27
+ def root(to:)
28
+ get "/", to: to, as: "root"
29
+ end
30
+
31
+ def resources(plural_name)
32
+ get "/#{plural_name}", to: "#{plural_name}#index", as: plural_name.to_s
33
+ get "/#{plural_name}/new", to: "#{plural_name}#new", as: "new_#{plural_name.to_s.singularize}"
34
+ post "/#{plural_name}", to: "#{plural_name}#create"
35
+ get "/#{plural_name}/:id", to: "#{plural_name}#show", as: plural_name.to_s.singularize
36
+ get "/#{plural_name}/:id/edit", to: "#{plural_name}#edit", as: "edit_#{plural_name.to_s.singularize}"
37
+ patch "/#{plural_name}/:id", to: "#{plural_name}#update"
38
+ delete "/#{plural_name}/:id", to: "#{plural_name}#destroy"
39
+ end
40
+
41
+ private
42
+
43
+ def add_route(method, path, to:, as: nil)
44
+ method = method.to_s.upcase
45
+ controller, action = to.split("#")
46
+ path = path.to_s.start_with?("/") ? path.to_s : "/#{path}"
47
+ as ||= path.sub("/", "")
48
+
49
+ @route_set.add_route(method, path, controller, action, as)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "mapper"
4
+
5
+ module Jun
6
+ module ActionDispatch
7
+ module Routing
8
+ class Route < Struct.new(:method, :path, :controller, :action, :name)
9
+ def match?(request)
10
+ request.request_method == method && path_regex.match?(request.path_info)
11
+ end
12
+
13
+ def dispatch(request)
14
+ controller = controller_class.new
15
+ controller.request = request
16
+ controller.response = Rack::Response.new
17
+ controller.handle_response(action)
18
+ controller.response.finish
19
+ end
20
+
21
+ def controller_class
22
+ class_name = "#{controller.camelize}Controller"
23
+ Object.const_get(class_name)
24
+ end
25
+
26
+ def path_regex
27
+ path_string_for_regex = path.gsub(/:\w+/) { |match| "(?<#{match.delete(":")}>\\w+)" }
28
+ Regexp.new("^#{path_string_for_regex}\/?$")
29
+ end
30
+ end
31
+
32
+ class RouteSet
33
+ def initialize
34
+ @routes = []
35
+ end
36
+
37
+ def call(env)
38
+ return welcome_response if @routes.none?
39
+
40
+ request = Rack::Request.new(env)
41
+
42
+ if route = find_route(request)
43
+ route.dispatch(request)
44
+ else
45
+ not_found_response
46
+ end
47
+ end
48
+
49
+ def add_route(*args)
50
+ route = Route.new(*args)
51
+ @routes.push(route)
52
+ define_url_helper(route)
53
+
54
+ route
55
+ end
56
+
57
+ def find_route(request)
58
+ @routes.detect { |route| route.match?(request) }
59
+ end
60
+
61
+ def draw(&block)
62
+ mapper = Jun::ActionDispatch::Routing::Mapper.new(self)
63
+ mapper.instance_eval(&block)
64
+ end
65
+
66
+ def url_helpers
67
+ @url_helpers ||= Module.new.extend(url_helpers_module).include(url_helpers_module)
68
+ end
69
+
70
+ private
71
+
72
+ def url_helpers_module
73
+ @url_helpers_module ||= Module.new
74
+ end
75
+
76
+ def define_url_helper(route)
77
+ path_name = "#{route.name}_path"
78
+
79
+ url_helpers_module.define_method(path_name) do |*args|
80
+ options = args.last.is_a?(Hash) ? args.pop : {}
81
+ path = route.path
82
+ path_tokens = path.scan(/:\w+/)
83
+
84
+ path_tokens.each.with_index do |token, index|
85
+ path.sub!(token, args[index])
86
+ end
87
+
88
+ path
89
+ end
90
+ end
91
+
92
+ def not_found_response
93
+ response = Rack::Response.new
94
+ response.content_type = "text/plain"
95
+ response.status = 404
96
+ response.write("Not found")
97
+ response.finish
98
+ end
99
+
100
+ def welcome_response
101
+ template_filepath = File.expand_path("welcome.html.erb", __dir__)
102
+ template = Tilt::ERBTemplate.new(template_filepath)
103
+
104
+ response = Rack::Response.new
105
+ response.content_type = "text/html"
106
+ response.write(template.render)
107
+ response.finish
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,41 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Welcome to Jun!</title>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+
8
+ <style>
9
+ body {
10
+ font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
11
+ background-color: #fff3f3;
12
+ color: #7d3737;
13
+ }
14
+
15
+ ::selection {
16
+ background-color: #ffbdb6;
17
+ }
18
+
19
+ .root {
20
+ margin-top: 4rem;
21
+ text-align: center;
22
+ }
23
+
24
+ .sunspot {
25
+ width: 100px;
26
+ height: 100px;
27
+ border-radius: 50%;
28
+ background-color: #fc7e70;
29
+ margin: 2rem auto;
30
+ }
31
+ </style>
32
+ </head>
33
+
34
+ <body>
35
+ <div class="root">
36
+ <div class="sunspot"></div>
37
+ <h1>Welcome to Jun!</h1>
38
+ <small>Jun v<%= Jun::VERSION %> | Ruby v<%= RUBY_VERSION %> (<%= RUBY_PLATFORM %>)</small>
39
+ </div>
40
+ </body>
41
+ </html>
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "helpers"
4
+
5
+ module Jun
6
+ module ActionView
7
+ class Base
8
+ include Jun::ActionView::Helpers
9
+
10
+ attr_reader :controller
11
+
12
+ def initialize(controller)
13
+ @controller = controller
14
+ assign_instance_variables
15
+ end
16
+
17
+ private
18
+
19
+ def assign_instance_variables
20
+ controller.view_assigns.each do |name, value|
21
+ instance_variable_set("@#{name}", value)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jun
4
+ module ActionView
5
+ module Helpers
6
+ module UrlHelper
7
+ def link_to(title, url, options = {})
8
+ %Q{<a href="#{url}"#{ "class=\"#{options[:class]}\"" if options[:class] }>#{title}</a>}
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "helpers/url_helper"
4
+
5
+ module Jun
6
+ module ActionView
7
+ module Helpers
8
+ include Jun::ActionView::Helpers::UrlHelper
9
+ end
10
+ end
11
+ end