ru.Bee 1.6.0 → 1.7.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.
@@ -0,0 +1,39 @@
1
+ module Rubee
2
+ module CLI
3
+ module Console
4
+ class << self
5
+ def call(command, argv)
6
+ send(command, argv)
7
+ end
8
+
9
+ def console(argv)
10
+ argv.clear
11
+ ENV['RACK_ENV'] ||= 'development'
12
+
13
+ if Rubee::PROJECT_NAME == 'rubee'
14
+ Rubee::Configuration.setup(env = :test) do |config|
15
+ config.database_url = { url: 'sqlite://lib/tests/test.db', env: }
16
+ end
17
+ # Rubee::Autoload.call
18
+ # Rubee::SequelObject.reconnect!
19
+ end
20
+
21
+ def reload
22
+ app_files = Dir["./#{Rubee::APP_ROOT}/**/*.rb"]
23
+ app_files.each { |file| load(file) }
24
+ color_puts('Reloaded ..', color: :green)
25
+ end
26
+
27
+ begin
28
+ # Start IRB
29
+ IRB.start
30
+ rescue => _e
31
+ IRB.start
32
+ end
33
+ end
34
+
35
+ alias_method :c, :console
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,105 @@
1
+ module Rubee
2
+ module CLI
3
+ module Db
4
+ class << self
5
+ def call(command, argv)
6
+ command = argv[1].split(':').first
7
+ ENV['RACK_ENV'] ||= 'development'
8
+ if Rubee::PROJECT_NAME == 'rubee'
9
+ Rubee::Configuration.setup(env = :test) do |config|
10
+ config.database_url = { url: 'sqlite://lib/tests/test.db', env: }
11
+ end
12
+ Rubee::SequelObject.reconnect! unless command == 'init'
13
+ end
14
+
15
+ send(command, argv)
16
+ end
17
+
18
+ def run(argv)
19
+ _, file_name = argv[1]&.split(':')
20
+ file_names = if file_name == 'all'
21
+ lib = Rubee::PROJECT_NAME == 'rubee' ? '/lib' : ''
22
+ Dir.glob(".#{lib}/db/*.rb").map do |file|
23
+ File.basename(file, '.rb')
24
+ end.reject { |file| file == 'structure' }
25
+ else
26
+ [file_name]
27
+ end
28
+ Rubee::Configuration.envs.each do |env|
29
+ ENV['RACK_ENV'] = env.to_s
30
+ file_names.each do |file|
31
+ color_puts("Run #{file} file for #{env} env", color: :cyan)
32
+ Object.const_get(file.split('_').map(&:capitalize).join).new.call
33
+ end
34
+ end
35
+ color_puts("Migration for #{file_name} completed", color: :green)
36
+ unless Rubee::PROJECT_NAME == 'rubee'
37
+ color_puts('Regenerate schema file', color: :cyan)
38
+ generate_structure
39
+ end
40
+ end
41
+
42
+ def init(_argv)
43
+ ensure_database_exists(Rubee::Configuration.get_database_url)
44
+ end
45
+
46
+ def structure(_argv)
47
+ generate_structure
48
+ end
49
+
50
+ private
51
+
52
+ def generate_structure
53
+ schema_hash = {}
54
+
55
+ Rubee::SequelObject::DB.tables.each do |table|
56
+ schema_hash[table] = {}
57
+
58
+ Rubee::SequelObject::DB.schema(table).each do |column, details|
59
+ schema_hash[table][column] = details
60
+ end
61
+ end
62
+ formatted_hash = JSON.pretty_generate(schema_hash)
63
+ .gsub(/"(\w+)":/, '\1:') # Convert keys to symbols
64
+ .gsub(': null', ': nil') # Convert `null` to `nil`
65
+
66
+ File.open('db/structure.rb', 'w') do |file|
67
+ file.puts "STRUCTURE = #{formatted_hash}"
68
+ end
69
+
70
+ color_puts('db/structure.rb updated', color: :green)
71
+ end
72
+
73
+ def ensure_database_exists(db_url)
74
+ uri = URI.parse(db_url)
75
+ case uri.scheme
76
+ when 'sqlite'
77
+ begin
78
+ Sequel.connect(db_url)
79
+ color_puts("Database #{ENV['RACK_ENV']} exists", color: :cyan)
80
+ rescue => _e
81
+ if File.exist?(db_path = db_url.sub(%r{^sqlite://}, ''))
82
+ color_puts("Database #{ENV['RACK_ENV']} exists", color: :cyan)
83
+ else
84
+ Sequel.sqlite(db_path)
85
+ color_puts("Database #{ENV['RACK_ENV']} created", color: :green)
86
+ end
87
+ end
88
+ when 'postgres'
89
+ begin
90
+ Sequel.connect(db_url)
91
+ color_puts("Database #{ENV['RACK_ENV']} exists", color: :cyan)
92
+ rescue StandardError => _e
93
+ con = Sequel.connect(Rubee::Configuration.get_database_url.gsub(%r{(/test|/development|/production)},
94
+ ''))
95
+ con.run("CREATE DATABASE #{ENV['RACK_ENV']}")
96
+ color_puts("Database #{ENV['RACK_ENV']} created", color: :green)
97
+ end
98
+ else
99
+ color_puts("Unsupported database type: #{db_url}", color: :red)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,33 @@
1
+ module Rubee
2
+ module CLI
3
+ module Generate
4
+ class << self
5
+ def call(command, argv)
6
+ send(command, argv)
7
+ end
8
+
9
+ def generate(argv)
10
+ method, path = argv[1..2]
11
+ app = argv[3]
12
+ app_name = app.nil? ? :app : app.split(':')[1]
13
+ ENV['RACK_ENV'] ||= 'development'
14
+ file = Rubee::PROJECT_NAME == 'rubee' ? File.join(Dir.pwd, '/lib', 'config/routes.rb') : 'config/routes.rb'
15
+ routes = eval(File.read(file))
16
+ route = routes.find { |route| route[:path] == path.to_s && route[:method] == method.to_sym }
17
+
18
+ color_puts("Route not found with path: #{path} and method: #{method}", color: :red) unless route
19
+ Rubee::Generator.new(
20
+ route[:model]&.[](:name),
21
+ route[:model]&.[](:attributes),
22
+ "#{route[:controller]&.capitalize}Controller",
23
+ route[:action],
24
+ react: route[:react],
25
+ app_name:
26
+ ).call
27
+ end
28
+
29
+ alias_method :gen, :generate
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,124 @@
1
+ module Rubee
2
+ module CLI
3
+ class Project
4
+ class << self
5
+ def call(command, argv)
6
+ send(command, argv)
7
+ end
8
+
9
+ def project(argv)
10
+ project_name = argv[1]
11
+
12
+ if project_name.nil?
13
+ color_puts('Please indicate project name.', color: :red)
14
+ exit(1)
15
+ end
16
+
17
+ if project_name == 'rubee'
18
+ color_puts("Error: Project 'rubee' is reserved", color: :red)
19
+ exit(1)
20
+ end
21
+ source_dir = File.join(Rubee::ROOT_PATH, '/lib')
22
+ target_dir = File.expand_path("./#{project_name}", Dir.pwd)
23
+
24
+ if Dir.exist?(target_dir)
25
+ color_puts("Error: Project #{project_name} already exists!", color: :red)
26
+ exit(1)
27
+ end
28
+ # Create target directory
29
+ FileUtils.mkdir_p(target_dir)
30
+ # Define blacklist
31
+ blacklist_files = %w[rubee.rb print_colors.rb version.rb config.ru test_helper.rb Gemfile.lock test.yml test.db
32
+ development.db production.db]
33
+ blacklist_dirs = %w[rubee tests .git .github .idea node_modules db inits]
34
+ # Copy files, excluding blacklisted ones
35
+ copy_project_files(source_dir, target_dir, blacklist_files, blacklist_dirs)
36
+ # create tests dir and copy test_helper.rb and user_model_test.rb
37
+ setup_test_structure(target_dir, source_dir)
38
+ # create db dir
39
+ setup_db_structure(target_dir, source_dir)
40
+ # create inits dir
41
+ FileUtils.mkdir_p("#{target_dir}/inits")
42
+ # create a gemfile context
43
+ setup_gemfile(target_dir)
44
+ color_puts("Project #{project_name} created successfully at #{target_dir}", color: :green)
45
+ end
46
+
47
+ private
48
+
49
+ def copy_project_files(source_dir, target_dir, blacklist_files, blacklist_dirs)
50
+ Dir.glob("#{source_dir}/**/*", File::FNM_DOTMATCH).each do |file|
51
+ relative_path = file.sub("#{source_dir}/", '')
52
+ # Skip blacklisted directories
53
+ next if blacklist_dirs.any? { |dir| relative_path.split('/').include?(dir) }
54
+ # Skip blacklisted files
55
+ next if blacklist_files.include?(File.basename(file))
56
+
57
+ target_path = File.join(target_dir, relative_path)
58
+ if File.directory?(file)
59
+ FileUtils.mkdir_p(target_path)
60
+ else
61
+ FileUtils.cp(file, target_path)
62
+ end
63
+ end
64
+ end
65
+
66
+ def setup_test_structure(target_dir, source_dir)
67
+ FileUtils.mkdir_p("#{target_dir}/tests")
68
+ FileUtils.mkdir_p("#{target_dir}/tests/models")
69
+ FileUtils.mkdir_p("#{target_dir}/tests/controllers")
70
+ FileUtils.cp("#{source_dir}/tests/models/user_model_test.rb", "#{target_dir}/tests/models/user_model_test.rb")
71
+
72
+ # create test_helper.rb file
73
+ test_helper = <<~TESTHELPER
74
+ require "bundler/setup"
75
+ Bundler.require(:test)
76
+
77
+ require 'minitest/autorun'
78
+ require 'rack/test'
79
+ require 'rubee'
80
+
81
+ Rubee::Autoload.call
82
+ TESTHELPER
83
+
84
+ File.open("#{target_dir}/tests/test_helper.rb", 'w') do |file|
85
+ file.puts test_helper
86
+ end
87
+ end
88
+
89
+ def setup_db_structure(target_dir, source_dir)
90
+ FileUtils.mkdir_p("#{target_dir}/db")
91
+ FileUtils.cp("#{source_dir}/db/structure.rb", "#{target_dir}/db/structure.rb")
92
+ FileUtils.cp("#{source_dir}/db/create_users.rb", "#{target_dir}/db/create_users.rb")
93
+ end
94
+
95
+ def setup_gemfile(target_dir)
96
+ gemfile = <<~GEMFILE
97
+ source 'https://rubygems.org'
98
+
99
+ gem 'ru.Bee'
100
+ gem 'sequel'
101
+ gem 'sqlite3'
102
+ gem 'rake'
103
+ gem 'rack'
104
+ gem 'rackup'
105
+ gem 'pry'
106
+ gem 'pry-byebug'
107
+ gem 'puma'
108
+ gem 'json'
109
+
110
+ group :development do
111
+ gem 'rerun'
112
+ gem 'minitest'
113
+ gem 'rack-test'
114
+ end
115
+ GEMFILE
116
+ # create a gemfile
117
+ File.open("#{target_dir}/Gemfile", 'w') do |file|
118
+ file.puts gemfile
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,28 @@
1
+ module Rubee
2
+ module CLI
3
+ class React
4
+ class << self
5
+ def call(command, argv)
6
+ command = argv[1]
7
+ send(command, argv)
8
+ end
9
+
10
+ def prepare(_argv)
11
+ if Rubee::PROJECT_NAME == 'rubee'
12
+ exec('cd ./lib && npm run prepare')
13
+ else
14
+ exec('npm run prepare')
15
+ end
16
+ end
17
+
18
+ def watch(_argv)
19
+ if Rubee::PROJECT_NAME == 'rubee'
20
+ exec('cd ./lib && npm run watch')
21
+ else
22
+ exec('npm run watch')
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ module Rubee
2
+ module CLI
3
+ class Routes
4
+ class << self
5
+ def call(command, argv)
6
+ send(command, argv)
7
+ end
8
+
9
+ def routes(_argv)
10
+ file = Rubee::PROJECT_NAME == 'rubee' ? File.join(Dir.pwd, '/lib', 'config/routes.rb') : 'config/routes.rb'
11
+ routes = eval(File.read(file)) # TODO: rewrite it omitting eval
12
+
13
+ color_puts(routes, color: :green)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,52 @@
1
+ module Rubee
2
+ module CLI
3
+ class Server
4
+ LOGO = <<-'LOGO'
5
+ ____ _ _ ____ _____
6
+ | _ \| | | || __ )| ____|
7
+ | |_) | | | || _ \| _|
8
+ | _ <| |__| || |_) | |___
9
+ |_| \_\\____/ |____/|_____|
10
+ Ver: %s
11
+ LOGO
12
+
13
+ class << self
14
+ def call(command, argv)
15
+ send(command, argv)
16
+ end
17
+
18
+ def start(argv)
19
+ _, port = argv.first&.split(':')
20
+
21
+ port ||= '7000'
22
+ print_logo
23
+ color_puts("Starting takeoff of ruBee server on port #{port}...", color: :yellow)
24
+ exec("rackup #{ENV['RACKUP_FILE']} -p #{port}")
25
+ end
26
+
27
+ def start_dev(argv)
28
+ _, port = argv.first&.split(':')
29
+
30
+ port ||= '7000'
31
+ print_logo
32
+
33
+ color_puts("Starting takeoff of ruBee server on port #{port} in dev mode...", color: :yellow)
34
+
35
+ exec("rerun -- rackup --port #{port} #{ENV['RACKUP_FILE']}")
36
+ end
37
+
38
+ def stop(_argv)
39
+ exec('pkill -f rubee')
40
+ end
41
+
42
+ def status(_argv)
43
+ exec('ps aux | grep rubee')
44
+ end
45
+
46
+ def print_logo
47
+ puts "\e[36m#{LOGO % Rubee::VERSION}\e[0m" # Cyan color
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,24 @@
1
+ module Rubee
2
+ module CLI
3
+ module Test
4
+ class << self
5
+ def call(command, argv)
6
+ send(command, argv)
7
+ end
8
+
9
+ def test(argv)
10
+ ENV['RACK_ENV'] = 'test'
11
+ file_name = argv[1] # Get the first argument
12
+ lib = Rubee::PROJECT_NAME == 'rubee' ? '/lib' : ''
13
+ if file_name
14
+ color_puts("Running #{file_name} test ...", color: :yellow)
15
+ exec("ruby -Itest -e \"require '.#{lib}/tests/#{file_name}'\"")
16
+ else
17
+ color_puts('Running all tests ...', color: :yellow)
18
+ exec("ruby -Itest -e \"Dir.glob('.#{lib}/tests/**/*_test.rb').each { |file| require file }\"")
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ module Rubee
2
+ module CLI
3
+ class Version
4
+ class << self
5
+ def call(command, argv)
6
+ send(command, argv)
7
+ end
8
+
9
+ def version(_argv)
10
+ color_puts("ruBee v#{Rubee::VERSION}", color: :yellow)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,55 +1,78 @@
1
1
  module Rubee
