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.
Files changed (44) 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 +4 -9
  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 +20 -0
  11. data/lib/jun/action_controller/rendering.rb +88 -0
  12. data/lib/jun/action_dispatch/routing/mapper.rb +29 -0
  13. data/lib/jun/action_dispatch/routing/route_set.rb +65 -0
  14. data/lib/jun/action_view/base.rb +26 -0
  15. data/lib/jun/action_view/helpers/url_helper.rb +13 -0
  16. data/lib/jun/action_view/helpers.rb +11 -0
  17. data/lib/jun/active_record.rb +41 -0
  18. data/lib/jun/active_support/dependencies.rb +31 -0
  19. data/lib/jun/active_support/inflector.rb +119 -0
  20. data/lib/jun/application.rb +18 -0
  21. data/lib/jun/cli/commands/base.rb +17 -0
  22. data/lib/jun/cli/commands/db/create.rb +15 -0
  23. data/lib/jun/cli/commands/db/drop.rb +15 -0
  24. data/lib/jun/cli/commands/db/migrate.rb +15 -0
  25. data/lib/jun/cli/commands/new.rb +57 -0
  26. data/lib/jun/cli/commands/server.rb +17 -0
  27. data/lib/jun/cli/commands/version.rb +13 -0
  28. data/lib/jun/cli/generators/new/Gemfile.erb +9 -0
  29. data/lib/jun/cli/generators/new/README.md.erb +3 -0
  30. data/lib/jun/cli/generators/new/app/controllers/application_controller.rb.erb +4 -0
  31. data/lib/jun/cli/generators/new/app/helpers/application_helper.rb.erb +4 -0
  32. data/lib/jun/cli/generators/new/app/views/layouts/application.html.erb.erb +11 -0
  33. data/lib/jun/cli/generators/new/config/application.rb.erb +18 -0
  34. data/lib/jun/cli/generators/new/config/routes.rb.erb +4 -0
  35. data/lib/jun/cli/generators/new/config.ru.erb +7 -0
  36. data/lib/jun/cli/generators/new/db/app.db.erb +0 -0
  37. data/lib/jun/cli.rb +28 -0
  38. data/lib/jun/connection_adapters/sqlite_adapter.rb +19 -0
  39. data/lib/jun/version.rb +3 -1
  40. data/lib/jun.rb +39 -2
  41. metadata +107 -24
  42. data/.gitignore +0 -10
  43. data/.rspec +0 -2
  44. 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: a23824148b7546d35ec51bc05200095f47bb5ffcb3604597a2c1606b893ae1c5
4
+ data.tar.gz: e6e03e5e69d3cebd025a9194de2488539a096c9864fbb53b720f43c5f7d5f68b
5
5
  SHA512:
6
- metadata.gz: c282aabb2f3057321548c32de8058e65b13d433f885e71fc5a3ca405351cb609a8a98bc288162f0c8a67591ac2106d63c55d121cd6fdf6c3bc376e0d3f2001c5
7
- data.tar.gz: 8fdd848aad1a8cdafd410e0090b4b85a6a09f7b63e0dee7fbd072ed08890e0b2ceebd989a1aca5665458cda3e8a7ac0c2292c78cd0ee6f7ff236888f9ef604ed
6
+ metadata.gz: 0a50ed3137ed9926647d32e534c908a1f6bca9e235a6de53f6969932be97dca05bbb44dec9bffd4d3de3b1b5d96cdcc227b2c0728a176a3433dd98cb85e0b403
7
+ data.tar.gz: 78931eee706054b9b6f85585b3dd0af3055a9ff6641439802aea254c1f3c0c23292802e4c40147f17bea8e72b6d6e1ce5fe3b85455260ef559ca9dedeadcc40e
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,6 +1,6 @@
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
+ 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
- TODO: Write usage instructions here
23
+ _TBD_
24
24
 
25
25
  ## Development
26
26
 
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.
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 "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."
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"
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,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
@@ -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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jun
4
+ module CLI
5
+ module Commands
6
+ module DB
7
+ class Create < Base
8
+ def process(*args)
9
+ puts "Creating database..."
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jun
4
+ module CLI
5
+ module Commands
6
+ module DB
7
+ class Drop < Base
8
+ def process(*args)
9
+ puts "Dropping database..."
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jun
4
+ module CLI
5
+ module Commands
6
+ module DB
7
+ class Migrate < Base
8
+ def process(*args)
9
+ puts "Running migrations..."
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jun
4
+ module CLI
5
+ module Commands
6
+ class Version < Base
7
+ def process(*args)
8
+ puts "Jun #{Jun::VERSION}"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "jun", path: "/Users/zoran/dev/jun"
4
+ gem "puma"
5
+
6
+ group :development do
7
+ gem "pry"
8
+ gem "rerun"
9
+ end
@@ -0,0 +1,3 @@
1
+ # New Jun App
2
+
3
+ Your newly created Jun app.
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationController < Jun::ActionController::Base
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApplicationHelper
4
+ end
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Jun App</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ </head>
7
+
8
+ <body>
9
+ <%%= yield %>
10
+ </body>
11
+ </html>
@@ -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
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ Jun.application.routes.draw do
4
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require_relative "config/application"
5
+ require_relative "config/routes"
6
+
7
+ run Jun.application
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jun
2
- VERSION = "0.0.1"
4
+ VERSION = "0.1.0"
3
5
  end
data/lib/jun.rb CHANGED
@@ -1,5 +1,42 @@
1
- require "jun/version"
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.1
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: 2016-04-07 00:00:00.000000000 Z
11
+ date: 2022-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
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: '1.11'
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: '1.11'
68
+ version: '13.0'
27
69
  - !ruby/object:Gem::Dependency
28
- name: rake
70
+ name: minitest
29
71
  requirement: !ruby/object:Gem::Requirement
30
72
  requirements:
31
73
  - - "~>"
32
74
  - !ruby/object:Gem::Version
33
- version: '10.0'
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: '10.0'
82
+ version: '5.0'
41
83
  - !ruby/object:Gem::Dependency
42
- name: rspec
84
+ name: rubocop
43
85
  requirement: !ruby/object:Gem::Requirement
44
86
  requirements:
45
87
  - - "~>"
46
88
  - !ruby/object:Gem::Version
47
- version: '3.0'
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: '3.0'
55
- description: A simple web framework inspired by Rails, built with the hopes of learning
56
- more about Rails internals in the process.
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
- - zoran1991@gmail.com
59
- executables: []
113
+ - zspesic@gmail.com
114
+ executables:
115
+ - jun
60
116
  extensions: []
61
117
  extra_rdoc_files: []
62
118
  files:
63
- - ".gitignore"
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: '0'
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
- rubyforge_project:
95
- rubygems_version: 2.5.1
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
@@ -1,10 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- .DS_Store
data/.rspec DELETED
@@ -1,2 +0,0 @@
1
- --format documentation
2
- --color
data/.travis.yml DELETED
@@ -1,4 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.3.0
4
- before_install: gem install bundler -v 1.11.2