roda-project 0.1.12 → 0.1.13

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba772c511bc001bfa35bf4345638652b7291dd2e144003922fa85880e7dc03f3
4
- data.tar.gz: be9c85ed58652607a5614d7f37d0878085f7834f257cd2053d766d067fd71a44
3
+ metadata.gz: 3156e8ffc1fbf7c48c30c1224bfccd2bf66c775b90cb802ada325c2ef2a5607e
4
+ data.tar.gz: 02e04e9cb18a32f82cad2fceb59f1d567d33fe61330f1f0a26bb6e38ebf3b618
5
5
  SHA512:
6
- metadata.gz: 63ef03a38f04ae88bda73eb7d890c3d80fda95521695a4e7ad6f19bd30a84630bff9f4daa4b7a8d08818fe77a235241bcb60f48e58e060646b2cd8c3dad62fd0
7
- data.tar.gz: 9737a1545992102d0e82c5563cc0a29ae6c0c15d6b24bf07f16f493f5421b043b77be2b15ca2894157358f89b077e9cc9e7f42f84ed114fb9e59d8da0c92be38
6
+ metadata.gz: b2a91d0af7f801951d65f288a3fcbc0012b3779e6736dfdd94d7703377834cad15d8a8680293b1800a8293e90cfb59d8d3f971e1e67abedf682b32a432e9920a
7
+ data.tar.gz: b80916d58cdd4a167f8be8a9e5e43f4c56730457818b6cfee081ac4946d1e353284c7b6d087f9d030a03d7a705426e78fe871610dd5438dfe3c1c9bb1d426563
@@ -0,0 +1,120 @@
1
+ class Roda
2
+ module Project
3
+ module Bin
4
+ class Generators < ::Thor
5
+ class Service < Roda::Project::Bin::Generator
6
+ include Roda::Project::Helpers::Inflections
7
+
8
+ def call
9
+ unless valid_args?
10
+ puts "Usage: bin/roda g service <module|class> <name>"
11
+ exit 1
12
+ end
13
+
14
+ puts "* creating service"
15
+ create_service
16
+ create_service_spec
17
+ end
18
+
19
+ def create_service
20
+ ensure_and_get_path("app/services", service_relative_path)
21
+ filename = File.join("app/services", *name_segments[0..-2], "#{service_file_basename}.rb")
22
+ File.write(filename, code)
23
+
24
+ action_success_message(filename)
25
+ end
26
+
27
+ def create_service_spec
28
+ ensure_and_get_path("spec/app/services", service_relative_path)
29
+ filename = File.join("spec/app/services", *name_segments[0..-2], "#{service_file_basename}_spec.rb")
30
+ File.write(filename, spec_code)
31
+
32
+ action_success_message(filename)
33
+ end
34
+
35
+ def spec_code
36
+ depth = 2 + name_segments.length - 1
37
+ relative_prefix = "../" * depth
38
+ <<~RUBY
39
+ require_relative "#{relative_prefix}spec_helper"
40
+
41
+ describe #{qualified_class_name} do
42
+ end
43
+ RUBY
44
+ end
45
+
46
+ def code
47
+ leaf_keyword = service_type
48
+ if name_segments.length == 1
49
+ <<~RUBY
50
+ #{leaf_keyword} #{camelize(name_segments.last)}
51
+ end
52
+ RUBY
53
+ else
54
+ depth = name_segments.length - 1
55
+ leaf_indent = " " * depth
56
+ lines = [
57
+ "#{leaf_indent}#{leaf_keyword} #{camelize(name_segments.last)}",
58
+ "#{leaf_indent}end"
59
+ ]
60
+
61
+ name_segments[0..-2].map { |s| camelize(s) }.reverse.each_with_index do |mod, idx|
62
+ mod_indent = " " * (depth - 1 - idx)
63
+ lines = ["#{mod_indent}module #{mod}"] + lines + ["#{mod_indent}end"]
64
+ end
65
+
66
+ lines.join("\n") + "\n"
67
+ end
68
+ end
69
+
70
+ def name_segments
71
+ @name_segments ||= service_name.split("/")
72
+ end
73
+
74
+ def qualified_class_name
75
+ @qualified_class_name ||= name_segments.map { |s| camelize(s) }.join("::")
76
+ end
77
+
78
+ def service_relative_path
79
+ @service_relative_path ||= if name_segments.length > 1
80
+ File.join(*name_segments[0..-2], service_file_basename)
81
+ else
82
+ service_file_basename
83
+ end
84
+ end
85
+
86
+ def service_file_basename
87
+ @service_file_basename ||= underscore(name_segments.last)
88
+ end
89
+
90
+ def service_type
91
+ @service_type ||= parse_args[:type]
92
+ end
93
+
94
+ def service_name
95
+ @service_name ||= parse_args[:name]
96
+ end
97
+
98
+ def parse_args
99
+ @parse_args ||= begin
100
+ if %w[module class].include?(@args[0].to_s.downcase)
101
+ { type: @args[0].to_s.downcase, name: @args[1].to_s }
102
+ else
103
+ { type: "class", name: @args[0].to_s }
104
+ end
105
+ end
106
+ end
107
+
108
+ def valid_args?
109
+ return false unless %w[module class].include?(service_type)
110
+ return false if service_name.empty?
111
+ return false if service_name.include?(" ")
112
+ return false if service_name.match?(/\A\d/)
113
+
114
+ true
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -20,6 +20,15 @@ class Roda
20
20
  Model.new(context:, args:, options:).call
