jun 0.0.1 → 0.1.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 +5 -5
- data/.rubocop.yml +13 -0
- data/Gemfile +3 -1
- data/LICENSE.txt +1 -1
- data/README.md +4 -9
- data/Rakefile +13 -3
- data/bin/console +3 -9
- data/exe/jun +7 -0
- data/jun.gemspec +28 -18
- data/lib/jun/action_controller/base.rb +20 -0
- data/lib/jun/action_controller/rendering.rb +88 -0
- data/lib/jun/action_dispatch/routing/mapper.rb +29 -0
- data/lib/jun/action_dispatch/routing/route_set.rb +65 -0
- data/lib/jun/action_view/base.rb +26 -0
- data/lib/jun/action_view/helpers/url_helper.rb +13 -0
- data/lib/jun/action_view/helpers.rb +11 -0
- data/lib/jun/active_record.rb +41 -0
- data/lib/jun/active_support/dependencies.rb +31 -0
- data/lib/jun/active_support/inflector.rb +119 -0
- data/lib/jun/application.rb +18 -0
- data/lib/jun/cli/commands/base.rb +17 -0
- data/lib/jun/cli/commands/db/create.rb +15 -0
- data/lib/jun/cli/commands/db/drop.rb +15 -0
- data/lib/jun/cli/commands/db/migrate.rb +15 -0
- data/lib/jun/cli/commands/new.rb +57 -0
- data/lib/jun/cli/commands/server.rb +17 -0
- data/lib/jun/cli/commands/version.rb +13 -0
- data/lib/jun/cli/generators/new/Gemfile.erb +9 -0
- data/lib/jun/cli/generators/new/README.md.erb +3 -0
- data/lib/jun/cli/generators/new/app/controllers/application_controller.rb.erb +4 -0
- data/lib/jun/cli/generators/new/app/helpers/application_helper.rb.erb +4 -0
- data/lib/jun/cli/generators/new/app/views/layouts/application.html.erb.erb +11 -0
- data/lib/jun/cli/generators/new/config/application.rb.erb +18 -0
- data/lib/jun/cli/generators/new/config/routes.rb.erb +4 -0
- data/lib/jun/cli/generators/new/config.ru.erb +7 -0
- data/lib/jun/cli/generators/new/db/app.db.erb +0 -0
- data/lib/jun/cli.rb +28 -0
- data/lib/jun/connection_adapters/sqlite_adapter.rb +19 -0
- data/lib/jun/version.rb +3 -1
- data/lib/jun.rb +39 -2
- metadata +107 -24
- data/.gitignore +0 -10
- data/.rspec +0 -2
- data/.travis.yml +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a23824148b7546d35ec51bc05200095f47bb5ffcb3604597a2c1606b893ae1c5
|
4
|
+
data.tar.gz: e6e03e5e69d3cebd025a9194de2488539a096c9864fbb53b720f43c5f7d5f68b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a50ed3137ed9926647d32e534c908a1f6bca9e235a6de53f6969932be97dca05bbb44dec9bffd4d3de3b1b5d96cdcc227b2c0728a176a3433dd98cb85e0b403
|
7
|
+
data.tar.gz: 78931eee706054b9b6f85585b3dd0af3055a9ff6641439802aea254c1f3c0c23292802e4c40147f17bea8e72b6d6e1ce5fe3b85455260ef559ca9dedeadcc40e
|
data/.rubocop.yml
ADDED
data/Gemfile
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Jun
|
2
2
|
|
3
|
-
Jun is a simple web application framework
|
3
|
+
Jun is a simple, Rails-inspired web application framework. It is built with the goal of learning more about Rails internals. Not meant for production use.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -20,18 +20,13 @@ Or install it yourself as:
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
-
|
23
|
+
_TBD_
|
24
24
|
|
25
25
|
## Development
|
26
26
|
|
27
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake
|
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.
|
27
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
34
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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
35
30
|
|
36
31
|
## License
|
37
32
|
|
data/Rakefile
CHANGED
@@ -1,6 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "bundler/gem_tasks"
|
2
|
-
require "
|
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
|
-
|
14
|
+
RuboCop::RakeTask.new
|
5
15
|
|
6
|
-
task :
|
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
|
-
|
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
data/jun.gemspec
CHANGED
@@ -1,25 +1,35 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
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
|
8
|
-
spec.version
|
9
|
-
spec.authors
|
10
|
-
spec.email
|
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."
|
13
|
+
spec.homepage = "https://github.com/zokioki/jun"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 2.6.0"
|
11
16
|
|
12
|
-
spec.
|
13
|
-
|
14
|
-
|
15
|
-
|
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.
|
18
|
-
spec.
|
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.
|
23
|
-
spec.
|
24
|
-
spec.
|
27
|
+
spec.add_runtime_dependency "rack"
|
28
|
+
spec.add_runtime_dependency "tilt"
|
29
|
+
spec.add_runtime_dependency "sqlite3"
|
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,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "rendering"
|
4
|
+
|
5
|
+
module Jun
|
6
|
+
module ActionController
|
7
|
+
class Base
|
8
|
+
include Jun::ActionController::Rendering
|
9
|
+
|
10
|
+
attr_accessor :request, :response
|
11
|
+
|
12
|
+
def handle_response(action)
|
13
|
+
public_send(action)
|
14
|
+
render(action) unless response_rendered?
|
15
|
+
|
16
|
+
response
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,88 @@
|
|
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 response_rendered?
|
75
|
+
!!@_response_rendered
|
76
|
+
end
|
77
|
+
|
78
|
+
def views_path
|
79
|
+
dirname = self.class.name.sub(/Controller\z/, "").underscore
|
80
|
+
Jun.root.join("app/views/#{dirname}")
|
81
|
+
end
|
82
|
+
|
83
|
+
def layouts_path
|
84
|
+
Jun.root.join("app/views/layouts")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,29 @@
|
|
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
|
+
controller, action = to.split("#")
|
13
|
+
path = path.to_s.start_with?("/") ? path.to_s : "/#{path}"
|
14
|
+
|
15
|
+
@route_set.add_route("GET", path, controller, action, as)
|
16
|
+
end
|
17
|
+
|
18
|
+
def root(to:)
|
19
|
+
get "/", to: to, as: "root"
|
20
|
+
end
|
21
|
+
|
22
|
+
def resources(plural_name)
|
23
|
+
get "/#{plural_name}", to: "#{plural_name}#index", as: plural_name.to_s
|
24
|
+
get "/#{plural_name}/new", to: "#{plural_name}#new", as: "new_#{plural_name.to_s.singularize}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,65 @@
|
|
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
|
+
request = Rack::Request.new(env)
|
39
|
+
|
40
|
+
if route = find_route(request)
|
41
|
+
route.dispatch(request)
|
42
|
+
else
|
43
|
+
[404, { "Content-Type" => "text/plain" }, ["Not found"]]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_route(*args)
|
48
|
+
route = Route.new(*args)
|
49
|
+
@routes.push(route)
|
50
|
+
|
51
|
+
route
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_route(request)
|
55
|
+
@routes.detect { |route| route.match?(request) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def draw(&block)
|
59
|
+
mapper = Jun::ActionDispatch::Routing::Mapper.new(self)
|
60
|
+
mapper.instance_eval(&block)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -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,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "connection_adapters/sqlite_adapter"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
class Base
|
7
|
+
def initialize(attributes = {})
|
8
|
+
@attributes = attributes
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(name, *args)
|
12
|
+
if self.class.connection.columns(self.class.table_name).include?(name)
|
13
|
+
@attributes[name]
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.find(id)
|
20
|
+
find_by_sql("SELECT * FROM #{table_name} WHERE id = #{id.to_i} LIMIT 1").first
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.all
|
24
|
+
find_by_sql("SELECT * FROM #{table_name}")
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.find_by_sql(sql)
|
28
|
+
connection.execute(sql).map do |attributes|
|
29
|
+
new(attributes)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.table_name
|
34
|
+
name.downcase.pluralize
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.connection
|
38
|
+
@@connection ||= SqliteAdapter.new
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jun
|
4
|
+
module ActiveSupport
|
5
|
+
module Dependencies
|
6
|
+
extend self
|
7
|
+
|
8
|
+
attr_accessor :autoload_paths
|
9
|
+
self.autoload_paths = []
|
10
|
+
|
11
|
+
def find_file(filename)
|
12
|
+
autoload_paths.each do |path|
|
13
|
+
filepath = File.join(path, "#{filename}.rb")
|
14
|
+
return filepath if File.file?(filepath)
|
15
|
+
end
|
16
|
+
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Object
|
24
|
+
def self.const_missing(constant_name)
|
25
|
+
file = Jun::ActiveSupport::Dependencies.find_file(constant_name.to_s.underscore)
|
26
|
+
super if file.nil?
|
27
|
+
|
28
|
+
require file.sub(/\.rb\z/, "")
|
29
|
+
Object.const_get(constant_name)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Inflector
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
end
|
5
|
+
|
6
|
+
PLURAL = {
|
7
|
+
'(quiz)$' => '\1zes',
|
8
|
+
'^(ox)$' => '\1en',
|
9
|
+
'([m|l])ouse$' => '\1ice',
|
10
|
+
'(matr|vert|ind)ix|ex$' => '\1ices',
|
11
|
+
'(x|ch|ss|sh)$' => '\1es',
|
12
|
+
'([^aeiouy]|qu)y$' => '\1ies',
|
13
|
+
'(hive)$' => '\1s',
|
14
|
+
'(?:([^f])fe|([lr])f)$' => '\1\2ves',
|
15
|
+
'(shea|lea|loa|thie)f$' => '\1ves',
|
16
|
+
'sis$' => 'ses',
|
17
|
+
'([ti])um$' => '\1a',
|
18
|
+
'(tomat|potat|ech|her|vet)o$' => '\1oes',
|
19
|
+
'(bu)s$' => '\1ses',
|
20
|
+
'(alias)$' => '\1es',
|
21
|
+
'(octop)us$' => '\1i',
|
22
|
+
'(ax|test)is$' => '\1es',
|
23
|
+
'(us)$' => '\1es',
|
24
|
+
'([^s]+)$' => '\1s'
|
25
|
+
}
|
26
|
+
|
27
|
+
SINGULAR = {
|
28
|
+
'(quiz)zes$' => '\1',
|
29
|
+
'(matr)ices$' => '\1ix',
|
30
|
+
'(vert|ind)ices$' => '\1ex',
|
31
|
+
'^(ox)en$' => '\1',
|
32
|
+
'(alias)es$' => '\1',
|
33
|
+
'(octop|vir)i$' => '\1us',
|
34
|
+
'(cris|ax|test)es$' => '\1is',
|
35
|
+
'(shoe)s$' => '\1',
|
36
|
+
'(o)es$' => '\1',
|
37
|
+
'(bus)es$' => '\1',
|
38
|
+
'([m|l])ice$' => '\1ouse',
|
39
|
+
'(x|ch|ss|sh)es$' => '\1',
|
40
|
+
'(m)ovies$' => '\1ovie',
|
41
|
+
'(s)eries$' => '\1eries',
|
42
|
+
'([^aeiouy]|qu)ies$' => '\1y',
|
43
|
+
'([lr])ves$' => '\1f',
|
44
|
+
'(tive)s$' => '\1',
|
45
|
+
'(hive)s$' => '\1',
|
46
|
+
'(li|wi|kni)ves$' => '\1fe',
|
47
|
+
'(shea|loa|lea|thie)ves$'=> '\1f',
|
48
|
+
'(^analy)ses$' => '\1sis',
|
49
|
+
'((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$' => '\1\2sis',
|
50
|
+
'([ti])a$' => '\1um',
|
51
|
+
'(n)ews$' => '\1ews',
|
52
|
+
'(h|bl)ouses$' => '\1ouse',
|
53
|
+
'(corpse)s$' => '\1',
|
54
|
+
'(us)es$' => '\1',
|
55
|
+
's$' => ''
|
56
|
+
}
|
57
|
+
|
58
|
+
UNCHANGEABLE = [
|
59
|
+
'sheep',
|
60
|
+
'fish',
|
61
|
+
'deer',
|
62
|
+
'moose',
|
63
|
+
'series',
|
64
|
+
'species',
|
65
|
+
'money',
|
66
|
+
'rice',
|
67
|
+
'information',
|
68
|
+
'equipment'
|
69
|
+
]
|
70
|
+
|
71
|
+
def pluralize
|
72
|
+
return self if UNCHANGEABLE.include? self
|
73
|
+
|
74
|
+
pattern, replacement = ""
|
75
|
+
PLURAL.each do |k, v|
|
76
|
+
if self.match(k)
|
77
|
+
pattern = Regexp.new(k)
|
78
|
+
replacement = v
|
79
|
+
break
|
80
|
+
end
|
81
|
+
end
|
82
|
+
self.sub(pattern, replacement)
|
83
|
+
end
|
84
|
+
|
85
|
+
def singularize
|
86
|
+
return self if UNCHANGEABLE.include? self
|
87
|
+
|
88
|
+
pattern, replacement = ""
|
89
|
+
SINGULAR.each do |k, v|
|
90
|
+
if self.match(k)
|
91
|
+
pattern = Regexp.new(k)
|
92
|
+
replacement = v
|
93
|
+
break
|
94
|
+
end
|
95
|
+
end
|
96
|
+
self.sub(pattern, replacement)
|
97
|
+
end
|
98
|
+
|
99
|
+
def underscore
|
100
|
+
gsub(/::/, '/').
|
101
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
102
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
103
|
+
tr("-", "_").
|
104
|
+
downcase
|
105
|
+
end
|
106
|
+
|
107
|
+
def camelize
|
108
|
+
sub(/^[a-z\d]*/) { |match| match.capitalize }.
|
109
|
+
gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.
|
110
|
+
gsub("/", "::")
|
111
|
+
end
|
112
|
+
|
113
|
+
module ClassMethods
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class String
|
118
|
+
include Inflector
|
119
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jun
|
4
|
+
class Application
|
5
|
+
def self.inherited(subclass)
|
6
|
+
super
|
7
|
+
Jun.app_class = subclass
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
routes.call(env)
|
12
|
+
end
|
13
|
+
|
14
|
+
def routes
|
15
|
+
@routes ||= Jun::ActionDispatch::Routing::RouteSet.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jun
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
class Base
|
7
|
+
def self.command_name
|
8
|
+
name.sub("Jun::CLI::Commands::", "").underscore.gsub("/", ":")
|
9
|
+
end
|
10
|
+
|
11
|
+
def process(*args)
|
12
|
+
raise NoMethodError, "Subclass must implement method."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module Jun
|
7
|
+
module CLI
|
8
|
+
module Commands
|
9
|
+
class New < Base
|
10
|
+
def process(*args)
|
11
|
+
if Jun.root
|
12
|
+
puts "Already in a Jun app..."
|
13
|
+
else
|
14
|
+
generate_new_app(args.first)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def generate_new_app(app_name)
|
21
|
+
puts "Generating new Jun app (#{app_name})..."
|
22
|
+
|
23
|
+
templates = [
|
24
|
+
"config.ru",
|
25
|
+
"Gemfile",
|
26
|
+
"README.md",
|
27
|
+
"app/controllers/application_controller.rb",
|
28
|
+
"app/helpers/application_helper.rb",
|
29
|
+
"app/views/layouts/application.html.erb",
|
30
|
+
"config/application.rb",
|
31
|
+
"config/routes.rb",
|
32
|
+
"db/app.db"
|
33
|
+
]
|
34
|
+
|
35
|
+
FileUtils.mkdir_p(app_name)
|
36
|
+
|
37
|
+
FileUtils.chdir(app_name) do
|
38
|
+
templates.each do |filepath|
|
39
|
+
template_filepath = File.expand_path("../generators/new/#{filepath}.erb", __dir__)
|
40
|
+
template = Tilt::ERBTemplate.new(template_filepath)
|
41
|
+
template_locals = { app_name: app_name }
|
42
|
+
file_body = template.render(nil, template_locals)
|
43
|
+
|
44
|
+
FileUtils.mkdir_p(File.dirname(filepath))
|
45
|
+
|
46
|
+
File.open(filepath, "w") { |f| f.write(file_body) }
|
47
|
+
puts "created #{filepath}"
|
48
|
+
end
|
49
|
+
|
50
|
+
puts "Installing dependencies..."
|
51
|
+
Bundler.with_original_env { system("bundle install") }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jun
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
class Server < Base
|
7
|
+
def process(*args)
|
8
|
+
if Jun.root
|
9
|
+
system("rerun --background -- rackup -p 3001")
|
10
|
+
else
|
11
|
+
abort("Command \"#{self.class.command_name}\" must be run inside of a Jun app.")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jun"
|
4
|
+
|
5
|
+
Jun::ActiveSupport::Dependencies.autoload_paths += Jun.root.join("app").children
|
6
|
+
|
7
|
+
# Include all helpers in app/helpers directory
|
8
|
+
Dir.glob(Jun.root.join("app/helpers/**/*.rb")).each do |filepath|
|
9
|
+
helper_class_name = File.basename(filepath, ".rb").camelize
|
10
|
+
helper_class = Object.const_get(helper_class_name)
|
11
|
+
|
12
|
+
Jun::ActionView::Base.include(helper_class)
|
13
|
+
end
|
14
|
+
|
15
|
+
module <%= app_name.camelize %>
|
16
|
+
class Application < Jun::Application
|
17
|
+
end
|
18
|
+
end
|
File without changes
|
data/lib/jun/cli.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Dir[File.expand_path("cli/commands/**/*.rb", __dir__)].each do |filepath|
|
4
|
+
require_relative filepath
|
5
|
+
end
|
6
|
+
|
7
|
+
module Jun
|
8
|
+
module CLI
|
9
|
+
class << self
|
10
|
+
COMMAND_KLASSES = [
|
11
|
+
Jun::CLI::Commands::New,
|
12
|
+
Jun::CLI::Commands::DB::Create,
|
13
|
+
Jun::CLI::Commands::DB::Migrate,
|
14
|
+
Jun::CLI::Commands::DB::Drop,
|
15
|
+
Jun::CLI::Commands::Server,
|
16
|
+
Jun::CLI::Commands::Version
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
def process_command(argv)
|
20
|
+
command_name = argv.shift
|
21
|
+
command_klass = COMMAND_KLASSES.find { |klass| klass.command_name == command_name }
|
22
|
+
abort("Command \"#{command_name}\" not found.") if command_klass.nil?
|
23
|
+
|
24
|
+
command_klass.new.process(*argv)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sqlite3"
|
4
|
+
|
5
|
+
class SqliteAdapter
|
6
|
+
def initialize
|
7
|
+
@db = SQLite3::Database.new(Jun.root.join("db/app.db").to_s, results_as_hash: true)
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute(sql)
|
11
|
+
@db.execute(sql).each do |row|
|
12
|
+
row.keys.each { |key| row[(key.to_sym rescue key) || key] = row.delete(key) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def columns(table_name)
|
17
|
+
@db.table_info(table_name).map { |info| info["name"].to_sym }
|
18
|
+
end
|
19
|
+
end
|
data/lib/jun/version.rb
CHANGED
data/lib/jun.rb
CHANGED
@@ -1,5 +1,42 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
require_relative "jun/version"
|
6
|
+
require_relative "jun/active_support/inflector"
|
7
|
+
require_relative "jun/active_support/dependencies"
|
8
|
+
require_relative "jun/action_dispatch/routing/route_set"
|
9
|
+
require_relative "jun/active_record"
|
10
|
+
require_relative "jun/action_controller/base"
|
11
|
+
require_relative "jun/application"
|
2
12
|
|
3
13
|
module Jun
|
4
|
-
|
14
|
+
class << self
|
15
|
+
attr_accessor :app_class
|
16
|
+
|
17
|
+
def application
|
18
|
+
@application ||= app_class&.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def root
|
22
|
+
project_root_path
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def project_root_path
|
28
|
+
current_dir = Dir.pwd
|
29
|
+
root_file = "config.ru"
|
30
|
+
|
31
|
+
while current_dir && File.directory?(current_dir) && !File.exist?("#{current_dir}/#{root_file}")
|
32
|
+
parent_dir = File.dirname(current_dir)
|
33
|
+
current_dir = parent_dir != current_dir && parent_dir
|
34
|
+
end
|
35
|
+
|
36
|
+
root_dir = current_dir if File.exist?("#{current_dir}/#{root_file}")
|
37
|
+
return if root_dir.nil?
|
38
|
+
|
39
|
+
Pathname.new(File.realpath(root_dir))
|
40
|
+
end
|
41
|
+
end
|
5
42
|
end
|
metadata
CHANGED
@@ -1,82 +1,166 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jun
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zoran
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-03-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: tilt
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sqlite3
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
15
57
|
requirement: !ruby/object:Gem::Requirement
|
16
58
|
requirements:
|
17
59
|
- - "~>"
|
18
60
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
61
|
+
version: '13.0'
|
20
62
|
type: :development
|
21
63
|
prerelease: false
|
22
64
|
version_requirements: !ruby/object:Gem::Requirement
|
23
65
|
requirements:
|
24
66
|
- - "~>"
|
25
67
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
68
|
+
version: '13.0'
|
27
69
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
70
|
+
name: minitest
|
29
71
|
requirement: !ruby/object:Gem::Requirement
|
30
72
|
requirements:
|
31
73
|
- - "~>"
|
32
74
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
75
|
+
version: '5.0'
|
34
76
|
type: :development
|
35
77
|
prerelease: false
|
36
78
|
version_requirements: !ruby/object:Gem::Requirement
|
37
79
|
requirements:
|
38
80
|
- - "~>"
|
39
81
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
82
|
+
version: '5.0'
|
41
83
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
84
|
+
name: rubocop
|
43
85
|
requirement: !ruby/object:Gem::Requirement
|
44
86
|
requirements:
|
45
87
|
- - "~>"
|
46
88
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
89
|
+
version: '1.21'
|
48
90
|
type: :development
|
49
91
|
prerelease: false
|
50
92
|
version_requirements: !ruby/object:Gem::Requirement
|
51
93
|
requirements:
|
52
94
|
- - "~>"
|
53
95
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
55
|
-
|
56
|
-
|
96
|
+
version: '1.21'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.14'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.14'
|
111
|
+
description: A simple web framework inspired by Rails.
|
57
112
|
email:
|
58
|
-
-
|
59
|
-
executables:
|
113
|
+
- zspesic@gmail.com
|
114
|
+
executables:
|
115
|
+
- jun
|
60
116
|
extensions: []
|
61
117
|
extra_rdoc_files: []
|
62
118
|
files:
|
63
|
-
- ".
|
64
|
-
- ".rspec"
|
65
|
-
- ".travis.yml"
|
119
|
+
- ".rubocop.yml"
|
66
120
|
- Gemfile
|
67
121
|
- LICENSE.txt
|
68
122
|
- README.md
|
69
123
|
- Rakefile
|
70
124
|
- bin/console
|
71
125
|
- bin/setup
|
126
|
+
- exe/jun
|
72
127
|
- jun.gemspec
|
73
128
|
- lib/jun.rb
|
129
|
+
- lib/jun/action_controller/base.rb
|
130
|
+
- lib/jun/action_controller/rendering.rb
|
131
|
+
- lib/jun/action_dispatch/routing/mapper.rb
|
132
|
+
- lib/jun/action_dispatch/routing/route_set.rb
|
133
|
+
- lib/jun/action_view/base.rb
|
134
|
+
- lib/jun/action_view/helpers.rb
|
135
|
+
- lib/jun/action_view/helpers/url_helper.rb
|
136
|
+
- lib/jun/active_record.rb
|
137
|
+
- lib/jun/active_support/dependencies.rb
|
138
|
+
- lib/jun/active_support/inflector.rb
|
139
|
+
- lib/jun/application.rb
|
140
|
+
- lib/jun/cli.rb
|
141
|
+
- lib/jun/cli/commands/base.rb
|
142
|
+
- lib/jun/cli/commands/db/create.rb
|
143
|
+
- lib/jun/cli/commands/db/drop.rb
|
144
|
+
- lib/jun/cli/commands/db/migrate.rb
|
145
|
+
- lib/jun/cli/commands/new.rb
|
146
|
+
- lib/jun/cli/commands/server.rb
|
147
|
+
- lib/jun/cli/commands/version.rb
|
148
|
+
- lib/jun/cli/generators/new/Gemfile.erb
|
149
|
+
- lib/jun/cli/generators/new/README.md.erb
|
150
|
+
- lib/jun/cli/generators/new/app/controllers/application_controller.rb.erb
|
151
|
+
- lib/jun/cli/generators/new/app/helpers/application_helper.rb.erb
|
152
|
+
- lib/jun/cli/generators/new/app/views/layouts/application.html.erb.erb
|
153
|
+
- lib/jun/cli/generators/new/config.ru.erb
|
154
|
+
- lib/jun/cli/generators/new/config/application.rb.erb
|
155
|
+
- lib/jun/cli/generators/new/config/routes.rb.erb
|
156
|
+
- lib/jun/cli/generators/new/db/app.db.erb
|
157
|
+
- lib/jun/connection_adapters/sqlite_adapter.rb
|
74
158
|
- lib/jun/version.rb
|
75
159
|
homepage: https://github.com/zokioki/jun
|
76
160
|
licenses:
|
77
161
|
- MIT
|
78
162
|
metadata: {}
|
79
|
-
post_install_message:
|
163
|
+
post_install_message:
|
80
164
|
rdoc_options: []
|
81
165
|
require_paths:
|
82
166
|
- lib
|
@@ -84,16 +168,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
168
|
requirements:
|
85
169
|
- - ">="
|
86
170
|
- !ruby/object:Gem::Version
|
87
|
-
version:
|
171
|
+
version: 2.6.0
|
88
172
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
173
|
requirements:
|
90
174
|
- - ">="
|
91
175
|
- !ruby/object:Gem::Version
|
92
176
|
version: '0'
|
93
177
|
requirements: []
|
94
|
-
|
95
|
-
|
96
|
-
signing_key:
|
178
|
+
rubygems_version: 3.2.32
|
179
|
+
signing_key:
|
97
180
|
specification_version: 4
|
98
181
|
summary: A simple Ruby web framework.
|
99
182
|
test_files: []
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/.travis.yml
DELETED