├── .dockerignore ├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ └── .keep │ └── stylesheets │ │ └── application.css ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── components │ ├── application_component.rb │ ├── benchmark_component.rb │ ├── first_component.html.rb │ ├── first_component.rb │ ├── second_component.rb │ ├── third_component.rb │ └── third_component │ │ └── third_component.html.rb ├── controllers │ ├── application_controller.rb │ ├── benchmark_controller.rb │ ├── concerns │ │ └── .keep │ └── home_controller.rb ├── helpers │ └── application_helper.rb ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ └── concerns │ │ └── .keep ├── phlex │ └── phlex_view.rb └── views │ ├── benchmark │ ├── normal_html.html.erb │ ├── ruby_2html.html.rb │ └── slim_html.html.slim │ ├── home │ ├── form.html.rb │ ├── index.html.erb │ └── rb_files.html.rb │ ├── layouts │ ├── application.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb │ ├── pwa │ ├── manifest.json.erb │ └── service-worker.js │ └── shared │ ├── _footer.html.rb │ └── _navbar.html.erb ├── bin ├── brakeman ├── bundle ├── docker-entrypoint ├── rails ├── rake ├── rubocop └── setup ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── assets.rb │ ├── content_security_policy.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ └── permissions_policy.rb ├── locales │ └── en.yml ├── puma.rb ├── routes.rb └── storage.yml ├── db └── seeds.rb ├── ext └── ruby2html │ ├── extconf.rb │ └── ruby2html.c ├── lefthook.yml ├── lib ├── assets │ └── .keep ├── gem │ ├── ruby2html.rb │ └── ruby2html │ │ ├── component_helper.rb │ │ ├── html_beautifier_middleware.rb │ │ ├── rails_components │ │ ├── base_component.rb │ │ ├── button_to.rb │ │ ├── form_with.rb │ │ ├── image_tag.rb │ │ └── link_to.rb │ │ ├── rails_helper.rb │ │ ├── railtie.rb │ │ ├── render.rb │ │ └── version.rb └── tasks │ └── .keep ├── log └── .keep ├── public ├── 404.html ├── 406-unsupported-browser.html ├── 422.html ├── 500.html ├── icon.png ├── icon.svg └── robots.txt ├── ruby2html.gemspec ├── spec ├── benchmarks │ └── requests_spec.rb ├── features │ └── home_spec.rb ├── gem │ └── render_spec.rb ├── rails_helper.rb └── spec_helper.rb ├── storage └── .keep ├── tmp ├── .keep ├── pids │ └── .keep └── storage │ └── .keep └── vendor └── .keep /.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. 2 | 3 | # Ignore git directory. 4 | /.git/ 5 | /.gitignore 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files (except templates). 11 | /.env* 12 | !/.env*.erb 13 | 14 | # Ignore all default key files. 15 | /config/master.key 16 | /config/credentials/*.key 17 | 18 | # Ignore all logfiles and tempfiles. 19 | /log/* 20 | /tmp/* 21 | !/log/.keep 22 | !/tmp/.keep 23 | 24 | # Ignore pidfiles, but keep the directory. 25 | /tmp/pids/* 26 | !/tmp/pids/.keep 27 | 28 | # Ignore storage (uploaded files in development and any SQLite databases). 29 | /storage/* 30 | !/storage/.keep 31 | /tmp/storage/* 32 | !/tmp/storage/.keep 33 | 34 | # Ignore assets. 35 | /node_modules/ 36 | /app/assets/builds/* 37 | !/app/assets/builds/.keep 38 | /public/assets 39 | 40 | # Ignore CI service files. 41 | /.github 42 | 43 | # Ignore development files 44 | /.devcontainer 45 | 46 | # Ignore Docker-related files 47 | /.dockerignore 48 | /Dockerfile* 49 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | # Mark any vendored files as having been vendored. 7 | vendor/* linguist-vendored 8 | config/credentials/*.yml.enc diff=rails_credentials 9 | config/credentials.yml.enc diff=rails_credentials 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [ main ] 7 | 8 | jobs: 9 | ruby_check: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Install system dependencies 17 | run: | 18 | sudo apt-get update 19 | sudo apt-get install -y build-essential 20 | 21 | - name: Set up Ruby 22 | uses: ruby/setup-ruby@v1 23 | with: 24 | ruby-version: .ruby-version 25 | bundler-cache: true 26 | 27 | - name: Compile C extension 28 | run: bundle exec rake compile 29 | 30 | - name: RSPEC 31 | run: bundle exec rspec 32 | 33 | - name: Scan for security vulnerabilities in Ruby dependencies 34 | run: bin/brakeman --no-pager 35 | 36 | lint: 37 | runs-on: ubuntu-latest 38 | steps: 39 | - name: Checkout code 40 | uses: actions/checkout@v4 41 | 42 | - name: Set up Ruby 43 | uses: ruby/setup-ruby@v1 44 | with: 45 | ruby-version: .ruby-version 46 | bundler-cache: true 47 | 48 | - name: Lint code for consistent style 49 | run: bin/rubocop --parallel -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # Temporary files generated by your text editor or operating system 4 | # belong in git's global ignore instead: 5 | # `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore` 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files (except templates). 11 | /.env* 12 | !/.env*.erb 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp/* 17 | !/log/.keep 18 | !/tmp/.keep 19 | 20 | # Ignore pidfiles, but keep the directory. 21 | /tmp/pids/* 22 | !/tmp/pids/ 23 | !/tmp/pids/.keep 24 | 25 | # Ignore storage (uploaded files in development and any SQLite databases). 26 | /storage/* 27 | !/storage/.keep 28 | /tmp/storage/* 29 | !/tmp/storage/ 30 | !/tmp/storage/.keep 31 | 32 | /public/assets 33 | 34 | # Ignore master key for decrypting credentials and more. 35 | /config/master.key 36 | .idea/ 37 | *.gem 38 | *.so -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --format progress -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | rubocop-rails_config: 3 | - config/rails.yml 4 | 5 | AllCops: 6 | TargetRubyVersion: 3.0 7 | 8 | Style/ClassAndModuleChildren: 9 | EnforcedStyle: nested 10 | 11 | Lint/Debugger: 12 | Enabled: true 13 | 14 | Style/StringLiterals: 15 | Enabled: true 16 | EnforcedStyle: single_quotes 17 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.4.1 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gem 'rails', '~> 7.1', '>= 7.1.3.4' 6 | gem 'sprockets-rails' 7 | gem 'sqlite3', '>= 1.4' 8 | gem 'puma', '>= 5.0' 9 | gem 'tzinfo-data', platforms: %i[ windows jruby ] 10 | gem 'htmlbeautifier' 11 | gem 'bootsnap', require: false 12 | 13 | group :development, :test do 14 | gem 'brakeman', require: false 15 | end 16 | 17 | group :development do 18 | gem 'web-console' 19 | end 20 | 21 | gem 'rubocop-rails_config', '~> 1.16', require: false 22 | 23 | gem 'pry-rails', '~> 0.3.11' 24 | 25 | gem 'view_component', '~> 3.13' 26 | 27 | gem 'rspec-rails', '~> 6.1', '>= 6.1.3' 28 | 29 | gem 'capybara', '~> 3.40' 30 | 31 | gem 'lefthook', '~> 1.7' 32 | 33 | gem 'faker', '~> 3.4', '>= 3.4.2' 34 | 35 | gem 'slim-rails', '~> 3.6', '>= 3.6.3' 36 | 37 | gem 'phlex-rails', '~> 1.2', '>= 1.2.1' 38 | 39 | gem 'rake-compiler', '~> 1.2', require: false 40 | 41 | group :test do 42 | gem 'benchmark-ips' 43 | end 44 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (7.2.2.1) 5 | actionpack (= 7.2.2.1) 6 | activesupport (= 7.2.2.1) 7 | nio4r (~> 2.0) 8 | websocket-driver (>= 0.6.1) 9 | zeitwerk (~> 2.6) 10 | actionmailbox (7.2.2.1) 11 | actionpack (= 7.2.2.1) 12 | activejob (= 7.2.2.1) 13 | activerecord (= 7.2.2.1) 14 | activestorage (= 7.2.2.1) 15 | activesupport (= 7.2.2.1) 16 | mail (>= 2.8.0) 17 | actionmailer (7.2.2.1) 18 | actionpack (= 7.2.2.1) 19 | actionview (= 7.2.2.1) 20 | activejob (= 7.2.2.1) 21 | activesupport (= 7.2.2.1) 22 | mail (>= 2.8.0) 23 | rails-dom-testing (~> 2.2) 24 | actionpack (7.2.2.1) 25 | actionview (= 7.2.2.1) 26 | activesupport (= 7.2.2.1) 27 | nokogiri (>= 1.8.5) 28 | racc 29 | rack (>= 2.2.4, < 3.2) 30 | rack-session (>= 1.0.1) 31 | rack-test (>= 0.6.3) 32 | rails-dom-testing (~> 2.2) 33 | rails-html-sanitizer (~> 1.6) 34 | useragent (~> 0.16) 35 | actiontext (7.2.2.1) 36 | actionpack (= 7.2.2.1) 37 | activerecord (= 7.2.2.1) 38 | activestorage (= 7.2.2.1) 39 | activesupport (= 7.2.2.1) 40 | globalid (>= 0.6.0) 41 | nokogiri (>= 1.8.5) 42 | actionview (7.2.2.1) 43 | activesupport (= 7.2.2.1) 44 | builder (~> 3.1) 45 | erubi (~> 1.11) 46 | rails-dom-testing (~> 2.2) 47 | rails-html-sanitizer (~> 1.6) 48 | activejob (7.2.2.1) 49 | activesupport (= 7.2.2.1) 50 | globalid (>= 0.3.6) 51 | activemodel (7.2.2.1) 52 | activesupport (= 7.2.2.1) 53 | activerecord (7.2.2.1) 54 | activemodel (= 7.2.2.1) 55 | activesupport (= 7.2.2.1) 56 | timeout (>= 0.4.0) 57 | activestorage (7.2.2.1) 58 | actionpack (= 7.2.2.1) 59 | activejob (= 7.2.2.1) 60 | activerecord (= 7.2.2.1) 61 | activesupport (= 7.2.2.1) 62 | marcel (~> 1.0) 63 | activesupport (7.2.2.1) 64 | base64 65 | benchmark (>= 0.3) 66 | bigdecimal 67 | concurrent-ruby (~> 1.0, >= 1.3.1) 68 | connection_pool (>= 2.2.5) 69 | drb 70 | i18n (>= 1.6, < 2) 71 | logger (>= 1.4.2) 72 | minitest (>= 5.1) 73 | securerandom (>= 0.3) 74 | tzinfo (~> 2.0, >= 2.0.5) 75 | addressable (2.8.7) 76 | public_suffix (>= 2.0.2, < 7.0) 77 | ast (2.4.2) 78 | base64 (0.2.0) 79 | benchmark (0.4.0) 80 | benchmark-ips (2.14.0) 81 | bigdecimal (3.1.9) 82 | bindex (0.8.1) 83 | bootsnap (1.18.4) 84 | msgpack (~> 1.2) 85 | brakeman (7.0.0) 86 | racc 87 | builder (3.3.0) 88 | capybara (3.40.0) 89 | addressable 90 | matrix 91 | mini_mime (>= 0.1.3) 92 | nokogiri (~> 1.11) 93 | rack (>= 1.6.0) 94 | rack-test (>= 0.6.3) 95 | regexp_parser (>= 1.5, < 3.0) 96 | xpath (~> 3.2) 97 | coderay (1.1.3) 98 | concurrent-ruby (1.3.5) 99 | connection_pool (2.5.0) 100 | crass (1.0.6) 101 | date (3.4.1) 102 | diff-lcs (1.5.1) 103 | drb (2.2.1) 104 | erubi (1.13.1) 105 | faker (3.5.1) 106 | i18n (>= 1.8.11, < 2) 107 | globalid (1.2.1) 108 | activesupport (>= 6.1) 109 | htmlbeautifier (1.4.3) 110 | i18n (1.14.7) 111 | concurrent-ruby (~> 1.0) 112 | io-console (0.8.0) 113 | irb (1.15.1) 114 | pp (>= 0.6.0) 115 | rdoc (>= 4.0.0) 116 | reline (>= 0.4.2) 117 | json (2.9.1) 118 | language_server-protocol (3.17.0.4) 119 | lefthook (1.10.10) 120 | logger (1.6.5) 121 | loofah (2.24.0) 122 | crass (~> 1.0.2) 123 | nokogiri (>= 1.12.0) 124 | mail (2.8.1) 125 | mini_mime (>= 0.1.1) 126 | net-imap 127 | net-pop 128 | net-smtp 129 | marcel (1.0.4) 130 | matrix (0.4.2) 131 | method_source (1.1.0) 132 | mini_mime (1.1.5) 133 | mini_portile2 (2.8.8) 134 | minitest (5.25.4) 135 | msgpack (1.8.0) 136 | net-imap (0.5.6) 137 | date 138 | net-protocol 139 | net-pop (0.1.2) 140 | net-protocol 141 | net-protocol (0.2.2) 142 | timeout 143 | net-smtp (0.5.1) 144 | net-protocol 145 | nio4r (2.7.4) 146 | nokogiri (1.18.2) 147 | mini_portile2 (~> 2.8.2) 148 | racc (~> 1.4) 149 | nokogiri (1.18.2-aarch64-linux-gnu) 150 | racc (~> 1.4) 151 | nokogiri (1.18.2-aarch64-linux-musl) 152 | racc (~> 1.4) 153 | nokogiri (1.18.2-arm-linux-gnu) 154 | racc (~> 1.4) 155 | nokogiri (1.18.2-arm-linux-musl) 156 | racc (~> 1.4) 157 | nokogiri (1.18.2-arm64-darwin) 158 | racc (~> 1.4) 159 | nokogiri (1.18.2-x86_64-linux-gnu) 160 | racc (~> 1.4) 161 | nokogiri (1.18.2-x86_64-linux-musl) 162 | racc (~> 1.4) 163 | parallel (1.26.3) 164 | parser (3.3.7.1) 165 | ast (~> 2.4.1) 166 | racc 167 | phlex (1.11.0) 168 | phlex-rails (1.2.2) 169 | phlex (>= 1.10, < 2) 170 | railties (>= 6.1, < 9) 171 | pp (0.6.2) 172 | prettyprint 173 | prettyprint (0.2.0) 174 | pry (0.15.2) 175 | coderay (~> 1.1) 176 | method_source (~> 1.0) 177 | pry-rails (0.3.11) 178 | pry (>= 0.13.0) 179 | psych (5.2.3) 180 | date 181 | stringio 182 | public_suffix (6.0.1) 183 | puma (6.6.0) 184 | nio4r (~> 2.0) 185 | racc (1.8.1) 186 | rack (3.1.9) 187 | rack-session (2.1.0) 188 | base64 (>= 0.1.0) 189 | rack (>= 3.0.0) 190 | rack-test (2.2.0) 191 | rack (>= 1.3) 192 | rackup (2.2.1) 193 | rack (>= 3) 194 | rails (7.2.2.1) 195 | actioncable (= 7.2.2.1) 196 | actionmailbox (= 7.2.2.1) 197 | actionmailer (= 7.2.2.1) 198 | actionpack (= 7.2.2.1) 199 | actiontext (= 7.2.2.1) 200 | actionview (= 7.2.2.1) 201 | activejob (= 7.2.2.1) 202 | activemodel (= 7.2.2.1) 203 | activerecord (= 7.2.2.1) 204 | activestorage (= 7.2.2.1) 205 | activesupport (= 7.2.2.1) 206 | bundler (>= 1.15.0) 207 | railties (= 7.2.2.1) 208 | rails-dom-testing (2.2.0) 209 | activesupport (>= 5.0.0) 210 | minitest 211 | nokogiri (>= 1.6) 212 | rails-html-sanitizer (1.6.2) 213 | loofah (~> 2.21) 214 | nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) 215 | railties (7.2.2.1) 216 | actionpack (= 7.2.2.1) 217 | activesupport (= 7.2.2.1) 218 | irb (~> 1.13) 219 | rackup (>= 1.0.0) 220 | rake (>= 12.2) 221 | thor (~> 1.0, >= 1.2.2) 222 | zeitwerk (~> 2.6) 223 | rainbow (3.1.1) 224 | rake (13.2.1) 225 | rake-compiler (1.2.9) 226 | rake 227 | rdoc (6.12.0) 228 | psych (>= 4.0.0) 229 | regexp_parser (2.10.0) 230 | reline (0.6.0) 231 | io-console (~> 0.5) 232 | rspec-core (3.13.3) 233 | rspec-support (~> 3.13.0) 234 | rspec-expectations (3.13.3) 235 | diff-lcs (>= 1.2.0, < 2.0) 236 | rspec-support (~> 3.13.0) 237 | rspec-mocks (3.13.2) 238 | diff-lcs (>= 1.2.0, < 2.0) 239 | rspec-support (~> 3.13.0) 240 | rspec-rails (6.1.5) 241 | actionpack (>= 6.1) 242 | activesupport (>= 6.1) 243 | railties (>= 6.1) 244 | rspec-core (~> 3.13) 245 | rspec-expectations (~> 3.13) 246 | rspec-mocks (~> 3.13) 247 | rspec-support (~> 3.13) 248 | rspec-support (3.13.2) 249 | rubocop (1.71.2) 250 | json (~> 2.3) 251 | language_server-protocol (>= 3.17.0) 252 | parallel (~> 1.10) 253 | parser (>= 3.3.0.2) 254 | rainbow (>= 2.2.2, < 4.0) 255 | regexp_parser (>= 2.9.3, < 3.0) 256 | rubocop-ast (>= 1.38.0, < 2.0) 257 | ruby-progressbar (~> 1.7) 258 | unicode-display_width (>= 2.4.0, < 4.0) 259 | rubocop-ast (1.38.0) 260 | parser (>= 3.3.1.0) 261 | rubocop-md (1.2.4) 262 | rubocop (>= 1.45) 263 | rubocop-minitest (0.36.0) 264 | rubocop (>= 1.61, < 2.0) 265 | rubocop-ast (>= 1.31.1, < 2.0) 266 | rubocop-packaging (0.5.2) 267 | rubocop (>= 1.33, < 2.0) 268 | rubocop-performance (1.23.1) 269 | rubocop (>= 1.48.1, < 2.0) 270 | rubocop-ast (>= 1.31.1, < 2.0) 271 | rubocop-rails (2.29.1) 272 | activesupport (>= 4.2.0) 273 | rack (>= 1.1) 274 | rubocop (>= 1.52.0, < 2.0) 275 | rubocop-ast (>= 1.31.1, < 2.0) 276 | rubocop-rails_config (1.16.0) 277 | rubocop (>= 1.57.0) 278 | rubocop-ast (>= 1.26.0) 279 | rubocop-md 280 | rubocop-minitest (~> 0.22) 281 | rubocop-packaging (~> 0.5) 282 | rubocop-performance (~> 1.11) 283 | rubocop-rails (~> 2.0) 284 | ruby-progressbar (1.13.0) 285 | securerandom (0.4.1) 286 | slim (5.2.1) 287 | temple (~> 0.10.0) 288 | tilt (>= 2.1.0) 289 | slim-rails (3.7.0) 290 | actionpack (>= 3.1) 291 | railties (>= 3.1) 292 | slim (>= 3.0, < 6.0, != 5.0.0) 293 | sprockets (4.2.1) 294 | concurrent-ruby (~> 1.0) 295 | rack (>= 2.2.4, < 4) 296 | sprockets-rails (3.5.2) 297 | actionpack (>= 6.1) 298 | activesupport (>= 6.1) 299 | sprockets (>= 3.0.0) 300 | sqlite3 (2.5.0-aarch64-linux-gnu) 301 | sqlite3 (2.5.0-aarch64-linux-musl) 302 | sqlite3 (2.5.0-arm-linux-gnu) 303 | sqlite3 (2.5.0-arm-linux-musl) 304 | sqlite3 (2.5.0-arm64-darwin) 305 | sqlite3 (2.5.0-x86-linux-gnu) 306 | sqlite3 (2.5.0-x86-linux-musl) 307 | sqlite3 (2.5.0-x86_64-linux-gnu) 308 | sqlite3 (2.5.0-x86_64-linux-musl) 309 | stringio (3.1.2) 310 | temple (0.10.3) 311 | thor (1.3.2) 312 | tilt (2.6.0) 313 | timeout (0.4.3) 314 | tzinfo (2.0.6) 315 | concurrent-ruby (~> 1.0) 316 | unicode-display_width (3.1.4) 317 | unicode-emoji (~> 4.0, >= 4.0.4) 318 | unicode-emoji (4.0.4) 319 | useragent (0.16.11) 320 | view_component (3.21.0) 321 | activesupport (>= 5.2.0, < 8.1) 322 | concurrent-ruby (~> 1.0) 323 | method_source (~> 1.0) 324 | web-console (4.2.1) 325 | actionview (>= 6.0.0) 326 | activemodel (>= 6.0.0) 327 | bindex (>= 0.4.0) 328 | railties (>= 6.0.0) 329 | websocket-driver (0.7.7) 330 | base64 331 | websocket-extensions (>= 0.1.0) 332 | websocket-extensions (0.1.5) 333 | xpath (3.2.0) 334 | nokogiri (~> 1.8) 335 | zeitwerk (2.7.1) 336 | 337 | PLATFORMS 338 | aarch64-linux 339 | aarch64-linux-gnu 340 | aarch64-linux-musl 341 | arm-linux 342 | arm-linux-gnu 343 | arm-linux-musl 344 | arm64-darwin 345 | x86-linux 346 | x86-linux-gnu 347 | x86-linux-musl 348 | x86_64-linux 349 | x86_64-linux-gnu 350 | x86_64-linux-musl 351 | 352 | DEPENDENCIES 353 | benchmark-ips 354 | bootsnap 355 | brakeman 356 | capybara (~> 3.40) 357 | faker (~> 3.4, >= 3.4.2) 358 | htmlbeautifier 359 | lefthook (~> 1.7) 360 | phlex-rails (~> 1.2, >= 1.2.1) 361 | pry-rails (~> 0.3.11) 362 | puma (>= 5.0) 363 | rails (~> 7.1, >= 7.1.3.4) 364 | rake-compiler (~> 1.2) 365 | rspec-rails (~> 6.1, >= 6.1.3) 366 | rubocop-rails_config (~> 1.16) 367 | slim-rails (~> 3.6, >= 3.6.3) 368 | sprockets-rails 369 | sqlite3 (>= 1.4) 370 | tzinfo-data 371 | view_component (~> 3.13) 372 | web-console 373 | 374 | BUNDLED WITH 375 | 2.5.14 376 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruby2html 🔮✨ 2 | 3 | Transform your view logic into elegant, semantic HTML with the power of pure Ruby! 🚀✨ 4 | 5 | ## 🌟 What is Ruby2html? 6 | 7 | Ruby2html is a magical gem that allows you to write your views in pure Ruby and automatically converts them into clean, well-formatted HTML. Say goodbye to messy ERB templates and hello to the full power of Ruby in your views! 🎉 8 | 9 | ## 🚀 Installation 10 | 11 | Add this line to your application's Gemfile: 12 | 13 | 14 | ```ruby 15 | gem 'ruby2html' 16 | ``` 17 | 18 | And then execute: 19 | 20 | $ bundle install 21 | 22 | Or install it yourself as: 23 | 24 | $ gem install ruby2html 25 | 26 | ## 🎨 Usage 27 | 28 | ### In your views 29 | 30 | File: `app/views/your_view.html.rb` 31 | 32 | ```ruby 33 | div class: 'container' do 34 | h1 'Welcome to Ruby2html! 🎉', class: 'main-title', 'data-controller': 'welcome' 35 | link_to 'Home Sweet Home 🏠', root_path, class: 'btn btn-primary', 'data-turbo': false 36 | 37 | @products.each do |product| 38 | h2 class: 'item-title', id: "product-#{product[:id]}" do 39 | product.title 40 | end 41 | p class: 'item-description' do 42 | product.description 43 | end 44 | end 45 | end 46 | 47 | plain '
Inline html
'.html_safe 48 | 49 | render partial: 'shared/navbar' 50 | ``` 51 | 52 | ### (Optional) Nicely Format the HTML for source inspection 53 | 54 | File: `config/environments/development.rb` or `config/environments/test.rb` 55 | ```ruby 56 | config.middleware.use Ruby2html::HtmlBeautifierMiddleware 57 | ``` 58 | 59 | #### Or use your current .erb views 60 | 61 | ### In your ApplicationController 62 | 63 | File: `app/controllers/application_controller.rb` 64 | 65 | ```ruby 66 | # frozen_string_literal: true 67 | 68 | class ApplicationController < ActionController::Base 69 | include Ruby2html::RailsHelper # to access the <%= html %> helper 70 | end 71 | ``` 72 | 73 | File: `app/views/your_view.html.erb` 74 | 75 | Replace your ERB with beautiful Ruby code: 76 | 77 | ```erb 78 | <%= 79 | html(self) do 80 | h1 "Welcome to Ruby2html! 🎉", class: 'main-title', 'data-controller': 'welcome' 81 | div id: 'content', class: 'container' do 82 | link_to 'Home Sweet Home 🏠', root_path, class: 'btn btn-primary', 'data-turbo': false 83 | end 84 | 85 | @items.each do |item| 86 | h2 class: 'item-title', id: "item-#{item[:id]}" do 87 | item.title 88 | end 89 | p class: 'item-description' do 90 | item.description 91 | end 92 | end 93 | 94 | plain "
Inline html
".html_safe 95 | 96 | render partial: 'shared/navbar' 97 | end 98 | %> 99 | ``` 100 | 101 | ### Benchmark 102 | 103 | ```bash 104 | ruby 3.3.4 (2024-07-09 revision be1089c8ec) +YJIT [x86_64-linux] 105 | Warming up -------------------------------------- 106 | GET /benchmark/html (ERB) 107 | 40.000 i/100ms 108 | GET /benchmark/ruby (Ruby2html templates .html.rb) 109 | 12.000 i/100ms 110 | GET /benchmark/ruby (Ruby2html + view components) 111 | 12.000 i/100ms 112 | GET /benchmark/slim (Slim) 113 | 46.000 i/100ms 114 | GET /benchmark/phlex (Phlex) 115 | 34.000 i/100ms 116 | Calculating ------------------------------------- 117 | GET /benchmark/html (ERB) 118 | 414.030 (± 2.4%) i/s - 24.840k in 60.032818s 119 | GET /benchmark/ruby (Ruby2html templates .html.rb) 120 | 124.973 (± 3.2%) i/s - 7.500k in 60.071485s 121 | GET /benchmark/ruby (Ruby2html + view components) 122 | 123.211 (± 4.1%) i/s - 7.380k in 60.000731s 123 | GET /benchmark/slim (Slim) 124 | 431.525 (± 9.0%) i/s - 25.668k in 60.103492s 125 | GET /benchmark/phlex (Phlex) 126 | 328.925 (± 7.0%) i/s - 19.618k in 60.019961s 127 | 128 | Comparison: 129 | GET /benchmark/slim (Slim): 431.5 i/s 130 | GET /benchmark/html (ERB): 414.0 i/s - same-ish: difference falls within error 131 | GET /benchmark/phlex (Phlex): 328.9 i/s - 1.31x slower 132 | GET /benchmark/ruby (Ruby2html templates .html.rb): 125.0 i/s - 3.45x slower 133 | GET /benchmark/ruby (Ruby2html + view components): 123.2 i/s - 3.50x slower 134 | ``` 135 | 136 | ### With ViewComponents 137 | 138 | Ruby2html seamlessly integrates with ViewComponents, offering flexibility in how you define your component's HTML structure. You can use the `call` method with Ruby2html syntax, or stick with traditional `.erb` template files. 139 | 140 | File: `app/components/application_component.rb` 141 | 142 | ```ruby 143 | # frozen_string_literal: true 144 | 145 | class ApplicationComponent < ViewComponent::Base 146 | include Ruby2html::ComponentHelper 147 | end 148 | ``` 149 | 150 | #### Option 1: Using `call` method with Ruby2html 151 | 152 | File: `app/components/greeting_component.rb` 153 | 154 | ```ruby 155 | # frozen_string_literal: true 156 | 157 | class GreetingComponent < ApplicationComponent 158 | def initialize(name) 159 | @name = name 160 | end 161 | 162 | def call 163 | html do 164 | h1 class: 'greeting', 'data-user': @name do 165 | "Hello, #{@name}! 👋" 166 | end 167 | p class: 'welcome-message' do 168 | 'Welcome to the wonderful world of Ruby2html!' 169 | end 170 | end 171 | end 172 | end 173 | ``` 174 | 175 | #### Option 2: Using traditional ERB template 176 | 177 | File: `app/components/farewell_component.rb` 178 | 179 | ```ruby 180 | # frozen_string_literal: true 181 | 182 | class FarewellComponent < ApplicationComponent 183 | def initialize(name) 184 | @name = name 185 | end 186 | end 187 | ``` 188 | 189 | File: `app/components/farewell_component.html.rb` 190 | 191 | ```rb 192 | div class: 'farewell' do 193 | h1 class: 'farewell-message' do 194 | "Goodbye, #{@name}! 👋" 195 | end 196 | p class: 'farewell-text' do 197 | 'We hope to see you again soon!' 198 | end 199 | end 200 | ``` 201 | 202 | This flexibility allows you to: 203 | - Use Ruby2html syntax for new components or when refactoring existing ones 204 | - Keep using familiar ERB templates where preferred 205 | - Mix and match approaches within your application as needed 206 | 207 | ### More Component Examples 208 | 209 | File: `app/components/first_component.rb` 210 | 211 | ```ruby 212 | # frozen_string_literal: true 213 | 214 | class FirstComponent < ApplicationComponent 215 | def initialize 216 | @item = 'Hello, World!' 217 | end 218 | 219 | def call 220 | html do 221 | h1 id: 'first-component-title' do 222 | 'first component' 223 | end 224 | div class: 'content-wrapper' do 225 | h2 'A subheading' 226 | end 227 | p class: 'greeting-text', 'data-testid': 'greeting' do 228 | @item 229 | end 230 | end 231 | end 232 | end 233 | ``` 234 | 235 | File: `app/components/second_component.rb` 236 | 237 | ```ruby 238 | # frozen_string_literal: true 239 | 240 | class SecondComponent < ApplicationComponent 241 | def call 242 | html do 243 | h1 class: 'my-class', id: 'second-component-title', 'data-controller': 'second' do 244 | 'second component' 245 | end 246 | link_to 'Home', root_path, class: 'nav-link', 'data-turbo-frame': false 247 | end 248 | end 249 | end 250 | ``` 251 | 252 | ## Without Rails 253 | ```ruby 254 | renderer = Ruby2html::Render.new(nil) do # context by default is nil, you can use self or any other object 255 | html do 256 | head do 257 | title 'Ruby2html Example' 258 | end 259 | body do 260 | h1 'Hello, World!' 261 | end 262 | end 263 | end 264 | 265 | puts renderer.render # => "Ruby2html Example