21
21
  end
22
22
 
23
+ desc "service", "Create service module or class and test scaffold"
24
+ def service(*args)
25
+ Service.new(context:, args:, options:).call
26
+ end
27
+
28
+ def self.exit_on_failure?
29
+ true
30
+ end
31
+
23
32
  private
24
33
 
25
34
  def context
@@ -26,14 +26,16 @@ class Roda
26
26
  puts "\n* create your database\n"
27
27
  puts "$ bin/roda db create"
28
28
  end
29
- puts "\nmigrate the database (use RACK_ENV to migrate 'test' or 'production' environments):\n\n"
30
- puts "$ bin/roda db migrate"
29
+ if @context.rodauth?
30
+ puts "\nmigrate the database (use RACK_ENV to migrate 'test' or 'production' environments):\n\n"
31
+ puts "$ bin/roda db migrate"
32
+ end
31
33
  end
32
34
  puts "\nrun and watch the project in dev mode:\n"
33
35
  puts "\n$ bin/roda dev"
34
36
  if @context.fullstack?
35
37
  puts "\ncompile and watch assets:\n"
36
- puts "\n$ bin/roda assets:dev"
38
+ puts "\n$ bin/roda assets -w"
37
39
  end
38
40
  puts "\nrun 'bin/roda' inside #{@context.project_name} to see all available tasks\n\n"
39
41
  rescue TTY::Reader::InputInterrupt
@@ -102,6 +104,8 @@ class Roda
102
104
  erb_cp_dir("front-end", "app/assets")
103
105
  erb_cp_file("front-end", "esbuild.js")
104
106
  erb_cp_file("front-end", "package.json")
107
+ cp_dir("front-end", "app/config/locales")
108
+ action_success_message("app/config/locales")
105
109
  cp_dir("front-end", "app/views")
106
110
  action_success_message("app/views")
107
111
  cp_dir("front-end", "public/assets")
@@ -1,4 +1,4 @@
1
- require 'fileutils'
1
+ require "fileutils"
2
2
 
3
3
  class Roda
4
4
  module Project
@@ -12,7 +12,7 @@ class Roda
12
12
  success = system("git clone --depth 1 #{repo_url} #{@context.project_name}")
13
13
  abort("\nCould not download the template.") unless success
14
14
 
15
- git_dir = File.join(@context.project_name, '.git')
15
+ git_dir = File.join(@context.project_name, ".git")
16
16
  if Dir.exist?(git_dir)
17
17
  FileUtils.rm_rf(git_dir)
18
18
  puts "\n* Cleaned up template git history.\n\n"
@@ -24,10 +24,9 @@ class Roda
24
24
  system(setup_command)
25
25
 
26
26
  action_success_message(setup_command, "run")
27
- puts "\n Setup is done! follow the link below to learn about this template:"
28
- puts "\n #{repo}"
27
+ puts "\nSetup is done! follow the link below to learn about this template:"
28
+ puts "\n#{repo_url}"
29
29
  end
30
30
  end
31
31
  end
32
32
  end
33
-
@@ -108,7 +108,7 @@ class Roda
108
108
  def root_example
109
109
  return 'view("index")' if fullstack?
110
110
 
111
- '{ message: "#{t.hello.message}" }'
111
+ '{ message: "hello world" }'
112
112
  end
113
113
  # rubocop:enable Lint/InterpolationCheck
114
114
 
@@ -69,7 +69,6 @@ class <%= context.const_project_name %> < Roda
69
69
  # r.i18n_set_locale_from(:session)<% end %>
70
70
  <% if context.rodauth? %>