2
2
  class Configuration
3
3
  include Singleton
4
+ require_relative '../inits/charged_hash' unless defined?(ChargedHash)
5
+ using ChargedHash
4
6
 
5
7
  @configuraiton = {
6
- development: {
7
- database_url: '',
8
- port: 7000,
8
+ app: {
9
+ development: {
10
+ database_url: '',
11
+ port: 7000,
12
+ },
13
+ production: {},
14
+ test: {},
9
15
  },
10
- production: {},
11
- test: {},
12
16
  }
13
17
 
14
18
  class << self
15
- def setup(_env)
19
+ def setup(env, app = :app)
20
+ unless @configuraiton[app.to_sym]
21
+ @configuraiton[app.to_sym] = {
22
+ development: {},
23
+ production: {},
24
+ test: {},
25
+ }
26
+ unless @configuraiton[app.to_sym][env.to_sym]
27
+ @configuraiton[app.to_sym][env.to_sym] = {}
28
+ end
29
+ end
30
+
16
31
  yield(self)
17
32
  end
18
33
 
19
34
  def database_url=(args)
20
- @configuraiton[args[:env].to_sym][:database_url] = args[:url]
35
+ args[:app] ||= :app
36
+ @configuraiton[args[:app].to_sym][args[:env].to_sym][:database_url] = args[:url]
21
37
  end
22
38
 
23
39
  def async_adapter=(args)
24
- @configuraiton[args[:env].to_sym][:async_adapter] = args[:async_adapter]
40
+ args[:app] ||= :app
41
+ @configuraiton[args[:app].to_sym][args[:env].to_sym][:async_adapter] = args[:async_adapter]
25
42
  end
26
43
 
27
44
  def threads_limit=(args)
28
- @configuraiton[args[:env].to_sym][:thread_pool_limit] = args[:value]
45
+ args[:app] ||= :app
46
+ @configuraiton[args[:app].to_sym][args[:env].to_sym][:thread_pool_limit] = args[:value]
29
47
  end
30
48
 
31
49
  def fibers_limit=(args)
32
- @configuraiton[args[:env].to_sym][:fiber_pool_limit] = args[:value]
50
+ args[:app] ||= :app
51
+ @configuraiton[args[:app].to_sym][args[:env].to_sym][:fiber_pool_limit] = args[:value]
33
52
  end
34
53
 
35
54
  def logger=(args)
36
- @configuraiton[args[:env].to_sym][:logger] = args[:logger]
55
+ args[:app] ||= :app
56
+ @configuraiton[args[:app].to_sym][args[:env].to_sym][:logger] = args[:logger]
37
57
  end
38
58
 
39
-
40
59
  def react=(args)
41
- @configuraiton[args[:env].to_sym][:react] ||= { on: false }
42
- @configuraiton[args[:env].to_sym][:react].merge!(on: args[:on])
60
+ args[:app] ||= :app
61
+ @configuraiton[args[:app].to_sym][args[:env].to_sym][:react] ||= { on: false }
62
+ @configuraiton[args[:app].to_sym][args[:env].to_sym][:react].merge!(on: args[:on])
43
63
  end
44
64
 
45
- def react
46
- @configuraiton[ENV['RACK_ENV']&.to_sym || :development][:react] || {}
65
+ def react(**args)
66
+ args[:app] ||= :app
67
+ @configuraiton[args[:app].to_sym][ENV['RACK_ENV']&.to_sym || :development][:react] || {}
47
68
  end
48
69
 
49
- def method_missing(method_name, *_args)
70
+ def method_missing(method_name, *args)
50
71
  return unless method_name.to_s.start_with?('get_')
51
72
 
52
- @configuraiton[ENV['RACK_ENV']&.to_sym || :development]&.[](method_name.to_s.delete_prefix('get_').to_sym)
73
+ app_name = args[0] || :app
74
+ @configuraiton[app_name.to_sym][ENV['RACK_ENV']&.to_sym || :development]
75
+ &.[](method_name.to_s.delete_prefix('get_').to_sym)
53
76
  end
54
77
 
55
78
  def envs
@@ -68,12 +68,18 @@ module Rubee
68
68
  in :not_found
69
69
  [404, { 'content-type' => 'text/plain' }, ['Route not found']]
70
70
  else # rendering erb view is a default behavior
71
- view_file_name = self.class.name.split('Controller').first.downcase
71
+ # TODO: refactor
72
+ view_file_name = self.class.name.split('Controller').first.gsub('::', '_').downcase
72
73
  erb_file = render_view ? render_view.to_s : "#{view_file_name}_#{@route[:action]}"
73
74
  lib = Rubee::PROJECT_NAME == 'rubee' ? 'lib/' : ''
74
- view = render_template(erb_file, { object:, **(options[:locals] || {}) })
75
-
76
- whole_erb = if File.exist?(layout_path = "#{lib}app/views/#{options[:layout] || 'layout'}.erb")
75
+ path_parts = self.class.instance_method(@route[:action]).source_location[0].split('/').reverse
76
+ controller_index = path_parts.find_index { |part| part == 'controllers' }
77
+ app_name = path_parts[controller_index + 1]
78
+ view = render_template(erb_file, { object:, **(options[:locals] || {}) }, app_name:)
79
+ # Since controller sits in the controllers folder we can get parent folder of it and pull out name of the app
80
+ app_name_prefix = app_name == 'app' ? '' : "#{app_name}_"
81
+ layout_path = "#{lib}#{app_name}/views/#{app_name_prefix}#{options[:layout] || 'layout'}.erb"
82
+ whole_erb = if File.exist?(layout_path)
77
83
  context = Object.new
78
84
  context.define_singleton_method(:_yield_template) { view }
79
85
  layout = File.read(layout_path)
@@ -86,9 +92,9 @@ module Rubee
86
92
  end
87
93
  end
88
94
 
89
- def render_template(file_name, locals = {})
95
+ def render_template(file_name, locals = {}, **options)
90
96
  lib = Rubee::PROJECT_NAME == 'rubee' ? 'lib/' : ''
91
- path = "#{lib}app/views/#{file_name}.erb"
97
+ path = "#{lib}#{options[:app_name] || 'app'}/views/#{file_name}.erb"
92
98
  erb_template = ERB.new(File.read(path))
93
99
 
94
100
  erb_template.result(binding)
@@ -3,8 +3,8 @@ require 'date'
3
3
 
4
4
  module Rubee
5
5
  module AuthTokenable
6
- KEY = "secret#{Date.today}".freeze # Feel free to cusomtize it
7
- EXPIRE = 3600 # 1 hour
6
+ KEY = "secret#{Date.today}".freeze unless defined?(KEY) # Feel free to cusomtize it
7
+ EXPIRE = 3600 unless defined?(EXPIRE)
8
8
 
9
9
  def self.included(base)
10
10
  base.include(Middlewarable)