Hello, World!

" 266 | ``` 267 | 268 | ## 🐢 Gradual Adoption 269 | 270 | One of the best features of Ruby2html is that you don't need to rewrite all your views at once! You can adopt it gradually, mixing Ruby2html with your existing ERB templates. This allows for a smooth transition at your own pace. 271 | 272 | ### Mixed usage example 273 | 274 | File: `app/views/your_mixed_view.html.erb` 275 | 276 | ```erb 277 |

Welcome to our gradually evolving page!

278 | 279 | <%= render partial: 'legacy_erb_partial' %> 280 | 281 | <%= 282 | html(self) do 283 | div class: 'ruby2html-section' do 284 | h2 "This section is powered by Ruby2html!" 285 | p "Isn't it beautiful? 😍" 286 | end 287 | end 288 | %> 289 | 290 | <%= render ModernComponent.new %> 291 | 292 | 295 | ``` 296 | 297 | In this example, you can see how Ruby2html seamlessly integrates with existing ERB code. This approach allows you to: 298 | 299 | - Keep your existing ERB templates and partials 300 | - Gradually introduce Ruby2html in specific sections 301 | - Use Ruby2html in new components while maintaining older ones 302 | - Refactor your views at your own pace 303 | 304 | Remember, there's no rush! You can keep your `.erb` files and Ruby2html code side by side until you're ready to fully transition. This flexibility ensures that adopting Ruby2html won't disrupt your existing workflow or require a massive rewrite of your application. 🌈 305 | 306 | ## 🛠 Development 307 | 308 | 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. 309 | 310 | ## 🤝 Contributing 311 | 312 | Bug reports and pull requests are welcome on GitHub at https://github.com/sebyx07/ruby2html. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/sebyx07/ruby2html/blob/master/CODE_OF_CONDUCT.md). 313 | 314 | ## 📜 License 315 | 316 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 317 | 318 | ## 🌈 Code of Conduct 319 | 320 | Everyone interacting in the Ruby2html project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/sebyx07/ruby2html/blob/master/CODE_OF_CONDUCT.md). 321 | 322 | ## 🌟 Features 323 | 324 | - Write views in pure Ruby 💎 325 | - Seamless Rails integration 🛤️ 326 | - ViewComponent support with flexible template options 🧩 327 | - Automatic HTML beautification 💅 328 | - Easy addition of custom attributes and data attributes 🏷️ 329 | - Gradual adoption - mix with existing ERB templates 🐢 330 | - Improved readability and maintainability 📚 331 | - Full access to Ruby's power in your views 💪 332 | 333 | Start writing your views in Ruby today and experience the magic of Ruby2html! ✨🔮 -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'config/application' 4 | require 'rake/extensiontask' 5 | require 'rake/testtask' 6 | 7 | # Load Rails tasks 8 | Rails.application.load_tasks 9 | 10 | # Add C extension compilation task 11 | Rake::ExtensionTask.new('ruby2html') do |ext| 12 | ext.lib_dir = 'lib/ruby2html' 13 | ext.ext_dir = 'ext/ruby2html' 14 | end 15 | 16 | task build: :compile 17 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebyx07/ruby2html/8b26ce02c33a402c38255067536ebf1e1e7a598c/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Channel < ActionCable::Channel::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Connection < ActionCable::Connection::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/components/application_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationComponent < ViewComponent::Base 4 | include Ruby2html::ComponentHelper 5 | end 6 | -------------------------------------------------------------------------------- /app/components/benchmark_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class BenchmarkComponent < ApplicationComponent 4 | def initialize(data) 5 | @complex_data = data 6 | end 7 | 8 | def call 9 | html do 10 | h1 'Benchmark: Ruby2html' 11 | 12 | h2 'User Statistics' 13 | ul do 14 | li { plain "Total Users: #{@complex_data[:stats][:total_users]}" } 15 | li { plain "Average Orders per User: #{@complex_data[:stats][:average_orders_per_user]}" } 16 | li { plain "Most Expensive Item: #{@complex_data[:stats][:most_expensive_item]}" } 17 | li { plain "Most Popular Country: #{@complex_data[:stats][:most_popular_country]}" } 18 | end 19 | 20 | h2 'User List' 21 | @complex_data[:users].each do |user| 22 | div class: 'user-card' do 23 | h3 user[:name] 24 | p { plain "Email: #{user[:email]}" } 25 | p { plain "Address: #{user[:address][:street]}, #{user[:address][:city]}, #{user[:address][:country]}" } 26 | 27 | h4 'Orders' 28 | user[:orders].each do |order| 29 | div class: 'order' do 30 | p { plain "Order ID: #{order[:id]}" } 31 | p { plain "Total: $#{order[:total]}" } 32 | ul do 33 | order[:items].each do |item| 34 | li { plain "#{item[:name]} - $#{item[:price]} (Quantity: #{item[:quantity]})" } 35 | end 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /app/components/first_component.html.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | h1 'first component' 4 | 5 | div do 6 | h2 'A subheading' 7 | end 8 | 9 | another_div do 10 | h1 'nested!!!!' 11 | end 12 | 13 | p @item 14 | -------------------------------------------------------------------------------- /app/components/first_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class FirstComponent < ApplicationComponent 4 | def initialize 5 | @item = 'Hello, World!' 6 | end 7 | 8 | def another_div 9 | div do 10 | "Item value: #{@item}" 11 | end 12 | 13 | div class: 'another' do 14 | h2 'Another subheading from component' 15 | end 16 | 17 | h1 'Yet Another heading from component' 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/components/second_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SecondComponent < ApplicationComponent 4 | def call 5 | html do 6 | h1 class: 'my-class' do 7 | plain 'Second Component' 8 | end 9 | link_to 'Home', root_path 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/components/third_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ThirdComponent < ApplicationComponent 4 | def initialize 5 | @item = 'Hello, World!' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/components/third_component/third_component.html.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | h1 'Third component' 4 | div do 5 | "Item value: #{@item}" 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::Base 4 | # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. 5 | allow_browser versions: :modern 6 | include Ruby2html::RailsHelper 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/benchmark_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class BenchmarkController < ApplicationController 4 | def normal_html 5 | @complex_data = COMPLEX_DATA 6 | end 7 | 8 | def ruby_2html 9 | @complex_data = COMPLEX_DATA 10 | end 11 | 12 | def slim_html 13 | @complex_data = COMPLEX_DATA 14 | end 15 | 16 | def phlex 17 | render PhlexView.new(COMPLEX_DATA) 18 | end 19 | 20 | def compruby 21 | render BenchmarkComponent.new(COMPLEX_DATA) 22 | end 23 | 24 | COMPLEX_DATA = { 25 | users: 50.times.map do |i| 26 | { 27 | id: i + 1, 28 | name: Faker::Name.name, 29 | email: Faker::Internet.email, 30 | address: { 31 | street: Faker::Address.street_address, 32 | city: Faker::Address.city, 33 | country: Faker::Address.country 34 | }, 35 | orders: rand(1..5).times.map do 36 | { 37 | id: Faker::Alphanumeric.alphanumeric(number: 10), 38 | total: Faker::Commerce.price(range: 10..1000.0), 39 | items: rand(1..10).times.map do 40 | { 41 | name: Faker::Commerce.product_name, 42 | price: Faker::Commerce.price(range: 5..500.0), 43 | quantity: rand(1..5) 44 | } 45 | end 46 | } 47 | end 48 | } 49 | end, 50 | stats: { 51 | total_users: 50, 52 | average_orders_per_user: rand(1.0..5.0).round(2), 53 | most_expensive_item: Faker::Commerce.product_name, 54 | most_popular_country: Faker::Address.country 55 | } 56 | }.freeze 57 | end 58 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebyx07/ruby2html/8b26ce02c33a402c38255067536ebf1e1e7a598c/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class HomeController < ApplicationController 4 | def index 5 | @items = [ 6 | { 7 | title: 'Item 1', 8 | description: 'Description 1' 9 | } 10 | ] 11 | end 12 | 13 | def rb_files 14 | @value = 'value' 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationJob < ActiveJob::Base 4 | # Automatically retry jobs that encountered a deadlock 5 | # retry_on ActiveRecord::Deadlocked 6 | 7 | # Most jobs are safe to ignore if the underlying records are no longer available 8 | # discard_on ActiveJob::DeserializationError 9 | end 10 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationMailer < ActionMailer::Base 4 | default from: 'from@example.com' 5 | layout 'mailer' 6 | end 7 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationRecord < ActiveRecord::Base 4 | primary_abstract_class 5 | end 6 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebyx07/ruby2html/8b26ce02c33a402c38255067536ebf1e1e7a598c/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/phlex/phlex_view.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class PhlexView < Phlex::HTML 4 | def initialize(complex_data) 5 | @complex_data = complex_data 6 | end 7 | 8 | def view_template 9 | h1 { 'Benchmark: PhlexView' } 10 | 11 | h2 { 'User Statistics' } 12 | ul do 13 | li { "Total Users: #{@complex_data[:stats][:total_users]}" } 14 | li { "Average Orders per User: #{@complex_data[:stats][:average_orders_per_user]}" } 15 | li { "Most Expensive Item: #{@complex_data[:stats][:most_expensive_item]}" } 16 | li { "Most Popular Country: #{@complex_data[:stats][:most_popular_country]}" } 17 | end 18 | 19 | h2 { 'User List' } 20 | @complex_data[:users].each do |user| 21 | div class: 'user-card' do 22 | h3 { user[:name] } 23 | p { "Email: #{user[:email]}" } 24 | p { "Address: #{user[:address][:street]}, #{user[:address][:city]}, #{user[:address][:country]}" } 25 | 26 | h4 { 'Orders' } 27 | user[:orders].each do |order| 28 | div class: 'order' do 29 | p { "Order ID: #{order[:id]}" } 30 | p { "Total: $#{order[:total]}" } 31 | ul do 32 | order[:items].each do |item| 33 | li { "#{item[:name]} - $#{item[:price]} (Quantity: #{item[:quantity]})" } 34 | end 35 | end 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/views/benchmark/normal_html.html.erb: -------------------------------------------------------------------------------- 1 |

Benchmark: Normal HTML (ERB)

2 | 3 |

User Statistics

4 | 10 | 11 |

User List

12 | <% @complex_data[:users].each do |user| %> 13 |
14 |

<%= user[:name] %>

15 |

Email: <%= user[:email] %>

16 |

Address: <%= "#{user[:address][:street]}, #{user[:address][:city]}, #{user[:address][:country]}" %>

17 | 18 |

Orders

19 | <% user[:orders].each do |order| %> 20 |
21 |

Order ID: <%= order[:id] %>

22 |

Total: $<%= order[:total] %>

23 | 28 |
29 | <% end %> 30 |
31 | <% end %> -------------------------------------------------------------------------------- /app/views/benchmark/ruby_2html.html.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | h1 'Benchmark: Ruby2html' 4 | 5 | h2 'User Statistics' 6 | ul do 7 | li { plain "Total Users: #{@complex_data[:stats][:total_users]}" } 8 | li { plain "Average Orders per User: #{@complex_data[:stats][:average_orders_per_user]}" } 9 | li { plain "Most Expensive Item: #{@complex_data[:stats][:most_expensive_item]}" } 10 | li { plain "Most Popular Country: #{@complex_data[:stats][:most_popular_country]}" } 11 | end 12 | 13 | h2 'User List' 14 | @complex_data[:users].each do |user| 15 | div class: 'user-card' do 16 | h3 user[:name] 17 | p { plain "Email: #{user[:email]}" } 18 | p { plain "Address: #{user[:address][:street]}, #{user[:address][:city]}, #{user[:address][:country]}" } 19 | 20 | h4 'Orders' 21 | user[:orders].each do |order| 22 | div class: 'order' do 23 | p { plain "Order ID: #{order[:id]}" } 24 | p { plain "Total: $#{order[:total]}" } 25 | ul do 26 | order[:items].each do |item| 27 | li { plain "#{item[:name]} - $#{item[:price]} (Quantity: #{item[:quantity]})" } 28 | end 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/views/benchmark/slim_html.html.slim: -------------------------------------------------------------------------------- 1 | h1 Benchmark: Normal HTML (ERB) 2 | 3 | h2 User Statistics 4 | ul 5 | li Total Users: #{@complex_data[:stats][:total_users]} 6 | li Average Orders per User: #{@complex_data[:stats][:average_orders_per_user]} 7 | li Most Expensive Item: #{@complex_data[:stats][:most_expensive_item]} 8 | li Most Popular Country: #{@complex_data[:stats][:most_popular_country]} 9 | 10 | h2 User List 11 | - @complex_data[:users].each do |user| 12 | .user-card 13 | h3 = user[:name] 14 | p Email: #{user[:email]} 15 | p Address: #{user[:address][:street]}, #{user[:address][:city]}, #{user[:address][:country]} 16 | 17 | h4 Orders 18 | - user[:orders].each do |order| 19 | .order 20 | p Order ID: #{order[:id]} 21 | p Total: $#{order[:total]} 22 | ul 23 | - order[:items].each do |item| 24 | li #{item[:name]} - $#{item[:price]} (Quantity: #{item[:quantity]}) -------------------------------------------------------------------------------- /app/views/home/form.html.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | div do 4 | h1 'form' 5 | 6 | form_with url: '/form', method: 'post' do |f| 7 | f.label :name 8 | f.text_field :name 9 | f.submit 'submit' 10 | end 11 | 12 | link_to 'Home', root_path 13 | # button_to 'home', '/' 14 | end 15 | -------------------------------------------------------------------------------- /app/views/home/index.html.erb: -------------------------------------------------------------------------------- 1 |

Home

2 | 3 | <%= render FirstComponent.new %> 4 | <%= render SecondComponent.new %> 5 | <%= render ThirdComponent.new %> 6 | 7 | <%= 8 | html(self) do 9 | h1(id: 4) { "Inside" } 10 | h2 "Inside 2", id: '44' 11 | h1 "ok" 12 | h1 "ok" 13 | h1 "ok" 14 | h1 "ok" 15 | h1 "ok" 16 | div do 17 | link_to 'home 1', root_path 18 | end 19 | link_to 'home 2', root_path 20 | 21 | div do 22 | link_to 'home 3', root_path 23 | end 24 | 25 | render partial: 'shared/navbar' 26 | 27 | @items.each do |item| 28 | h1 item[:title] 29 | end 30 | end 31 | %> 32 | 33 | <%= render partial: 'shared/navbar' %> 34 | 35 | <%= 36 | html(self) do 37 | @items.each do |item| 38 | h1 item[:title] 39 | end 40 | 41 | turbo_frame id: '4' 42 | end 43 | %> 44 | -------------------------------------------------------------------------------- /app/views/home/rb_files.html.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | h1 'RbFiles' 4 | h1 'ok' 5 | h1 'okddwadwa' 6 | 7 | div @value 8 | 9 | form_with url: '/test' do |f| 10 | f.text_field :name, placeholder: 'Name' 11 | f.submit 'Submit' 12 | end 13 | 14 | link_to 'Home', root_url 15 | render partial: 'shared/navbar' 16 | render partial: 'shared/footer' 17 | 18 | div @value 19 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= content_for(:title) || "Ruby2html" %> 5 | 6 | 7 | <%= csrf_meta_tags %> 8 | <%= csp_meta_tag %> 9 | 10 | <%= yield :head %> 11 | 12 | 13 | 14 | 15 | <%= stylesheet_link_tag "application" %> 16 | 17 | 18 | 19 | <%= yield %> 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/views/pwa/manifest.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ruby2html", 3 | "icons": [ 4 | { 5 | "src": "/icon.png", 6 | "type": "image/png", 7 | "sizes": "512x512" 8 | }, 9 | { 10 | "src": "/icon.png", 11 | "type": "image/png", 12 | "sizes": "512x512", 13 | "purpose": "maskable" 14 | } 15 | ], 16 | "start_url": "/", 17 | "display": "standalone", 18 | "scope": "/", 19 | "description": "Ruby2html.", 20 | "theme_color": "red", 21 | "background_color": "red" 22 | } 23 | -------------------------------------------------------------------------------- /app/views/pwa/service-worker.js: -------------------------------------------------------------------------------- 1 | // Add a service worker for processing Web Push notifications: 2 | // 3 | // self.addEventListener("push", async (event) => { 4 | // const { title, options } = await event.data.json() 5 | // event.waitUntil(self.registration.showNotification(title, options)) 6 | // }) 7 | // 8 | // self.addEventListener("notificationclick", function(event) { 9 | // event.notification.close() 10 | // event.waitUntil( 11 | // clients.matchAll({ type: "window" }).then((clientList) => { 12 | // for (let i = 0; i < clientList.length; i++) { 13 | // let client = clientList[i] 14 | // let clientPath = (new URL(client.url)).pathname 15 | // 16 | // if (clientPath == event.notification.data.path && "focus" in client) { 17 | // return client.focus() 18 | // } 19 | // } 20 | // 21 | // if (clients.openWindow) { 22 | // return clients.openWindow(event.notification.data.path) 23 | // } 24 | // }) 25 | // ) 26 | // }) 27 | -------------------------------------------------------------------------------- /app/views/shared/_footer.html.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | h1 'footer' 4 | -------------------------------------------------------------------------------- /app/views/shared/_navbar.html.erb: -------------------------------------------------------------------------------- 1 |

Navbar

-------------------------------------------------------------------------------- /bin/brakeman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | ARGV.unshift("--ensure-latest") 6 | 7 | load Gem.bin_path("brakeman", "brakeman") 8 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../Gemfile", __dir__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_requirement 64 | @bundler_requirement ||= 65 | env_var_version || 66 | cli_arg_version || 67 | bundler_requirement_for(lockfile_version) 68 | end 69 | 70 | def bundler_requirement_for(version) 71 | return "#{Gem::Requirement.default}.a" unless version 72 | 73 | bundler_gem_version = Gem::Version.new(version) 74 | 75 | bundler_gem_version.approximate_recommendation 76 | end 77 | 78 | def load_bundler! 79 | ENV["BUNDLE_GEMFILE"] ||= gemfile 80 | 81 | activate_bundler 82 | end 83 | 84 | def activate_bundler 85 | gem_error = activation_error_handling do 86 | gem "bundler", bundler_requirement 87 | end 88 | return if gem_error.nil? 89 | require_error = activation_error_handling do 90 | require "bundler/version" 91 | end 92 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 93 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 94 | exit 42 95 | end 96 | 97 | def activation_error_handling 98 | yield 99 | nil 100 | rescue StandardError, LoadError => e 101 | e 102 | end 103 | end 104 | 105 | m.load_bundler! 106 | 107 | if m.invoked_as_script? 108 | load Gem.bin_path("bundler", "bundle") 109 | end 110 | -------------------------------------------------------------------------------- /bin/docker-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Enable jemalloc for reduced memory usage and latency. 4 | if [ -z "${LD_PRELOAD+x}" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then 5 | export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)" 6 | fi 7 | 8 | # If running the rails server then create or migrate existing database 9 | if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then 10 | ./bin/rails db:prepare 11 | fi 12 | 13 | exec "${@}" 14 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | # explicit rubocop config increases performance slightly while avoiding config confusion. 6 | ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) 7 | 8 | load Gem.bin_path("rubocop", "rubocop") 9 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | APP_ROOT = File.expand_path("..", __dir__) 5 | APP_NAME = "ruby2html" 6 | 7 | def system!(*args) 8 | system(*args, exception: true) 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | 34 | # puts "\n== Configuring puma-dev ==" 35 | # system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}" 36 | # system "curl -Is https://#{APP_NAME}.test/up | head -n 1" 37 | end 38 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require_relative 'config/environment' 6 | 7 | run Rails.application 8 | Rails.application.load_server 9 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'boot' 4 | 5 | require 'rails' 6 | # Pick the frameworks you want: 7 | require 'active_model/railtie' 8 | require 'active_job/railtie' 9 | require 'active_record/railtie' 10 | require 'active_storage/engine' 11 | require 'action_controller/railtie' 12 | require 'action_mailer/railtie' 13 | require 'action_mailbox/engine' 14 | require 'action_text/engine' 15 | require 'action_view/railtie' 16 | require 'action_cable/engine' 17 | # require "rails/test_unit/railtie" 18 | 19 | # Require the gems listed in Gemfile, including any gems 20 | # you've limited to :test, :development, or :production. 21 | Bundler.require(*Rails.groups) 22 | require_relative '../lib/gem/ruby2html' 23 | 24 | module Ruby2html 25 | class Application < Rails::Application 26 | # Initialize configuration defaults for originally generated Rails version. 27 | config.load_defaults 7.2 28 | 29 | # Please, add to the `ignore` list any other `lib` subdirectories that do 30 | # not contain `.rb` files, or that should not be reloaded or eager loaded. 31 | # Common ones are `templates`, `generators`, or `middleware`, for example. 32 | config.autoload_lib(ignore: %w[gem assets tasks]) 33 | 34 | # Configuration for the application, engines, and railties goes here. 35 | # 36 | # These settings can be overridden in specific environments using the files 37 | # in config/environments, which are processed later. 38 | # 39 | # config.time_zone = "Central Time (US & Canada)" 40 | # config.eager_load_paths << Rails.root.join("extras") 41 | 42 | # Don't generate system test files. 43 | config.generators.system_tests = nil 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 4 | 5 | require 'bundler/setup' # Set up gems listed in the Gemfile. 6 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations. 7 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: ruby2html_production 11 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | syxIQNjKjMQTCYgIrZXzklAmWupRucx5oAblyiO1h4pPn+S9KZSUmxjnZ1ntCzFRMDGgbeIAUI5WoEsnKk6uRhX1DTULIOuW4ODNbxRWkWf5WOBXF7zxu9v1b0jcfsBO8mYHRoFxulDbxhsOIS8opAH1pzhLAB65uV8w3BPuG9547t/CHvarEzipOceugWaVjK1cF4lqkB72xWz5IQ1zxJmX+pSEj36jFfhSK5bbUlnC2h/TDBYc9tEz7GZPYnHw+xrGnhayz+6pLUBmHFwwnfQjZE9VYEm4Y0vLZLjveSmvV6LX+4QsX/r6wD0gWxDdG0jarO6i7u18Vg8aLcSQRII5bTjEdyn4NuF6E61HUhkuaSBNb9hcGCX7eGOlYziZHrOZdNg8lxIrjc2gauGz0M3bWUkJ--l6VJIq9MkwuXzeWl--MuF7LY4aYyUOZwMPsBdsBQ== -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem "sqlite3" 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: storage/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: storage/test.sqlite3 22 | 23 | 24 | # SQLite3 write its data on the local filesystem, as such it requires 25 | # persistent disks. If you are deploying to a managed service, you should 26 | # make sure it provides disk persistence, as many don't. 27 | # 28 | # Similarly, if you deploy your application as a Docker container, you must 29 | # ensure the database is located in a persisted volume. 30 | production: 31 | <<: *default 32 | # database: path/to/persistent/storage/production.sqlite3 33 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative 'application' 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/integer/time' 4 | 5 | Rails.application.configure do 6 | # Settings specified here will take precedence over those in config/application.rb. 7 | 8 | # In the development environment your application's code is reloaded any time 9 | # it changes. This slows down response time but is perfect for development 10 | # since you don't have to restart the web server when you make code changes. 11 | config.enable_reloading = true 12 | 13 | # Do not eager load code on boot. 14 | config.eager_load = false 15 | 16 | # Show full error reports. 17 | config.consider_all_requests_local = true 18 | 19 | # Enable server timing. 20 | config.server_timing = true 21 | 22 | # Enable/disable caching. By default caching is disabled. 23 | # Run rails dev:cache to toggle caching. 24 | if Rails.root.join('tmp/caching-dev.txt').exist? 25 | config.action_controller.perform_caching = true 26 | config.action_controller.enable_fragment_cache_logging = true 27 | 28 | config.cache_store = :memory_store 29 | config.public_file_server.headers = { 'Cache-Control' => "public, max-age=#{2.days.to_i}" } 30 | else 31 | config.action_controller.perform_caching = false 32 | 33 | config.cache_store = :null_store 34 | end 35 | 36 | # Store uploaded files on the local file system (see config/storage.yml for options). 37 | config.active_storage.service = :local 38 | 39 | # Don't care if the mailer can't send. 40 | config.action_mailer.raise_delivery_errors = false 41 | 42 | # Disable caching for Action Mailer templates even if Action Controller 43 | # caching is enabled. 44 | config.action_mailer.perform_caching = false 45 | 46 | config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } 47 | 48 | # Print deprecation notices to the Rails logger. 49 | config.active_support.deprecation = :log 50 | 51 | # Raise exceptions for disallowed deprecations. 52 | config.active_support.disallowed_deprecation = :raise 53 | 54 | # Tell Active Support which deprecation messages to disallow. 55 | config.active_support.disallowed_deprecation_warnings = [] 56 | 57 | # Raise an error on page load if there are pending migrations. 58 | config.active_record.migration_error = :page_load 59 | 60 | # Highlight code that triggered database queries in logs. 61 | config.active_record.verbose_query_logs = true 62 | 63 | # Highlight code that enqueued background job in logs. 64 | config.active_job.verbose_enqueue_logs = true 65 | 66 | # Suppress logger output for asset requests. 67 | config.assets.quiet = true 68 | 69 | # Raises error for missing translations. 70 | # config.i18n.raise_on_missing_translations = true 71 | 72 | # Annotate rendered view with file names. 73 | config.action_view.annotate_rendered_view_with_filenames = true 74 | 75 | # Uncomment if you wish to allow Action Cable access from any origin. 76 | # config.action_cable.disable_request_forgery_protection = true 77 | 78 | # Raise error when a before_action's only/except options reference missing actions. 79 | config.action_controller.raise_on_missing_callback_actions = true 80 | 81 | # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. 82 | # config.generators.apply_rubocop_autocorrect_after_generate! 83 | 84 | config.middleware.use Ruby2html::HtmlBeautifierMiddleware 85 | end 86 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/integer/time' 4 | 5 | Rails.application.configure do 6 | # Settings specified here will take precedence over those in config/application.rb. 7 | 8 | # Code is not reloaded between requests. 9 | config.enable_reloading = false 10 | 11 | # Eager load code on boot. This eager loads most of Rails and 12 | # your application in memory, allowing both threaded web servers 13 | # and those relying on copy on write to perform better. 14 | # Rake tasks automatically ignore this option for performance. 15 | config.eager_load = true 16 | 17 | # Full error reports are disabled and caching is turned on. 18 | config.consider_all_requests_local = false 19 | config.action_controller.perform_caching = true 20 | 21 | # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment 22 | # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files). 23 | # config.require_master_key = true 24 | 25 | # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead. 26 | # config.public_file_server.enabled = false 27 | 28 | # Compress CSS using a preprocessor. 29 | # config.assets.css_compressor = :sass 30 | 31 | # Do not fall back to assets pipeline if a precompiled asset is missed. 32 | config.assets.compile = false 33 | 34 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 35 | # config.asset_host = "http://assets.example.com" 36 | 37 | # Specifies the header that your server uses for sending files. 38 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache 39 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX 40 | 41 | # Store uploaded files on the local file system (see config/storage.yml for options). 42 | config.active_storage.service = :local 43 | 44 | # Mount Action Cable outside main process or domain. 45 | # config.action_cable.mount_path = nil 46 | # config.action_cable.url = "wss://example.com/cable" 47 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] 48 | 49 | # Assume all access to the app is happening through a SSL-terminating reverse proxy. 50 | # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. 51 | # config.assume_ssl = true 52 | 53 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 54 | config.force_ssl = true 55 | 56 | # Skip http-to-https redirect for the default health check endpoint. 57 | # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } 58 | 59 | # Log to STDOUT by default 60 | config.logger = ActiveSupport::Logger.new(STDOUT) 61 | .tap { |logger| logger.formatter = ::Logger::Formatter.new } 62 | .then { |logger| ActiveSupport::TaggedLogging.new(logger) } 63 | 64 | # Prepend all log lines with the following tags. 65 | config.log_tags = [ :request_id ] 66 | 67 | # "info" includes generic and useful information about system operation, but avoids logging too much 68 | # information to avoid inadvertent exposure of personally identifiable information (PII). If you 69 | # want to log everything, set the level to "debug". 70 | config.log_level = ENV.fetch('RAILS_LOG_LEVEL', 'info') 71 | 72 | # Use a different cache store in production. 73 | # config.cache_store = :mem_cache_store 74 | 75 | # Use a real queuing backend for Active Job (and separate queues per environment). 76 | # config.active_job.queue_adapter = :resque 77 | # config.active_job.queue_name_prefix = "ruby2html_production" 78 | 79 | # Disable caching for Action Mailer templates even if Action Controller 80 | # caching is enabled. 81 | config.action_mailer.perform_caching = false 82 | 83 | # Ignore bad email addresses and do not raise email delivery errors. 84 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 85 | # config.action_mailer.raise_delivery_errors = false 86 | 87 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 88 | # the I18n.default_locale when a translation cannot be found). 89 | config.i18n.fallbacks = true 90 | 91 | # Don't log any deprecations. 92 | config.active_support.report_deprecations = false 93 | 94 | # Do not dump schema after migrations. 95 | config.active_record.dump_schema_after_migration = false 96 | 97 | # Enable DNS rebinding protection and other `Host` header attacks. 98 | # config.hosts = [ 99 | # "example.com", # Allow requests from example.com 100 | # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` 101 | # ] 102 | # Skip DNS rebinding protection for the default health check endpoint. 103 | # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } 104 | end 105 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/integer/time' 4 | 5 | # The test environment is used exclusively to run your application's 6 | # test suite. You never need to work with it otherwise. Remember that 7 | # your test database is "scratch space" for the test suite and is wiped 8 | # and recreated between test runs. Don't rely on the data there! 9 | 10 | Rails.application.configure do 11 | # Settings specified here will take precedence over those in config/application.rb. 12 | 13 | # While tests run files are not watched, reloading is not necessary. 14 | config.enable_reloading = false 15 | 16 | # Eager loading loads your entire application. When running a single test locally, 17 | # this is usually not necessary, and can slow down your test suite. However, it's 18 | # recommended that you enable it in continuous integration systems to ensure eager 19 | # loading is working properly before deploying your code. 20 | config.eager_load = ENV['CI'].present? 21 | 22 | # Configure public file server for tests with Cache-Control for performance. 23 | config.public_file_server.headers = { 'Cache-Control' => "public, max-age=#{1.hour.to_i}" } 24 | 25 | # Show full error reports and disable caching. 26 | config.consider_all_requests_local = true 27 | config.action_controller.perform_caching = false 28 | config.cache_store = :null_store 29 | 30 | # Render exception templates for rescuable exceptions and raise for other exceptions. 31 | config.action_dispatch.show_exceptions = :rescuable 32 | 33 | # Disable request forgery protection in test environment. 34 | config.action_controller.allow_forgery_protection = false 35 | 36 | # Store uploaded files on the local file system in a temporary directory. 37 | config.active_storage.service = :test 38 | 39 | # Disable caching for Action Mailer templates even if Action Controller 40 | # caching is enabled. 41 | config.action_mailer.perform_caching = false 42 | 43 | # Tell Action Mailer not to deliver emails to the real world. 44 | # The :test delivery method accumulates sent emails in the 45 | # ActionMailer::Base.deliveries array. 46 | config.action_mailer.delivery_method = :test 47 | 48 | # Unlike controllers, the mailer instance doesn't have any context about the 49 | # incoming request so you'll need to provide the :host parameter yourself. 50 | config.action_mailer.default_url_options = { host: 'www.example.com' } 51 | 52 | # Print deprecation notices to the stderr. 53 | config.active_support.deprecation = :stderr 54 | 55 | # Raise exceptions for disallowed deprecations. 56 | config.active_support.disallowed_deprecation = :raise 57 | 58 | # Tell Active Support which deprecation messages to disallow. 59 | config.active_support.disallowed_deprecation_warnings = [] 60 | 61 | # Raises error for missing translations. 62 | # config.i18n.raise_on_missing_translations = true 63 | 64 | # Annotate rendered view with file names. 65 | # config.action_view.annotate_rendered_view_with_filenames = true 66 | 67 | # Raise error when a before_action's only/except options reference missing actions. 68 | config.action_controller.raise_on_missing_callback_actions = true 69 | end 70 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Version of your assets, change this if you want to expire all your assets. 6 | Rails.application.config.assets.version = '1.0' 7 | 8 | # Add additional assets to the asset load path. 9 | # Rails.application.config.assets.paths << Emoji.images_path 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Define an application-wide content security policy. 6 | # See the Securing Rails Applications Guide for more information: 7 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 8 | 9 | # Rails.application.configure do 10 | # config.content_security_policy do |policy| 11 | # policy.default_src :self, :https 12 | # policy.font_src :self, :https, :data 13 | # policy.img_src :self, :https, :data 14 | # policy.object_src :none 15 | # policy.script_src :self, :https 16 | # policy.style_src :self, :https 17 | # # Specify URI for violation reports 18 | # # policy.report_uri "/csp-violation-report-endpoint" 19 | # end 20 | # 21 | # # Generate session nonces for permitted importmap, inline scripts, and inline styles. 22 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 23 | # config.content_security_policy_nonce_directives = %w(script-src style-src) 24 | # 25 | # # Report violations without enforcing the policy. 26 | # # config.content_security_policy_report_only = true 27 | # end 28 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 6 | # Use this to limit dissemination of sensitive information. 7 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 8 | Rails.application.config.filter_parameters += [ 9 | :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 10 | ] 11 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Add new inflection rules using the following format. Inflections 6 | # are locale specific, and you may define rules for as many different 7 | # locales as you wish. All of these examples are active by default: 8 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 9 | # inflect.plural /^(ox)$/i, "\\1en" 10 | # inflect.singular /^(ox)en/i, "\\1" 11 | # inflect.irregular "person", "people" 12 | # inflect.uncountable %w( fish sheep ) 13 | # end 14 | 15 | # These inflection rules are supported but not enabled by default: 16 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 17 | # inflect.acronym "RESTful" 18 | # end 19 | -------------------------------------------------------------------------------- /config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Define an application-wide HTTP permissions policy. For further 6 | # information see: https://developers.google.com/web/updates/2018/06/feature-policy 7 | 8 | # Rails.application.config.permissions_policy do |policy| 9 | # policy.camera :none 10 | # policy.gyroscope :none 11 | # policy.microphone :none 12 | # policy.usb :none 13 | # policy.fullscreen :self 14 | # policy.payment :self, "https://secure.example.com" 15 | # end 16 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization and 2 | # are automatically loaded by Rails. If you want to use locales other than 3 | # English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more about the API, please read the Rails Internationalization guide 20 | # at https://guides.rubyonrails.org/i18n.html. 21 | # 22 | # Be aware that YAML interprets the following case-insensitive strings as 23 | # booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings 24 | # must be quoted to be interpreted as strings. For example: 25 | # 26 | # en: 27 | # "yes": yup 28 | # enabled: "ON" 29 | 30 | en: 31 | hello: "Hello world" 32 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This configuration file will be evaluated by Puma. The top-level methods that 4 | # are invoked here are part of Puma's configuration DSL. For more information 5 | # about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. 6 | 7 | # Puma starts a configurable number of processes (workers) and each process 8 | # serves each request in a thread from an internal thread pool. 9 | # 10 | # The ideal number of threads per worker depends both on how much time the 11 | # application spends waiting for IO operations and on how much you wish to 12 | # to prioritize throughput over latency. 13 | # 14 | # As a rule of thumb, increasing the number of threads will increase how much 15 | # traffic a given process can handle (throughput), but due to CRuby's 16 | # Global VM Lock (GVL) it has diminishing returns and will degrade the 17 | # response time (latency) of the application. 18 | # 19 | # The default is set to 3 threads as it's deemed a decent compromise between 20 | # throughput and latency for the average Rails application. 21 | # 22 | # Any libraries that use a connection pool or another resource pool should 23 | # be configured to provide at least as many connections as the number of 24 | # threads. This includes Active Record's `pool` parameter in `database.yml`. 25 | threads_count = ENV.fetch('RAILS_MAX_THREADS', 3) 26 | threads threads_count, threads_count 27 | 28 | # Specifies the `environment` that Puma will run in. 29 | rails_env = ENV.fetch('RAILS_ENV', 'development') 30 | environment rails_env 31 | 32 | case rails_env 33 | when 'production' 34 | # If you are running more than 1 thread per process, the workers count 35 | # should be equal to the number of processors (CPU cores) in production. 36 | # 37 | # Automatically detect the number of available processors in production. 38 | require 'concurrent-ruby' 39 | workers_count = Integer(ENV.fetch('WEB_CONCURRENCY') { Concurrent.available_processor_count }) 40 | workers workers_count if workers_count > 1 41 | 42 | preload_app! 43 | when 'development' 44 | # Specifies a very generous `worker_timeout` so that the worker 45 | # isn't killed by Puma when suspended by a debugger. 46 | worker_timeout 3600 47 | end 48 | 49 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 50 | port ENV.fetch('PORT', 3000) 51 | 52 | # Allow puma to be restarted by `bin/rails restart` command. 53 | plugin :tmp_restart 54 | 55 | # Only use a pidfile when requested 56 | pidfile ENV['PIDFILE'] if ENV['PIDFILE'] 57 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | root to: 'home#index' 5 | get '/benchmark/html', to: 'benchmark#normal_html' 6 | get '/benchmark/slim', to: 'benchmark#slim_html' 7 | get '/benchmark/ruby', to: 'benchmark#ruby_2html' 8 | get '/benchmark/compruby', to: 'benchmark#compruby' 9 | get '/benchmark/phlex', to: 'benchmark#phlex' 10 | 11 | get '/rb_files', to: 'home#rb_files' 12 | get 'form', to: 'home#form' 13 | 14 | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html 15 | 16 | # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. 17 | # Can be used by load balancers and uptime monitors to verify that the app is live. 18 | get 'up' => 'rails/health#show', as: :rails_health_check 19 | 20 | # Render dynamic PWA files from app/views/pwa/* 21 | get 'service-worker' => 'rails/pwa#service_worker', as: :pwa_service_worker 22 | get 'manifest' => 'rails/pwa#manifest', as: :pwa_manifest 23 | 24 | # Defines the root path route ("/") 25 | # root "posts#index" 26 | end 27 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket-<%= Rails.env %> 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket-<%= Rails.env %> 23 | 24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name-<%= Rails.env %> 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file should ensure the existence of records required to run the application in every environment (production, 4 | # development, test). The code here should be idempotent so that it can be executed at any point in every environment. 5 | # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). 6 | # 7 | # Example: 8 | # 9 | # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| 10 | # MovieGenre.find_or_create_by!(name: genre_name) 11 | # end 12 | -------------------------------------------------------------------------------- /ext/ruby2html/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # ext/ruby2html/extconf.rb 4 | require 'mkmf' 5 | 6 | # Add optimization flags 7 | $CFLAGS << ' -O3 -Wall -Wextra' 8 | 9 | # Check for required headers 10 | have_header('ruby.h') 11 | have_header('ruby/encoding.h') 12 | 13 | extension_name = 'ruby2html/ruby2html' 14 | dir_config(extension_name) 15 | 16 | create_makefile(extension_name) 17 | -------------------------------------------------------------------------------- /ext/ruby2html/ruby2html.c: -------------------------------------------------------------------------------- 1 | // ext/ruby2html/ruby2html.c 2 | #include 3 | #include 4 | #include 5 | 6 | static VALUE rb_mRuby2html; 7 | static VALUE rb_cRenderer; 8 | 9 | // Fast HTML escaping 10 | static VALUE fast_escape_html(VALUE self, VALUE str) { 11 | if (NIL_P(str)) return Qnil; 12 | 13 | str = rb_String(str); 14 | long len = RSTRING_LEN(str); 15 | const char *ptr = RSTRING_PTR(str); 16 | 17 | // First pass: calculate required buffer size 18 | long new_len = len; 19 | for (long i = 0; i < len; i++) { 20 | switch (ptr[i]) { 21 | case '&': new_len += 4; break; // & 22 | case '<': new_len += 3; break; // < 23 | case '>': new_len += 3; break; // > 24 | case '"': new_len += 5; break; // " 25 | case '\'': new_len += 5; break; // ' 26 | } 27 | } 28 | 29 | if (new_len == len) return str; 30 | 31 | VALUE result = rb_str_new(NULL, new_len); 32 | char *out = RSTRING_PTR(result); 33 | long pos = 0; 34 | 35 | // Second pass: actual escaping 36 | for (long i = 0; i < len; i++) { 37 | switch (ptr[i]) { 38 | case '&': 39 | memcpy(out + pos, "&", 5); 40 | pos += 5; 41 | break; 42 | case '<': 43 | memcpy(out + pos, "<", 4); 44 | pos += 4; 45 | break; 46 | case '>': 47 | memcpy(out + pos, ">", 4); 48 | pos += 4; 49 | break; 50 | case '"': 51 | memcpy(out + pos, """, 6); 52 | pos += 6; 53 | break; 54 | case '\'': 55 | memcpy(out + pos, "'", 5); 56 | pos += 5; 57 | break; 58 | default: 59 | out[pos++] = ptr[i]; 60 | } 61 | } 62 | 63 | rb_str_set_len(result, pos); 64 | rb_enc_associate(result, rb_enc_get(str)); 65 | return result; 66 | } 67 | 68 | // Fast attribute string builder 69 | static VALUE fast_attributes_to_s(VALUE self, VALUE hash) { 70 | if (NIL_P(hash) || RHASH_EMPTY_P(hash)) return rb_str_new2(""); 71 | 72 | VALUE result = rb_str_buf_new(64); // Pre-allocate with reasonable size 73 | VALUE keys = rb_funcall(hash, rb_intern("keys"), 0); 74 | long len = RARRAY_LEN(keys); 75 | 76 | for (long i = 0; i < len; i++) { 77 | VALUE key = rb_ary_entry(keys, i); 78 | VALUE value = rb_hash_aref(hash, key); 79 | 80 | if (!NIL_P(value)) { 81 | rb_str_cat2(result, " "); 82 | rb_str_append(result, rb_String(key)); 83 | rb_str_cat2(result, "=\""); 84 | rb_str_append(result, fast_escape_html(self, rb_String(value))); 85 | rb_str_cat2(result, "\""); 86 | } 87 | } 88 | 89 | return result; 90 | } 91 | 92 | // Fast string buffer concatenation 93 | static VALUE fast_buffer_append(VALUE self, VALUE buffer, VALUE str) { 94 | if (!NIL_P(str)) { 95 | rb_funcall(buffer, rb_intern("<<"), 1, rb_String(str)); 96 | } 97 | return Qnil; 98 | } 99 | 100 | void Init_ruby2html(void) { 101 | rb_mRuby2html = rb_define_module("Ruby2html"); 102 | 103 | rb_cRenderer = rb_define_class_under(rb_mRuby2html, "Render", rb_cObject); 104 | 105 | rb_define_method(rb_cRenderer, "fast_escape_html", fast_escape_html, 1); 106 | rb_define_method(rb_cRenderer, "fast_attributes_to_s", fast_attributes_to_s, 1); 107 | rb_define_method(rb_cRenderer, "fast_buffer_append", fast_buffer_append, 2); 108 | } -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | commands: 3 | rubocop: 4 | run: bundle exec rubocop -A 5 | skip: 6 | - merge 7 | - rebase 8 | stage_fixed: true -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebyx07/ruby2html/8b26ce02c33a402c38255067536ebf1e1e7a598c/lib/assets/.keep -------------------------------------------------------------------------------- /lib/gem/ruby2html.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'cgi' 4 | require 'stringio' 5 | require 'htmlbeautifier' 6 | 7 | Dir.glob(File.join(File.dirname(__FILE__), 'ruby2html', '**', '*.rb')).each do |file| 8 | require file 9 | end 10 | 11 | module Ruby2html 12 | class Error < StandardError; end 13 | end 14 | -------------------------------------------------------------------------------- /lib/gem/ruby2html/component_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Ruby2html 4 | module ComponentHelper 5 | def html(&block) 6 | previous_renderer = __ruby2html_renderer__ 7 | Ruby2html::Render.new(self, &block).yield_self do |component_renderer| 8 | Thread.current[:__ruby2html_renderer__] = component_renderer 9 | component_renderer.render.html_safe 10 | end 11 | ensure 12 | Thread.current[:__ruby2html_renderer__] = previous_renderer 13 | end 14 | 15 | def method_missing(method, *args, **options, &block) 16 | if __ruby2html_renderer__.respond_to?(method) 17 | __ruby2html_renderer__.send(method, *args, **options, &block) 18 | else 19 | super 20 | end 21 | end 22 | 23 | def __ruby2html_renderer__ 24 | Thread.current[:__ruby2html_renderer__] 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/gem/ruby2html/html_beautifier_middleware.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Ruby2html 4 | class HtmlBeautifierMiddleware 5 | def initialize(app) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | status, headers, body = @app.call(env) 11 | 12 | if headers['Content-Type']&.include?('text/html') 13 | new_body = [] 14 | body.each do |chunk| 15 | new_body << HtmlBeautifier.beautify(chunk) 16 | end 17 | headers['Content-Length'] = new_body.map(&:bytesize).sum.to_s 18 | body = new_body 19 | end 20 | 21 | [status, headers, body] 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/gem/ruby2html/rails_components/base_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Ruby2html 4 | module RailsComponents 5 | class BaseComponent 6 | def initialize(render, context, method, *args, **options) 7 | @render = render 8 | @context = context 9 | @method = method 10 | @args = args 11 | @options = options 12 | end 13 | 14 | def render(&block) 15 | raise NotImplementedError 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/gem/ruby2html/rails_components/button_to.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Ruby2html 4 | module RailsComponents 5 | class ButtonTo < BaseComponent 6 | def render(&block) 7 | @render.plain(@context.button_to(*@args, **@options, &block)) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/gem/ruby2html/rails_components/form_with.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Ruby2html 4 | module RailsComponents 5 | class FormWith < BaseComponent 6 | include ActionView::Helpers::FormHelper 7 | def render(&block) 8 | model = @options[:model] 9 | scope = @options[:scope] 10 | url = @options[:url] 11 | method = @options[:method] 12 | local = @options[:local] 13 | 14 | form_options = @options.except(:model, :scope, :url, :method, :local) 15 | form_options[:action] = determine_url(model, url) 16 | form_options[:method] = determine_method(model, method) 17 | form_options['data-remote'] = 'true' unless local 18 | @model = model 19 | @scope = determine_scope(model, scope) 20 | 21 | @render.form(**form_options) do 22 | authenticity_token_tag 23 | utf8_enforcer_tag 24 | block.call(self) 25 | end 26 | end 27 | 28 | def label(method, text = nil, options = {}) 29 | @render.label(**options.merge(for: field_id(method))) do 30 | text || method.to_s.humanize 31 | end 32 | end 33 | 34 | def text_field(method, options = {}) 35 | @render.input(**options.merge(type: 'text', name: field_name(method), id: field_id(method), value: object_value_for(method))) 36 | end 37 | 38 | def hidden_field(method, options = {}) 39 | @render.input(**options.merge(type: 'hidden', name: field_name(method), id: field_id(method), value: object_value_for(method))) 40 | end 41 | 42 | def password_field(method, options = {}) 43 | @render.input(**options.merge(type: 'password', name: field_name(method), id: field_id(method))) 44 | end 45 | 46 | def file_field(method, options = {}) 47 | @render.input(**options.merge(type: 'file', name: field_name(method), id: field_id(method))) 48 | end 49 | 50 | def submit(value = nil, options = {}) 51 | @render.input(**options.merge(type: 'submit', value: value || submit_default_value)) 52 | end 53 | 54 | private 55 | def determine_url(model, url) 56 | return url if url 57 | return polymorphic_path(model) if model && model.respond_to?(:persisted?) 58 | nil 59 | end 60 | 61 | def determine_method(model, method) 62 | return method if method 63 | return 'post' unless model 64 | model.respond_to?(:persisted?) && model.persisted? ? 'patch' : 'post' 65 | end 66 | 67 | def determine_scope(model, scope) 68 | return scope if scope 69 | model.model_name.param_key if model.respond_to?(:model_name) 70 | end 71 | 72 | def authenticity_token_tag 73 | @render.input(type: 'hidden', name: 'authenticity_token', value: @context.form_authenticity_token) 74 | end 75 | 76 | def utf8_enforcer_tag 77 | @render.input(type: 'hidden', name: 'utf8', value: '✓') 78 | end 79 | 80 | def field_name(method) 81 | @scope ? "#{@scope}[#{method}]" : method.to_s 82 | end 83 | 84 | def field_id(method) 85 | @scope ? "#{@scope}_#{method}" : method.to_s 86 | end 87 | 88 | def object_value_for(method) 89 | @model&.public_send(method) if @model 90 | end 91 | end 92 | end 93 | end if defined?(ActionView::Helpers::FormHelper) 94 | -------------------------------------------------------------------------------- /lib/gem/ruby2html/rails_components/image_tag.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Ruby2html 4 | module RailsComponents 5 | class ImageTag < BaseComponent 6 | def render(&block) 7 | @render.plain(@context.image_tag(*@args, **@options, &block)) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/gem/ruby2html/rails_components/link_to.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Ruby2html 4 | module RailsComponents 5 | class LinkTo < BaseComponent 6 | def render(&block) 7 | @render.plain(@context.button_to(*@args, **@options, &block)) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/gem/ruby2html/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Ruby2html 4 | module RailsHelper 5 | def html(context, &block) 6 | Ruby2html::Render.new(context, &block).render.html_safe 7 | end 8 | 9 | def self.included(base) 10 | base.helper_method :html 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/gem/ruby2html/railtie.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | if defined?(Rails) 4 | module Ruby2html 5 | class TemplateHandler 6 | class_attribute :default_format 7 | self.default_format = :html 8 | 9 | def self.call(template, source) 10 | <<-RUBY 11 | begin 12 | previous_renderer = Thread.current[:__ruby2html_renderer__] 13 | renderer = Ruby2html::Render.new(self) do 14 | #{source} 15 | end 16 | Thread.current[:__ruby2html_renderer__] = renderer 17 | renderer.__render_from_rails(#{template.identifier.inspect}) 18 | ensure 19 | Thread.current[:__ruby2html_renderer__] = previous_renderer 20 | end 21 | RUBY 22 | end 23 | 24 | def self.handles_encoding? 25 | true 26 | end 27 | end 28 | 29 | class Railtie < Rails::Railtie 30 | initializer 'ruby2html.initializer' do 31 | Rails.autoloaders.main.ignore( 32 | Rails.root.join('app/views/**/*.html.rb'), 33 | Rails.root.join('app/components/**/*.html.rb') 34 | ) 35 | end 36 | end 37 | end 38 | 39 | ActionView::Template.register_template_handler :rb, Ruby2html::TemplateHandler 40 | end 41 | -------------------------------------------------------------------------------- /lib/gem/ruby2html/render.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'ruby2html/ruby2html' 5 | rescue LoadError 6 | puts 'ruby2html not installed' 7 | end 8 | 9 | module Ruby2html 10 | class Render 11 | HTML5_TAGS = %w[ 12 | a abbr address area article aside audio b base bdi bdo blockquote body br button canvas caption 13 | cite code col colgroup data datalist dd del details dfn dialog div dl dt em embed fieldset 14 | figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hr html i iframe img input ins 15 | kbd label legend li link main map mark meta meter nav noscript object ol optgroup option 16 | output p param picture pre progress q rp rt ruby s samp script section select small source 17 | span strong style sub summary sup table tbody td template textarea tfoot th thead time title 18 | tr track u ul var video wbr turbo-frame turbo-stream 19 | ].freeze 20 | 21 | VOID_ELEMENTS = %w[area base br col embed hr img input link meta param source track wbr].freeze 22 | COMMON_RAILS_METHOD_HELPERS = %w[link_to image_tag form_with button_to].freeze 23 | 24 | # Pre-generate all HTML tag methods as a single string 25 | METHOD_DEFINITIONS = HTML5_TAGS.map do |tag| 26 | method_name = tag.tr('-', '_') 27 | is_void = VOID_ELEMENTS.include?(tag) 28 | <<-RUBY 29 | def #{method_name}(*args, **options, &block) 30 | content = args.first.is_a?(String) ? args.shift : nil 31 | estimated_size = #{tag.length * 2 + 5} 32 | estimated_size += 32 if options.any? 33 | estimated_size += content.length if content 34 | tag_content = String.new(capacity: estimated_size) 35 | tag_content << '<#{tag}' 36 | fast_buffer_append(tag_content, fast_attributes_to_s(options)) 37 | #{if is_void 38 | 'tag_content << \' />\'' 39 | else 40 | <<-TAG_LOGIC 41 | tag_content << '>' 42 | if block 43 | prev_output = @current_output 44 | nested_content = String.new(capacity: 1024) 45 | @current_output = nested_content 46 | block_result = block.call 47 | @current_output = prev_output 48 | if block_result.is_a?(String) 49 | fast_buffer_append(tag_content, fast_escape_html(block_result)) 50 | else 51 | fast_buffer_append(tag_content, nested_content) 52 | end 53 | elsif content 54 | fast_buffer_append(tag_content, fast_escape_html(content)) 55 | end 56 | tag_content << '' 57 | TAG_LOGIC 58 | end} 59 | fast_buffer_append(@current_output, tag_content) 60 | end 61 | RUBY 62 | end.join("\n") 63 | 64 | # Evaluate all method definitions at once 65 | class_eval(METHOD_DEFINITIONS, __FILE__, __LINE__ + 1) 66 | 67 | attr_reader :output 68 | attr_accessor :current_output 69 | 70 | def initialize(context = nil, &root) 71 | @context = context 72 | @root = root 73 | @output = String.new(capacity: 4096) 74 | @current_output = @output 75 | end 76 | 77 | def __render_from_rails(template_path) 78 | result = render 79 | return result unless annotate_rendered_view_with_filenames? 80 | 81 | template_path = template_path.sub("#{Rails.root}/", '') 82 | comment_start = "" 83 | comment_end = "" 84 | 85 | final_result = String.new(capacity: result.length + comment_start.length + comment_end.length) 86 | final_result << comment_start 87 | final_result << result 88 | final_result << comment_end 89 | final_result.html_safe 90 | end if defined?(ActionView) 91 | 92 | def render(*args, **options, &block) 93 | set_instance_variables 94 | 95 | return plain @context.render(*args, **options, &block) if !args.empty? || !options.empty? || block_given? 96 | 97 | instance_exec(&@root) 98 | result = @output 99 | result = ActiveSupport::SafeBuffer.new(result) if defined?(ActiveSupport) 100 | result 101 | end 102 | 103 | def respond_to?(method_name, include_private = false) 104 | HTML5_TAGS.include?(method_name.to_s.tr('_', '-')) || super 105 | end 106 | 107 | def plain(text) 108 | if defined?(ActiveSupport) && (text.is_a?(ActiveSupport::SafeBuffer) || text.html_safe?) 109 | fast_buffer_append(@current_output, text) 110 | else 111 | fast_buffer_append(@current_output, fast_escape_html(text.to_s)) 112 | end 113 | end 114 | 115 | def component(component_output) 116 | fast_buffer_append(@current_output, component_output) 117 | end 118 | 119 | private 120 | def method_missing(method_name, *args, **options, &block) 121 | if @context.respond_to?(method_name) 122 | @context.send(method_name, *args, **options, &block) 123 | else 124 | super 125 | end 126 | end 127 | 128 | def respond_to_missing?(method_name, include_private = false) 129 | @context.respond_to?(method_name) || super 130 | end 131 | 132 | COMMON_RAILS_METHOD_HELPERS.each do |method| 133 | define_method(method) do |*args, **options, &block| 134 | constant = "Ruby2html::RailsComponents::#{method.to_s.camelize}".constantize 135 | constant.new(self, @context, method, *args, **options).render(&block) 136 | end 137 | end if defined?(ActionView) 138 | 139 | def escape_html(text) 140 | fast_escape_html(text.to_s) 141 | end 142 | 143 | def annotate_rendered_view_with_filenames? 144 | return @annotate_rendered_view_with_filenames if defined?(@annotate_rendered_view_with_filenames) 145 | @annotate_rendered_view_with_filenames = Rails.application.config.action_view.annotate_rendered_view_with_filenames 146 | end 147 | 148 | def set_instance_variables 149 | @context.instance_variables.each do |name| 150 | instance_variable_set(name, @context.instance_variable_get(name)) 151 | end 152 | end 153 | end 154 | end 155 | -------------------------------------------------------------------------------- /lib/gem/ruby2html/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Ruby2html 4 | VERSION = '1.6.6' 5 | end 6 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebyx07/ruby2html/8b26ce02c33a402c38255067536ebf1e1e7a598c/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebyx07/ruby2html/8b26ce02c33a402c38255067536ebf1e1e7a598c/log/.keep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/406-unsupported-browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Your browser is not supported (406) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