71
71
  r.rodauth
72
- rodauth.require_authentication
73
72
  <% end %>
74
73
  r.root do
75
74
  <%= context.root_example %>
@@ -1,9 +1,18 @@
1
- Act as a Ruby specialist.
1
+ # Project Code Generation Rules
2
2
 
3
- This is a web application written using the Roda web framework
3
+ ## Core Stack
4
+ - **Web Framework**: Roda (STRICT: Do NOT use Rails, ActionController, or Sinatra conventions).
5
+ - **Autoloader**: Zeitwerk (Do NOT manually `require` files located in the `app/` directory. Only require standard libraries or gems).
6
+ - **No Rails Magic:** Do not use `ActiveSupport` methods (like `.present?` or `.blank?`) unless the gem is explicitly in the Gemfile.
4
7
 
5
- ## Project guidelines
8
+ ## Code Style & Conventions
6
9
 
7
- - **Never** deliver code without tests validating the code (execute with `rake test`)
8
- - **Never** write long comments in the code
9
- - **Always** use clean code
10
+ ### 1. Types
11
+ - Rely on standard Ruby 3.x patterns. Use explicit YARD docs for method signatures.
12
+
13
+ ### 2. Routing (Roda)
14
+ - Do not create deep routing blocks. Use Roda's tree routing efficiently (`r.on`, `r.is`, `r.get`, `r.post`).
15
+
16
+ ### 3. Boot Sequence & Initialization
17
+ - **`boot.rb`**: The absolute starting point. It sets up `Bundler` and boots `Zeitwerk`.
18
+ - **`config.ru`**: The Rack entrypoint. It defines middleware and mounts the primary Roda application class defined in `app.rb`.
@@ -42,6 +42,10 @@ class CLI < Thor
42
42
  end
43
43
  end
44
44
  map "s" => "server"
45
+
46
+ def self.exit_on_failure?
47
+ true
48
+ end
45
49
  end
46
50
 
47
51
  CLI.start(ARGV)
@@ -0,0 +1,47 @@
1
+ # Project Code Generation Rules
2
+
3
+ ## Core Stack
4
+ - **Web Framework**: Roda (STRICT: Do NOT use Rails, ActionController, or Sinatra conventions).
5
+ - **No Rails Magic:** Do not use `ActiveSupport` methods (like `.present?` or `.blank?`) unless the gem is explicitly in the Gemfile.<% if context.database %>
6
+ - **ORM**: Sequel (STRICT: Do NOT use ActiveRecord. Use Sequel Models, Datasets, and Migrations).<% end %>
7
+ - **Autoloader**: Zeitwerk (Do NOT manually `require` files located in the `app/` directory. Only require standard libraries or gems).<% if context.fullstack? %>
8
+ - **Frontend**: esbuild + ERB (Fullstack mode).<% end %>
9
+
10
+ ## Code Style & Conventions
11
+
12
+ ### 1. Types
13
+ - Rely on standard Ruby 3.x patterns. Use explicit YARD docs for method signatures.
14
+
15
+ ### 2. Routing (Roda)
16
+ - The main app resides in `app/[project_name].rb`.
17
+ - Sub-routes MUST be implemented using Roda **Hash Branches** (`hash_branches` plugin) and placed in `app/routes/`.
18
+ - Do not create deep routing blocks. Use Roda's tree routing efficiently (`r.on`, `r.is`, `r.get`, `r.post`).<% if context.database %>
19
+
20
+ ### 3. Database & Models (Sequel)
21
+ - Models reside in `app/models/`.
22
+ - Migrations reside in `db/migrations/` and MUST use sequential numbering (e.g., `001_...`).
23
+ - When writing database queries, use Sequel's dataset methods (e.g., `where`, `select`, `exclude`, `join`). Do not use string interpolation for SQL queries.
24
+
25
+ <% end %><% if context.fullstack? %>### 4. Views & Presentation
26
+ - ERB templates go in `app/views/`.
27
+
28
+ <% end %>### 5. Testing
29
+ - Specs reside in `spec/app/`.
30
+ - Use `rack-test` for route testing.
31
+ - Ensure database transactions are rolled back after each test block.
32
+ - Mirror the file structure of `app/` inside `spec/app/` (e.g., `app/models/user.rb` -> `spec/app/models/user_spec.rb`).
33
+
34
+ ### 6. Boot Sequence & Initialization
35
+ - **`boot.rb`**: The absolute starting point. It sets up `Bundler`, boots `Zeitwerk` for autoloading the `app/` directory, and loads service providers from `app/config/providers/`.
36
+ - **`config.ru`**: The Rack entrypoint. It defines middleware (like `Rack::LiveReload` in development) and mounts the primary Roda application class defined in `app/[project_name].rb`.
37
+
38
+ ### 7. Tooling
39
+ - Do NOT run `bundle exec rake` for scaffolding/servers. The project uses a custom CLI: `bin/roda`. Suggest commands using `bin/roda` (e.g., generators, running the server).
40
+
41
+ #### Generators
42
+ - To maintain consistency and avoid manual boilerplate, the project uses custom generators for scaffolding new features. These automatically hook into Zeitwerk's expectations, Sequel's DSL, and Roda's Hash Branches.
43
+ - **`bin/roda g routes <branch> [options]`**: Scaffolds a Roda hash branch file in `app/routes/`, matching spec files, and ERB views (for GET routes). Supports nested paths (e.g., `api/v1`) and bare verbs (e.g., `books :get :post`).<% if context.database %>
44
+ - **`bin/roda g model <name> [fields]`**: Generates a Sequel ORM model class, a matching model spec, and a timestamped Sequel migration. Supports namespacing (`Admin/User`) and auto-pluralizes table names.
45
+ - **`bin/roda g migration <name> [fields]`**: Creates a timestamped Sequel migration. It automatically detects intent from the name (e.g., `add_status_to_users` generates the `alter_table` block) and maps Rails-like field types (e.g., `user:references`, `body:text`) to Sequel schema methods.
46
+ <% end %>- **`bin/roda g service <type> <name>`**: Scaffolds a class or module service in `app/services/` and a matching spec. Defaults to `class` if type is omitted.
47
+
@@ -48,5 +48,6 @@ group :development do
48
48
  gem "minitest"
49
49
  gem "minitest-hooks"
50
50
  <% else %>
51
- gem "rspec"<% end %>
51
+ gem "rspec"<% end %><% if context.database? %>
52
+ gem "sequel-annotate"<% end %>
52
53
  end
@@ -5,7 +5,7 @@ module Config
5
5
  secret: ENV["SESSION_SECRET"] || 'DEV-ONLY-8<FQF8HiF)>l0hbPk£vBQ#IrYsoO}14k\l+-/gIU[j}l0hbPk£vBQ#IrY',
6
6
  environment:,<% if context.rodauth? %>
7
7
  hmac_secret: ENV["RODAUTH_HMAC_SECRET"] || 'DEV-ONLY',
8
- jwt_secret: ENV["JWT_SECRET"] || 'DEV-ONLY',<% end %><% if context.fullstack? %>
8
+ jwt_secret: ENV["JWT_SECRET"] || 'DEV-ONLY'<% end %><% if context.fullstack? %>,
9
9
  i18n: {
10
10
  translations: ["app/config/locales", "app/config/locales/foo"],
11
11
  locale: ["en", "pt-br"]
@@ -24,8 +24,8 @@ module Config
24
24
  escape: true, # For Erubi templates, escapes %= by default (use %== for unescaped
25
25
  chain_appends: true, # For Erubi templates, improves performance
26
26
  skip_compiled_encoding_detection: true, # Unless you need encodings explicitly specified
27
- }<% end %>
28
- }
27
+ }
28
+ }<% end %>
29
29
  }
30
30
  end
31
31
 
@@ -1,5 +1,6 @@
1
1
  class <%= context.const_project_name %>
2
- hash_branch "foo" do |r|
2
+ hash_branch "foo" do |r|<% if context.rodauth? %>
3
+ rodauth.require_authentication<% end %>
3
4
  r.get "bar" do
4
5
  <%= context.foo_bar_example %>
5
6
  end
@@ -3,7 +3,7 @@ require "thor"
3
3
  require "roda/project/bin/generators"
4
4
  <% if context.database? %>
5
5
  class Db < Thor
6
- desc "migrate", "Migrate the database. Optional: provide target version, e.g., 'roda db:migrate 1'"
6
+ desc "migrate", "Migrate the database (use RACK_ENV to migrate 'test' or 'production' environments). Optional: provide target version, e.g., 'roda db:migrate 1'"
7
7
  option :target, type: :numeric, aliases: "-t", desc: "provide target version"
8
8
  def migrate
9
9
  require_db_deps
@@ -148,6 +148,10 @@ class CLI < Thor
148
148
  desc "generate", "Commands to generate code"
149
149
  <%= context.to_thor_option %>
150
150
  subcommand "generate", Roda::Project::Bin::Generators