Your browser is not supported.

62 |

Please upgrade your browser to continue.

63 |
64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebyx07/ruby2html/8b26ce02c33a402c38255067536ebf1e1e7a598c/public/icon.png -------------------------------------------------------------------------------- /public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /ruby2html.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'lib/gem/ruby2html/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'ruby2html' 7 | spec.version = Ruby2html::VERSION 8 | spec.authors = ['sebi'] 9 | spec.email = ['gore.sebyx@yahoo.com'] 10 | 11 | spec.summary = 'Transform Ruby code into beautiful, structured HTML with C-optimized performance' 12 | spec.description = 'Ruby2HTML empowers developers to write view logic in pure Ruby, ' \ 13 | 'seamlessly converting it into clean, well-formatted HTML. ' \ 14 | 'Enhance your templating workflow, improve readability, and ' \ 15 | 'leverage the full power of Ruby in your views. ' \ 16 | 'Features include Rails integration, custom component support, ' \ 17 | 'automatic HTML beautification, and C-optimized rendering performance.' 18 | spec.homepage = 'https://github.com/sebyx07/ruby2html' 19 | spec.license = 'MIT' 20 | spec.required_ruby_version = '>= 3.0.0' 21 | 22 | spec.metadata['homepage_uri'] = spec.homepage 23 | spec.metadata['source_code_uri'] = spec.homepage 24 | 25 | # Include C extension 26 | spec.extensions = ['ext/ruby2html/extconf.rb'] 27 | 28 | # Include both lib and ext directories 29 | spec.files = Dir.glob('{lib,ext}/{**/*,*}') + 30 | ['README.md', File.basename(__FILE__)] 31 | 32 | # Set require paths for both the gem and extension 33 | spec.require_paths = %w[lib/gem lib] 34 | 35 | # Runtime dependencies 36 | spec.add_dependency 'htmlbeautifier', '>= 1.4' 37 | 38 | # Development dependencies 39 | spec.add_development_dependency 'rake', '~> 13.0' 40 | spec.add_development_dependency 'rake-compiler', '~> 1.2' 41 | spec.add_development_dependency 'minitest', '~> 5.14' 42 | spec.add_development_dependency 'benchmark-ips', '~> 2.10' 43 | end 44 | -------------------------------------------------------------------------------- /spec/benchmarks/requests_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe 'HTML vs Ruby2html Benchmark', type: :request do 6 | it 'compares requests per second for normal HTML and Ruby2html', skip: ENV['CI'].present? do 7 | Benchmark.ips do |x| 8 | # Configure the benchmark 9 | x.config(time: 60, warmup: 10) 10 | 11 | x.report('GET /benchmark/html (ERB)') do 12 | get '/benchmark/html' 13 | expect(response).to have_http_status(:success) 14 | end 15 | 16 | x.report('GET /benchmark/ruby (Ruby2html templates .html.rb)') do 17 | get '/benchmark/ruby' 18 | expect(response).to have_http_status(:success) 19 | end 20 | 21 | x.report('GET /benchmark/ruby (Ruby2html + view components)') do 22 | get '/benchmark/compruby' 23 | expect(response).to have_http_status(:success) 24 | end 25 | 26 | x.report('GET /benchmark/slim (Slim)') do 27 | get '/benchmark/slim' 28 | expect(response).to have_http_status(:success) 29 | end 30 | 31 | x.report('GET /benchmark/phlex (Phlex)') do 32 | get '/benchmark/phlex' 33 | expect(response).to have_http_status(:success) 34 | end 35 | 36 | # Compare the results 37 | x.compare! 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/features/home_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.feature 'Homes', type: :feature do 6 | it 'can visit root' do 7 | visit root_path 8 | expect(page).to have_content('Hello') 9 | end 10 | 11 | it 'can visit rb_files' do 12 | visit rb_files_path 13 | expect(page).to have_content('RbFiles') 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/gem/render_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | require 'active_support/core_ext/string/output_safety' 5 | 6 | RSpec.describe Ruby2html::Render do 7 | let(:context) { double('context') } 8 | let(:render) { described_class.new(context) { div { p 'Hello, World!' } } } 9 | 10 | describe '#render' do 11 | it 'renders basic HTML' do 12 | expect(render.render).to eq('