151
+
152
+ def self.exit_on_failure?
153
+ true
154
+ end
151
155
  end
152
156
 
153
157
  CLI.start(ARGV)
@@ -32,4 +32,10 @@ Providers::Mailer.boot
32
32
  Providers::Logger.boot
33
33
 
34
34
  # Consts<% if context.database %>
35
- DB = Providers::DB::Conn.get<% end %>
35
+ DB = Providers::DB::Conn.get
36
+
37
+ # Automatic models annotations in dev environment
38
+ if Config.not_production?
39
+ require 'sequel/annotate'
40
+ Sequel::Annotate.annotate(Dir['app/models/*.rb'], position: :before, border: true) rescue nil
41
+ end<% end %>
@@ -2,6 +2,6 @@
2
2
 
3
3
  class Roda
4
4
  module Project
5
- VERSION = "0.1.12"
5
+ VERSION = "0.1.13"
6
6
  end
7
7
  end
data/lib/roda/project.rb CHANGED
@@ -71,6 +71,8 @@ require_relative "project/bin/generators/migration/code_builder"
71
71
  require_relative "project/bin/generators/model"
72
72
  # Generators/routes
73
73
  require_relative "project/bin/generators/routes"
74
+ # Generators/service
75
+ require_relative "project/bin/generators/service"
74
76
  # Generators/base
75
77
  require_relative "project/bin/generators"
76
78
  # Version
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda-project
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.12
4
+ version: 0.1.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henrique F. Teixeira
@@ -85,6 +85,7 @@ files:
85
85
  - lib/roda/project/bin/generators/migration/field_parser.rb
86
86
  - lib/roda/project/bin/generators/model.rb
87
87
  - lib/roda/project/bin/generators/routes.rb
88
+ - lib/roda/project/bin/generators/service.rb
88
89
  - lib/roda/project/cli.rb
89
90
  - lib/roda/project/generator.rb
90
91
  - lib/roda/project/helpers/ids.rb
@@ -104,15 +105,11 @@ files:
104
105
  - lib/roda/project/templates/base/minimal/boot.rb
105
106
  - lib/roda/project/templates/base/minimal/config.ru
106
107
  - lib/roda/project/templates/base/minimal/public/exception_page.css
107
- - lib/roda/project/templates/base/scaffold/AGENTS.md
108
+ - lib/roda/project/templates/base/scaffold/AGENTS.md.erb
108
109
  - lib/roda/project/templates/base/scaffold/Gemfile.erb
109
110
  - lib/roda/project/templates/base/scaffold/Guardfile
110
111
  - lib/roda/project/templates/base/scaffold/README.md
111
112
  - lib/roda/project/templates/base/scaffold/app/config/config.rb.erb
112
- - lib/roda/project/templates/base/scaffold/app/config/locales/en.yml
113
- - lib/roda/project/templates/base/scaffold/app/config/locales/foo/en.yml
114
- - lib/roda/project/templates/base/scaffold/app/config/locales/foo/pt-br.yml
115
- - lib/roda/project/templates/base/scaffold/app/config/locales/pt-br.yml
116
113
  - lib/roda/project/templates/base/scaffold/app/config/providers/logger.rb
117
114
  - lib/roda/project/templates/base/scaffold/app/config/providers/mailer.rb
118
115
  - lib/roda/project/templates/base/scaffold/app/routes/foo.rb.erb
@@ -125,6 +122,10 @@ files:
125
122
  - lib/roda/project/templates/front-end/app/assets/css/app.css
126
123
  - lib/roda/project/templates/front-end/app/assets/js/app.js
127
124
  - lib/roda/project/templates/front-end/app/assets/js/some/foo.js
125
+ - lib/roda/project/templates/front-end/app/config/locales/en.yml
126
+ - lib/roda/project/templates/front-end/app/config/locales/foo/en.yml
127
+ - lib/roda/project/templates/front-end/app/config/locales/foo/pt-br.yml
128
+ - lib/roda/project/templates/front-end/app/config/locales/pt-br.yml
128
129
  - lib/roda/project/templates/front-end/app/views/foo/bar.erb
129
130
  - lib/roda/project/templates/front-end/app/views/index.erb
130
131
  - lib/roda/project/templates/front-end/app/views/layout.erb
@@ -1,9 +0,0 @@
1
- Act as a Ruby specialist.
2
-
3
- This is a web application written using the Roda web framework
4
-
5
- ## Project guidelines
6
-
7
- - **Never** deliver code without tests validating the code (execute with `rake test`)
8
- - **Never** write long comments in the code
9
- - **Always** use clean code