Hello, World!

') 13 | end 14 | 15 | it 'renders nested elements' do 16 | render = described_class.new(context) do 17 | div do 18 | h1 'Title' 19 | p 'Paragraph' 20 | end 21 | end 22 | expect(render.render).to eq('

Title

Paragraph

') 23 | end 24 | 25 | it 'handles attributes' do 26 | render = described_class.new(context) do 27 | div class: 'container', id: 'main' do 28 | p 'Content' 29 | end 30 | end 31 | expect(render.render).to eq('

Content

') 32 | end 33 | 34 | it 'escapes HTML in content' do 35 | render = described_class.new(context) { p '' } 36 | expect(render.render).to eq('

<script>alert("XSS")</script>

') 37 | end 38 | 39 | it 'handles void elements' do 40 | render = described_class.new(context) { img src: 'image.jpg', alt: 'An image' } 41 | expect(render.render).to eq('An image') 42 | end 43 | end 44 | 45 | describe '#plain' do 46 | it 'renders unescaped content for ActiveSupport::SafeBuffer' do 47 | safe_buffer = 'Safe HTML'.html_safe 48 | render = described_class.new(context) { plain safe_buffer } 49 | expect(render.render).to eq('Safe HTML') 50 | end 51 | 52 | it 'escapes regular strings' do 53 | render = described_class.new(context) { plain 'Unsafe HTML' } 54 | expect(render.render).to eq('<em>Unsafe HTML</em>') 55 | end 56 | end 57 | 58 | describe '#component' do 59 | it 'renders component output' do 60 | component_output = 'Content' 61 | render = described_class.new(context) { component component_output } 62 | expect(render.render).to eq(component_output) 63 | end 64 | end 65 | 66 | describe 'method_missing' do 67 | it 'delegates unknown methods to context' do 68 | expect(context).to receive(:custom_helper).and_return('Custom Helper Output') 69 | render = described_class.new(context) { plain(custom_helper) } 70 | expect(render.render).to eq('Custom Helper Output') 71 | end 72 | 73 | it 'raises NoMethodError or NameError for undefined methods' do 74 | render = described_class.new(context) { undefined_method } 75 | expect { render.render }.to raise_error(StandardError) # This will catch both NoMethodError and NameError 76 | end 77 | end 78 | 79 | describe 'instance variables' do 80 | it 'sets instance variables from context' do 81 | allow(context).to receive(:instance_variables).and_return([:@test_var]) 82 | allow(context).to receive(:instance_variable_get).with(:@test_var).and_return('Test Value') 83 | 84 | render = described_class.new(context) { plain @test_var } 85 | expect(render.render).to eq('Test Value') 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is copied to spec/ when you run 'rails generate rspec:install' 4 | require 'spec_helper' 5 | require 'capybara/rspec' 6 | require 'benchmark/ips' 7 | ENV['RAILS_ENV'] ||= 'test' 8 | require_relative '../config/environment' 9 | # Prevent database truncation if the environment is production 10 | abort('The Rails environment is running in production mode!') if Rails.env.production? 11 | require 'rspec/rails' 12 | # Add additional requires below this line. Rails is not loaded until this point! 13 | 14 | # Requires supporting ruby files with custom matchers and macros, etc, in 15 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 16 | # run as spec files by default. This means that files in spec/support that end 17 | # in _spec.rb will both be required and run as specs, causing the specs to be 18 | # run twice. It is recommended that you do not name files matching this glob to 19 | # end with _spec.rb. You can configure this pattern with the --pattern 20 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 21 | # 22 | # The following line is provided for convenience purposes. It has the downside 23 | # of increasing the boot-up time by auto-requiring all files in the support 24 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 25 | # require only the support files necessary. 26 | # 27 | # Rails.root.glob('spec/support/**/*.rb').sort.each { |f| require f } 28 | 29 | # Checks for pending migrations and applies them before tests are run. 30 | # If you are not using ActiveRecord, you can remove these lines. 31 | begin 32 | ActiveRecord::Migration.maintain_test_schema! 33 | rescue ActiveRecord::PendingMigrationError => e 34 | abort e.to_s.strip 35 | end 36 | RSpec.configure do |config| 37 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 38 | config.fixture_paths = [ 39 | Rails.root.join('spec/fixtures') 40 | ] 41 | 42 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 43 | # examples within a transaction, remove the following line or assign false 44 | # instead of true. 45 | config.use_transactional_fixtures = true 46 | 47 | # You can uncomment this line to turn off ActiveRecord support entirely. 48 | # config.use_active_record = false 49 | 50 | # RSpec Rails can automatically mix in different behaviours to your tests 51 | # based on their file location, for example enabling you to call `get` and 52 | # `post` in specs under `spec/controllers`. 53 | # 54 | # You can disable this behaviour by removing the line below, and instead 55 | # explicitly tag your specs with their type, e.g.: 56 | # 57 | # RSpec.describe UsersController, type: :controller do 58 | # # ... 59 | # end 60 | # 61 | # The different available types are documented in the features, such as in 62 | # https://rspec.info/features/6-0/rspec-rails 63 | config.infer_spec_type_from_file_location! 64 | 65 | # Filter lines from Rails gems in backtraces. 66 | config.filter_rails_from_backtrace! 67 | # arbitrary gems may also be filtered via: 68 | # config.filter_gems_from_backtrace("gem name") 69 | end 70 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 4 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 5 | # The generated `.rspec` file contains `--require spec_helper` which will cause 6 | # this file to always be loaded, without a need to explicitly require it in any 7 | # files. 8 | # 9 | # Given that it is always loaded, you are encouraged to keep this file as 10 | # light-weight as possible. Requiring heavyweight dependencies from this file 11 | # will add to the boot time of your test suite on EVERY test run, even for an 12 | # individual file that may not need all of that loaded. Instead, consider making 13 | # a separate helper file that requires the additional dependencies and performs 14 | # the additional setup, and require it from the spec files that actually need 15 | # it. 16 | # 17 | # See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 18 | RSpec.configure do |config| 19 | # rspec-expectations config goes here. You can use an alternate 20 | # assertion/expectation library such as wrong or the stdlib/minitest 21 | # assertions if you prefer. 22 | config.expect_with :rspec do |expectations| 23 | # This option will default to `true` in RSpec 4. It makes the `description` 24 | # and `failure_message` of custom matchers include text for helper methods 25 | # defined using `chain`, e.g.: 26 | # be_bigger_than(2).and_smaller_than(4).description 27 | # # => "be bigger than 2 and smaller than 4" 28 | # ...rather than: 29 | # # => "be bigger than 2" 30 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 31 | end 32 | 33 | # rspec-mocks config goes here. You can use an alternate test double 34 | # library (such as bogus or mocha) by changing the `mock_with` option here. 35 | config.mock_with :rspec do |mocks| 36 | # Prevents you from mocking or stubbing a method that does not exist on 37 | # a real object. This is generally recommended, and will default to 38 | # `true` in RSpec 4. 39 | mocks.verify_partial_doubles = true 40 | end 41 | 42 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 43 | # have no way to turn it off -- the option exists only for backwards 44 | # compatibility in RSpec 3). It causes shared context metadata to be 45 | # inherited by the metadata hash of host groups and examples, rather than 46 | # triggering implicit auto-inclusion in groups with matching metadata. 47 | config.shared_context_metadata_behavior = :apply_to_host_groups 48 | 49 | # The settings below are suggested to provide a good initial experience 50 | # with RSpec, but feel free to customize to your heart's content. 51 | =begin 52 | # This allows you to limit a spec run to individual examples or groups 53 | # you care about by tagging them with `:focus` metadata. When nothing 54 | # is tagged with `:focus`, all examples get run. RSpec also provides 55 | # aliases for `it`, `describe`, and `context` that include `:focus` 56 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 57 | config.filter_run_when_matching :focus 58 | 59 | # Allows RSpec to persist some state between runs in order to support 60 | # the `--only-failures` and `--next-failure` CLI options. We recommend 61 | # you configure your source control system to ignore this file. 62 | config.example_status_persistence_file_path = "spec/examples.txt" 63 | 64 | # Limits the available syntax to the non-monkey patched syntax that is 65 | # recommended. For more details, see: 66 | # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ 67 | config.disable_monkey_patching! 68 | 69 | # Many RSpec users commonly either run the entire suite or an individual 70 | # file, and it's useful to allow more verbose output when running an 71 | # individual spec file. 72 | if config.files_to_run.one? 73 | # Use the documentation formatter for detailed output, 74 | # unless a formatter has already been configured 75 | # (e.g. via a command-line flag). 76 | config.default_formatter = "doc" 77 | end 78 | 79 | # Print the 10 slowest examples and example groups at the 80 | # end of the spec run, to help surface which specs are running 81 | # particularly slow. 82 | config.profile_examples = 10 83 | 84 | # Run specs in random order to surface order dependencies. If you find an 85 | # order dependency and want to debug it, you can fix the order by providing 86 | # the seed, which is printed after each run. 87 | # --seed 1234 88 | config.order = :random 89 | 90 | # Seed global randomization in this process using the `--seed` CLI option. 91 | # Setting this allows you to use `--seed` to deterministically reproduce 92 | # test failures related to randomization by passing the same `--seed` value 93 | # as the one that triggered the failure. 94 | Kernel.srand config.seed 95 | =end 96 | end 97 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebyx07/ruby2html/8b26ce02c33a402c38255067536ebf1e1e7a598c/storage/.keep -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebyx07/ruby2html/8b26ce02c33a402c38255067536ebf1e1e7a598c/tmp/.keep -------------------------------------------------------------------------------- /tmp/pids/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebyx07/ruby2html/8b26ce02c33a402c38255067536ebf1e1e7a598c/tmp/pids/.keep -------------------------------------------------------------------------------- /tmp/storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebyx07/ruby2html/8b26ce02c33a402c38255067536ebf1e1e7a598c/tmp/storage/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebyx07/ruby2html/8b26ce02c33a402c38255067536ebf1e1e7a598c/vendor/.keep --------------------------------------------------------------------------------