├── .github └── workflows │ └── test.yml ├── .gitignore ├── .yardopts ├── CHANGES ├── Gemfile ├── LICENSE ├── README.jp.md ├── README.md ├── Rakefile ├── bin └── slimrb ├── doc ├── include.md ├── jp │ ├── include.md │ ├── logic_less.md │ ├── smart.md │ └── translator.md ├── logic_less.md ├── smart.md └── translator.md ├── lib ├── slim.rb └── slim │ ├── code_attributes.rb │ ├── command.rb │ ├── controls.rb │ ├── do_inserter.rb │ ├── embedded.rb │ ├── end_inserter.rb │ ├── engine.rb │ ├── erb_converter.rb │ ├── filter.rb │ ├── grammar.rb │ ├── include.rb │ ├── interpolation.rb │ ├── logic_less.rb │ ├── logic_less │ ├── context.rb │ └── filter.rb │ ├── parser.rb │ ├── railtie.rb │ ├── smart.rb │ ├── smart │ ├── escaper.rb │ ├── filter.rb │ └── parser.rb │ ├── splat │ ├── builder.rb │ └── filter.rb │ ├── template.rb │ ├── translator.rb │ └── version.rb ├── slim.gemspec └── test ├── core ├── helper.rb ├── test_code_blocks.rb ├── test_code_escaping.rb ├── test_code_evaluation.rb ├── test_code_output.rb ├── test_code_structure.rb ├── test_commands.rb ├── test_embedded_engines.rb ├── test_encoding.rb ├── test_erb_converter.rb ├── test_html_attributes.rb ├── test_html_escaping.rb ├── test_html_structure.rb ├── test_parser_errors.rb ├── test_pretty.rb ├── test_ruby_errors.rb ├── test_slim_template.rb ├── test_splat_prefix_option.rb ├── test_tabs.rb ├── test_text_interpolation.rb ├── test_thread_options.rb └── test_unicode.rb ├── include ├── files │ ├── recursive.slim │ ├── slimfile.slim │ ├── subdir │ │ └── test.slim │ └── textfile └── test_include.rb ├── literate ├── TESTS.md ├── helper.rb └── run.rb ├── logic_less └── test_logic_less.rb ├── rails ├── Rakefile ├── app │ ├── assets │ │ └── config │ │ │ └── manifest.js │ ├── controllers │ │ ├── application_controller.rb │ │ ├── entries_controller.rb │ │ └── slim_controller.rb │ ├── helpers │ │ └── application_helper.rb │ ├── models │ │ └── entry.rb │ └── views │ │ ├── entries │ │ └── edit.html.slim │ │ ├── layouts │ │ ├── application.html+testvariant.slim │ │ └── application.html.slim │ │ └── slim │ │ ├── _partial.html.slim │ │ ├── attributes.html.slim │ │ ├── content_for.html.slim │ │ ├── erb.html.erb │ │ ├── form_for.html.slim │ │ ├── helper.html.slim │ │ ├── integers.html.slim │ │ ├── no_layout.html.slim │ │ ├── normal.html.slim │ │ ├── partial.html.slim │ │ ├── splat.html.slim │ │ ├── splat_with_delimiter.slim │ │ ├── thread_options.html.slim │ │ ├── variables.html.slim │ │ └── xml.slim ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── environment.rb │ ├── environments │ │ └── test.rb │ ├── initializers │ │ ├── backtrace_silencers.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ └── session_store.rb │ ├── locales │ │ └── en.yml │ └── routes.rb ├── script │ └── rails └── test │ ├── helper.rb │ └── test_slim.rb ├── sinatra ├── contest.rb ├── helper.rb ├── test_core.rb ├── test_include.rb └── views │ ├── embed_include_js.slim │ ├── embed_js.slim │ ├── footer.slim │ ├── hello.slim │ └── layout2.slim ├── smart └── test_smart_text.rb └── translator └── test_translator.rb /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | paths-ignore: 5 | - '**.md' 6 | pull_request: 7 | paths-ignore: 8 | - '**.md' 9 | schedule: 10 | - cron: "0 15 * * 0" 11 | jobs: 12 | basic: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | ruby: ['2.5', '2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4'] 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Set up Ruby 21 | uses: ruby/setup-ruby@v1 22 | with: 23 | ruby-version: ${{ matrix.ruby }} 24 | - run: bundle update && bundle exec rake test 25 | - run: bundle update && bundle exec rake test:sinatra 26 | name: "rake test:sinatra SINATRA=2.2.4" 27 | env: 28 | SINATRA: 2.2.4 29 | - run: bundle update && bundle exec rake test:sinatra 30 | name: "rake test:sinatra SINATRA=3.0.6" 31 | if: ${{ matrix.ruby != '2.5' }} 32 | env: 33 | SINATRA: 3.0.6 34 | - run: bundle update && bundle exec rake test:sinatra 35 | name: "rake test:sinatra SINATRA=3.1.0" 36 | if: ${{ matrix.ruby != '2.5' }} 37 | env: 38 | SINATRA: 3.1.0 39 | - run: bundle update && bundle exec rake test:sinatra 40 | name: "rake test:sinatra SINATRA=3.2.0" 41 | if: ${{ matrix.ruby != '2.5' }} 42 | env: 43 | SINATRA: 3.2.0 44 | - run: bundle update && bundle exec rake test:sinatra 45 | name: "rake test:sinatra SINATRA=4.0.0" 46 | if: ${{ matrix.ruby != '2.5' && matrix.ruby != '2.6' }} 47 | env: 48 | SINATRA: 4.0.0 49 | - run: bundle update && bundle exec rake test:sinatra 50 | name: "rake test:sinatra SINATRA=4.1.1" 51 | if: ${{ matrix.ruby != '2.5' && matrix.ruby != '2.6' }} 52 | env: 53 | SINATRA: 4.1.1 54 | - run: bundle update && bundle exec rake test:sinatra 55 | name: "rake test:sinatra SINATRA=main" 56 | if: ${{ matrix.ruby != '2.5' && matrix.ruby != '2.6' }} 57 | env: 58 | SINATRA: main 59 | rails-5: 60 | runs-on: ubuntu-latest 61 | strategy: 62 | fail-fast: false 63 | steps: 64 | - uses: actions/checkout@v4 65 | - name: Set up Ruby 66 | uses: ruby/setup-ruby@v1 67 | with: 68 | ruby-version: 2.7 69 | - run: bundle update && bundle exec rake test:rails 70 | name: "rake test:rails RAILS=5.2.8" 71 | env: 72 | RAILS: 5.2.8 73 | rails-6: 74 | runs-on: ubuntu-latest 75 | strategy: 76 | fail-fast: false 77 | steps: 78 | - uses: actions/checkout@v4 79 | - name: Set up Ruby 80 | uses: ruby/setup-ruby@v1 81 | with: 82 | ruby-version: 2.7 83 | - run: bundle update && bundle exec rake test:rails 84 | name: "rake test:rails RAILS=6.0.6" 85 | env: 86 | RAILS: 6.0.6 87 | - run: bundle update && bundle exec rake test:rails 88 | name: "rake test:rails RAILS=6.1.7" 89 | env: 90 | RAILS: 6.1.7 91 | rails-7: 92 | runs-on: ubuntu-latest 93 | strategy: 94 | fail-fast: false 95 | steps: 96 | - uses: actions/checkout@v4 97 | - name: Set up Ruby 98 | uses: ruby/setup-ruby@v1 99 | with: 100 | ruby-version: 3.3 101 | - run: bundle update && bundle exec rake test:rails 102 | name: "rake test:rails RAILS=7.0.8" 103 | env: 104 | RAILS: 7.0.8 105 | - run: bundle update && bundle exec rake test:rails 106 | name: "rake test:rails RAILS=7.1.5" 107 | env: 108 | RAILS: 7.1.5 109 | - run: bundle update && bundle exec rake test:rails 110 | name: "rake test:rails RAILS=7.2.2" 111 | env: 112 | RAILS: 7.2.2 113 | rails-8: 114 | runs-on: ubuntu-latest 115 | strategy: 116 | fail-fast: false 117 | steps: 118 | - uses: actions/checkout@v4 119 | - name: Set up Ruby 120 | uses: ruby/setup-ruby@v1 121 | with: 122 | ruby-version: 3.4 123 | - run: bundle update && bundle exec rake test:rails 124 | name: "rake test:rails RAILS=8.0.1" 125 | env: 126 | RAILS: 8.0.1 127 | - run: bundle update && bundle exec rake test:rails 128 | name: "rake test:rails RAILS=main" 129 | env: 130 | RAILS: main 131 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.gem 3 | Gemfile.lock 4 | .bundle 5 | .redcar 6 | .rvmrc 7 | .yardoc 8 | coverage 9 | pkg 10 | test/rails/log 11 | test/rails/tmp 12 | /.ruby-gemset 13 | /.ruby-version 14 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup-provider redcarpet 2 | --markup markdown 3 | - README.md CHANGES LICENSE test/literate/TESTS.md doc/logic_less.md doc/translator.md doc/smart.md doc/include.md 4 | 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org/' 2 | 3 | gemspec 4 | 5 | gem 'minitest', '~> 5.15' 6 | gem 'rake', '~> 13.0' 7 | gem 'kramdown', '~> 2.4' 8 | 9 | if ENV['TEMPLE'] && ENV['TEMPLE'] != 'master' 10 | gem 'temple', "= #{ENV['TEMPLE']}" 11 | else 12 | # Test against temple master by default 13 | gem 'temple', github: 'judofyr/temple' 14 | end 15 | 16 | if ENV['TILT'] 17 | if ENV['TILT'] == 'master' 18 | gem 'tilt', github: 'jeremyevans/tilt' 19 | else 20 | gem 'tilt', "= #{ENV['TILT']}" 21 | end 22 | end 23 | 24 | if ENV['RAILS'] 25 | gem 'rails-controller-testing' 26 | 27 | # we need some smarter test logic for the different Rails versions 28 | if ENV['RAILS'] == 'main' 29 | gem 'rails', github: 'rails/rails', branch: 'main' 30 | else 31 | gem 'rails', "= #{ENV['RAILS']}" 32 | end 33 | end 34 | 35 | if ENV['SINATRA'] 36 | gem 'rack-test' 37 | 38 | if ENV['SINATRA'] == 'main' 39 | gem 'sinatra', github: 'sinatra/sinatra' 40 | else 41 | gem 'sinatra', "= #{ENV['SINATRA']}" 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010 - 2025 Slim Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'bundler/setup' 3 | Bundler::GemHelper.install_tasks 4 | rescue Exception 5 | end 6 | 7 | require 'rake/testtask' 8 | 9 | task 'test' => %w(test:core test:literate test:logic_less test:translator test:smart test:include) 10 | 11 | namespace 'test' do 12 | Rake::TestTask.new('core') do |t| 13 | t.libs << 'lib' << 'test/core' 14 | t.test_files = FileList['test/core/test_*.rb'] 15 | t.warning = true 16 | #t.ruby_opts << '-w' << '-v' 17 | end 18 | 19 | Rake::TestTask.new('literate') do |t| 20 | t.libs << 'lib' << 'test/literate' 21 | t.test_files = FileList['test/literate/run.rb'] 22 | t.warning = true 23 | end 24 | 25 | Rake::TestTask.new('logic_less') do |t| 26 | t.libs << 'lib' << 'test/core' 27 | t.test_files = FileList['test/logic_less/test_*.rb'] 28 | t.warning = true 29 | end 30 | 31 | Rake::TestTask.new('translator') do |t| 32 | t.libs << 'lib' << 'test/core' 33 | t.test_files = FileList['test/translator/test_*.rb'] 34 | t.warning = true 35 | end 36 | 37 | Rake::TestTask.new('smart') do |t| 38 | t.libs << 'lib' << 'test/core' 39 | t.test_files = FileList['test/smart/test_*.rb'] 40 | t.warning = true 41 | end 42 | 43 | Rake::TestTask.new('include') do |t| 44 | t.libs << 'lib' << 'test/core' 45 | t.test_files = FileList['test/include/test_*.rb'] 46 | t.warning = true 47 | end 48 | 49 | Rake::TestTask.new('rails') do |t| 50 | t.libs << 'lib' 51 | t.test_files = FileList['test/rails/test/test_*.rb'] 52 | t.warning = true 53 | end 54 | 55 | Rake::TestTask.new('sinatra') do |t| 56 | t.libs << 'lib' 57 | t.test_files = FileList['test/sinatra/test_*.rb'] 58 | 59 | # Copied from test task in Sinatra project to mimic their approach 60 | t.ruby_opts = ['-r rubygems'] if defined? Gem 61 | t.ruby_opts << '-I.' 62 | t.warning = true 63 | end 64 | end 65 | 66 | begin 67 | require 'yard' 68 | YARD::Rake::YardocTask.new do |t| 69 | t.files = %w(lib/**/*.rb) 70 | end 71 | rescue LoadError 72 | task :yard do 73 | abort 'YARD is not available. In order to run yard, you must: gem install yard' 74 | end 75 | end 76 | 77 | desc 'Generate Documentation' 78 | task doc: :yard 79 | 80 | task default: 'test' 81 | -------------------------------------------------------------------------------- /bin/slimrb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $:.unshift File.dirname(__FILE__) + '/../lib' 4 | require 'slim/command' 5 | 6 | Slim::Command.new(ARGV).run 7 | -------------------------------------------------------------------------------- /doc/include.md: -------------------------------------------------------------------------------- 1 | # Include 2 | 3 | The include plugin allows Slim templates to include other files. The .slim extension is appended automatically to the 4 | filename. If the included file is not a Slim file, it is interpreted as a text file with `#{interpolation}`. 5 | 6 | Example: 7 | 8 | include partial.slim 9 | include partial 10 | include partial.txt 11 | 12 | Enable the include plugin with 13 | 14 | require 'slim/include' 15 | 16 | # Options 17 | 18 | | Type | Name | Default | Purpose | 19 | | ---- | ---- | ------- | ------- | 20 | | Array | :include_dirs | [Dir.pwd, '.'] | Directories where to look for the files | 21 | -------------------------------------------------------------------------------- /doc/jp/include.md: -------------------------------------------------------------------------------- 1 | # インクルード 2 | 3 | インクルードプラグインを使うことで, Slim テンプレートに他の Slim ファイルを読み込むことができます。.slim 拡張子はファイル名に自動的に付加されます。 4 | 読み込まれたファイルが Slim でない場合は `#{文字列展開}` を含んだテキストファイルとして扱われます。 5 | 6 | 例: 7 | 8 | include partial.slim 9 | include partial 10 | include partial.txt 11 | 12 | インクルードプラグインを有効化 13 | 14 | require 'slim/include' 15 | 16 | # オプション 17 | 18 | | タイプ | 名前 | デフォルト値 | 目的 | 19 | | ------ | ---- | ------------ | ---- | 20 | | Array | :include_dirs | [Dir.pwd, '.'] | ファイルを検索するディレクトリ | 21 | -------------------------------------------------------------------------------- /doc/jp/logic_less.md: -------------------------------------------------------------------------------- 1 | # ロジックレスモード 2 | 3 | ロジックレスモードは [Mustache](https://github.com/defunkt/mustache) にインスパイアされています。ロジックレスモードは 4 | 例えば動的コンテンツを含む再帰的ハッシュツリーのような辞書オブジェクトを使います。 5 | 6 | ## 条件付き 7 | 8 | オブジェクトが false または empty? ではない場合, コンテンツが表示されます。 9 | 10 | - article 11 | h1 = title 12 | 13 | ## 反転条件付き 14 | 15 | オブジェクトが false または empty? の場合, コンテンツが表示されます。 16 | 17 | -! article 18 | p Sorry, article not found 19 | 20 | ## 繰り返し 21 | 22 | オブジェクトが配列の場合, この部分は繰り返されます。 23 | 24 | - articles 25 | tr: td = title 26 | 27 | ## ラムダ式 28 | 29 | Mustache のように, Slim はラムダ式をサポートします。 30 | 31 | = person 32 | = name 33 | 34 | ラムダ式は次のように定義できます: 35 | 36 | def lambda_method 37 | "
#{yield(name: 'Andrew')}
" 38 | end 39 | 40 | 任意に 1 つ以上のハッシュを `yield` に渡すことができます。複数のハッシュを渡した場合, 先述したようにブロックが繰り返されます。 41 | 42 | ## 辞書アクセス 43 | 44 | サンプルコード: 45 | 46 | - article 47 | h1 = title 48 | 49 | 辞書オブジェクトは `:dictionary_access` オプションによって設定された順序でアクセスされます。デフォルトの順序: 50 | 51 | 1. `シンボル` - `article.respond_to?(:has_key?)` 且つ `article.has_key?(:title)` の場合, Slim は `article[:title]` を実行します。 52 | 2. `文字列` - `article.respond_to?(:has_key?)` 且つ `article.has_key?('title')` の場合, Slim は `article['title']` を実行します。 53 | 3. `メソッド` - `article.respond_to?(:title)` の場合, Slim は `article.send(:title)` を実行します。 54 | 4. `インスタンス変数` - `article.instance_variable_defined?(@title)` の場合, Slim は `article.instance_variable_get @title` を実行します。 55 | 56 | すべて失敗した場合, Slim は親オブジェクトに対して同じ順序で title の参照を解決しようとします。この例では, 親オブジェクトはレンダリングしているテンプレートに対する辞書オブジェクトになります。 57 | 58 | ご想像のとおり, article への参照は辞書オブジェクトに対して同じ手順で行われます。インスタンス変数はビューのコードでは利用を許されていませんが, Slim はそれを見つけて使います。基本的には, テンプレートの中で @ プレフィックスを落として使っています。パラメータ付きメソッドの呼び出しは許可されません。 59 | 60 | 61 | ## 文字列 62 | 63 | `self` キーワードは検討中の要素を `.to_s` した値を返します。 64 | 65 | 辞書オブジェクトを与え, 66 | 67 | { 68 | article: [ 69 | 'Article 1', 70 | 'Article 2' 71 | ] 72 | } 73 | 74 | ビューで次のように 75 | 76 | - article 77 | tr: td = self 78 | 79 | これは次のようになります。 80 | 81 | 82 | Article 1 83 | 84 | 85 | Article 2 86 | 87 | 88 | 89 | ## Rails でロジックレスモード 90 | 91 | インストール: 92 | 93 | $ gem install slim 94 | 95 | require で指定: 96 | 97 | gem 'slim', require: 'slim/logic_less' 98 | 99 | 特定のアクションでのみロジックレスモードを有効化したい場合, まず設定でロジックレスモードを global に無効化します。 100 | 101 | Slim::Engine.set_options logic_less: false 102 | 103 | さらに, アクションの中でレンダリングする度にロジックレスモードを有効化します。 104 | 105 | class Controller 106 | def action 107 | Slim::Engine.with_options(logic_less: true) do 108 | render 109 | end 110 | end 111 | end 112 | 113 | ## Sinatra でロジックレスモード 114 | 115 | Sinatra には Slim のビルトインサポートがあります。しなければならないのはロジックレス Slim プラグインを require することです。config.ru で require できます: 116 | 117 | require 'slim/logic_less' 118 | 119 | これで準備は整いました! 120 | 121 | 特定のアクションでのみロジックレスモードを有効化したい場合, まず設定でロジックレスモードを global に無効化します。 122 | 123 | Slim::Engine.set_options logic_less: false 124 | 125 | さらに, アクションの中でレンダリングする度にロジックレスモードを有効化します。 126 | 127 | get '/page' 128 | slim :page, logic_less: true 129 | end 130 | 131 | ## オプション 132 | 133 | | 種類 | 名前 | デフォルト | 用途 | 134 | | ---- | ---- | ------- | ------- | 135 | | 真偽値 | :logic_less | true | ロジックレスモードを有効化 ('slim/logic_less' の required が必要) | 136 | | 文字列 | :dictionary | "self" | 変数が検索される辞書への参照 | 137 | | シンボル/配列<シンボル> | :dictionary_access | [:symbol, :string, :method, :instance_variable] | 辞書のアクセス順序 (:symbol, :string, :method, :instance_variable) | 138 | -------------------------------------------------------------------------------- /doc/jp/smart.md: -------------------------------------------------------------------------------- 1 | # スマートテキスト 2 | 3 | テキストとマークアップを組み合わせる最も簡単な方法は スマートテキストモード を使うことです。 4 | 5 | スマートテキストプラグインを有効化します。 6 | 7 | require 'slim/smart' 8 | 9 | 自動的に `:implicit_text` オプションが有効になることで, 10 | 次のように簡単にテキストを入力できるようになります: 11 | 12 | p 13 | This is text. 14 | 15 | 小文字のタグ名や暗黙的なテキスト行などの特殊文字で始まっていない行を 16 | Slim に自動的に処理させることができます。 17 | テキストが複数行にわたる場合, 単にそれらをインデントします。 18 | 19 | p 20 | This is text, 21 | and it spans 22 | several lines. 23 | 24 | `>` を使って明示的にテキストをマークすることもできます。 25 | 例えば, 小文字や特殊文字で始まっている場合, 26 | テキストが複数行にわたる場合, 27 | 単に見た目の一貫性のために, 28 | タグ名に大文字を使いたい場合, 29 | `:implicit_text` オプションを無効にしたままにする必要がある場合です。 30 | 31 | p 32 | > 'This is text, too.' 33 | p 34 | > 35 | This is text 36 | which spans 37 | several lines. 38 | 39 | `:smart_text_escaping` が有効化されている限り, 40 | エスケープされるべきテキストは自動的にエスケープされます。 41 | しかし, 不便にならないように検出された HTML エンティティはそのまま使われます。 42 | この方法はいつでも HTML のエスケープを気にすることのない, 43 | 最も理想的な方法です。 44 | 45 | h1 Questions & Answers 46 | footer 47 | Copyright © #{Time.now.year} 48 | 49 | スマートテキストの素晴らしいところの 1 つはマークアップとよくミックスしているところです。 50 | スマートテキストの行は通常改行を維持するので, 51 | 強調やリンクのような他のタグを簡単に混ぜ合わせて使うことができます: 52 | 53 | p 54 | Your credit card 55 | strong will not 56 | > be charged now. 57 | p 58 | Check 59 | a href=r(:faq) our FAQ 60 | > for more info. 61 | 62 | (タグから小文字のテキストを区別する場合明示的なテキストを示すインジケーター `>` の使用には注意してください)。 63 | 64 | インラインタグのまわりにスペースを入れたくない場合があります。 65 | 幸いなことにスマートテキストはこの一般的なケースを処理できます。 66 | スマートテキストのブロックが `:smart_text_begin_chars` で指定 (デフォルトは `,.;:!?)]}`) 67 | された文字で始まる場合には先頭の改行が行われません。 68 | 同様に, スマートテキストのブロックが `:smart_text_begin_chars` で指定 (デフォルトは `,.;:!?)]}`) 69 | された文字で終わる場合には改行されません。 70 | これによって通常のテキストとリンクや span タグを混在させることがとても容易になります: 71 | 72 | p 73 | Please proceed to 74 | a href="/" our homepage 75 | . 76 | p 77 | Status: failed ( 78 | a href="#1" details 79 | ). 80 | 81 | スマートテキストはタグのショートカットをも把握しているので, 82 | 次のような場合にも正しく対応します: 83 | 84 | .class 85 | #id 86 | #{'More'} 87 | i text 88 | ... 89 | 90 | 当然のことながら, これは短いテキストのスニペットでより便利に作業できることを意味しています。 91 | ほとんどがテキストのコンテンツの場合, Markdown や Textile のような 92 | ビルトインの埋め込みエンジンを使う方が良いでしょう。 93 | 94 | ## オプション 95 | 96 | | 種類 | 名前 | デフォルト | 用途 | 97 | | ---- | ---- | ---------- | ---- | 98 | | 真偽値 | :implicit_text | true | 暗黙的テキストの判別を有効化 | 99 | | 真偽値 | :smart_text | true | スマートテキストによる処理を有効化 | 100 | | 文字列 | :smart_text_begin_chars | ',.;:!?)]}' | スマートテキストで先頭の改行を抑制する文字 | 101 | | 文字列 | :smart_text_end_chars | '([{' | スマートテキストで末尾の改行を抑制する文字 | 102 | | 真偽値 | :smart_text_escaping | true | 設定すると, スマートテキスト中のエスケープが必要な HTML 文字は自動的にエスケープされる | 103 | -------------------------------------------------------------------------------- /doc/jp/translator.md: -------------------------------------------------------------------------------- 1 | # 翻訳/I18n 2 | 3 | 翻訳プラグインは Gettext, Fast-Gettext または Rails I18n を使ったテンプレートの自動翻訳機能を提供します。 4 | テンプレート内の静的テキストを翻訳版に変換します。 5 | 6 | 例: 7 | 8 | h1 Welcome to #{url}! 9 | 10 | Gettext は文字列を英語からドイツ語に変換し, 文字列が展開される部分は %1, %2, ... の順に変換されます。 11 | 12 | "Welcome to %1!" -> "Willkommen auf %1!" 13 | 14 | 次のようにレンダリングされます。 15 | 16 |

Willkommen auf github.com/slim-template/slim!

17 | 18 | 翻訳プラグインを有効化します。 19 | 20 | require 'slim/translator' 21 | 22 | # オプション 23 | 24 | | 種類 | 名前 | デフォルト | 用途 | 25 | | ---- | ---- | ---------- | ---- | 26 | | 真偽値 | :tr | true | 翻訳の有効化 ('slim/translator' の required が必要) | 27 | | シンボル | :tr_mode | :dynamic | 翻訳を :static = コンパイル時に実施, :dynamic = ランタイムで実施 | 28 | | 文字列 | :tr_fn | インストールされた翻訳ライブラリに依存 | 翻訳用ヘルパ, gettext の場合 '_' | 29 | -------------------------------------------------------------------------------- /doc/logic_less.md: -------------------------------------------------------------------------------- 1 | # Logic less mode 2 | 3 | Logic less mode is inspired by [Mustache](https://github.com/defunkt/mustache). Logic less mode uses a dictionary object 4 | e.g. a recursive hash tree which contains the dynamic content. 5 | 6 | ## Conditional 7 | 8 | If the object is not false or empty?, the content will show 9 | 10 | - article 11 | h1 = title 12 | 13 | ## Inverted conditional 14 | 15 | If the object is false or empty?, the content will show 16 | 17 | -! article 18 | p Sorry, article not found 19 | 20 | ## Iteration 21 | 22 | If the object is an array, the section will iterate 23 | 24 | - articles 25 | tr: td = title 26 | 27 | ## Lambdas 28 | 29 | Like mustache, Slim supports lambdas. 30 | 31 | = person 32 | = name 33 | 34 | The lambda method could be defined like this 35 | 36 | def lambda_method 37 | "
#{yield(name: 'Andrew')}
" 38 | end 39 | 40 | You can optionally pass one or more hashes to `yield`. If you pass multiple hashes, the block will be iterated as described above. 41 | 42 | ## Dictionary access 43 | 44 | Example code: 45 | 46 | - article 47 | h1 = title 48 | 49 | The dictionary object is accessed in the order given by the `:dictionary_access`. Default order: 50 | 51 | 1. `:symbol` - If `article.respond_to?(:has_key?)` and `article.has_key?(:title)`, Slim will execute `article[:title]` 52 | 2. `:string` - If `article.respond_to?(:has_key?)` and `article.has_key?('title')`, Slim will execute `article['title']` 53 | 3. `:method` - If `article.respond_to?(:title)`, Slim will execute `article.send(:title)` 54 | 4. `:instance_variable` - If `article.instance_variable_defined?(@title)`, Slim will execute `article.instance_variable_get @title` 55 | 56 | If all the above fails, Slim will try to resolve the title reference in the same order against the parent object. In this example, the parent would be the dictionary object you are rendering the template against. 57 | 58 | As you might have guessed, the article reference goes through the same steps against the dictionary. Instance variables are not allowed in the view code, but Slim will find and use them. Essentially, you're just dropping the @ prefix in your template. Parameterized method calls are not allowed. 59 | 60 | 61 | ## Strings 62 | 63 | The `self` keyword will return the `.to_s` value for the element under consideration. 64 | 65 | Given 66 | 67 | { 68 | article: [ 69 | 'Article 1', 70 | 'Article 2' 71 | ] 72 | } 73 | 74 | And 75 | 76 | - article 77 | tr: td = self 78 | 79 | This will yield 80 | 81 | 82 | Article 1 83 | 84 | 85 | Article 2 86 | 87 | 88 | 89 | ## Logic less in Rails 90 | 91 | Install: 92 | 93 | $ gem install slim 94 | 95 | Require: 96 | 97 | gem 'slim', require: 'slim/logic_less' 98 | 99 | You might want to activate logic less mode only for a few actions, you should disable logic-less mode globally at first in the configuration 100 | 101 | Slim::Engine.set_options logic_less: false 102 | 103 | and activate logic less mode per render call in your action 104 | 105 | class Controller 106 | def action 107 | Slim::Engine.with_options(logic_less: true) do 108 | render 109 | end 110 | end 111 | end 112 | 113 | ## Logic less in Sinatra 114 | 115 | Sinatra has built-in support for Slim. All you have to do is require the logic less Slim plugin. This can be done in your config.ru: 116 | 117 | require 'slim/logic_less' 118 | 119 | You are then ready to rock! 120 | 121 | You might want to activate logic less mode only for a few actions, you should disable logic-less mode globally at first in the configuration 122 | 123 | Slim::Engine.set_options logic_less: false 124 | 125 | and activate logic less mode per render call in your application 126 | 127 | get '/page' 128 | slim :page, logic_less: true 129 | end 130 | 131 | ## Options 132 | 133 | | Type | Name | Default | Purpose | 134 | | ---- | ---- | ------- | ------- | 135 | | Boolean | :logic_less | true | Enable logic less mode (Enabled if 'slim/logic_less' is required) | 136 | | String | :dictionary | "self" | Dictionary where variables are looked up | 137 | | Symbol/Array<Symbol> | :dictionary_access | [:symbol, :string, :method, :instance_variable] | Dictionary access order (:symbol, :string, :method, :instance_variable) | 138 | -------------------------------------------------------------------------------- /doc/smart.md: -------------------------------------------------------------------------------- 1 | # Smart text 2 | 3 | The smart text plugin was created to simplify the typing and combining of text and markup in Slim templates. 4 | Using the plugin gives you: 5 | 6 | * More convenient ways to type text in Slim templates. 7 | * Smarter and more consistent HTML escaping of typed text. 8 | * Easier combining of the text with inline HTML tags with smart handling of whitespace on the boundaries. 9 | 10 | To get started, enable the smart text plugin with 11 | 12 | require 'slim/smart' 13 | 14 | First of all, this automatically enables the `:implicit_text` option. 15 | When enabled, Slim will treat any line which doesn't start 16 | with a lowercase tag name or any of the special characters as an implicit text line. 17 | If the text needs to span several lines, indent them as usual. 18 | 19 | This allows you to easily type text like this, without any leading indicator: 20 | 21 | This is an implicit text. 22 | This is a text 23 | which spans 24 | several lines. 25 | This is yet another text. 26 | 27 | This works in addition to ways already available in stock Slim: 28 | 29 | p This is an inline text. 30 | p This is an inline text 31 | which spans multiple lines. 32 | | This is a verbatim text. 33 | | This is a verbatim text 34 | which spans multiple lines. 35 |

This is in fact a verbatim text as well.

36 | 37 | You can also mark the text explicitly with a leading `>`. 38 | This is used for example when the text starts with a lowercase letter or an unusual character, 39 | or merely for aesthetic consistency when it spans several lines. 40 | It may be also needed if you want to use uppercase tag names 41 | and therefore need to keep the `:implicit_text` option disabled. 42 | 43 | > This is an explicit text. 44 | > This is an explicit text 45 | which spans 46 | several lines. 47 | > 'This is a text, too.' 48 | 49 | > This is another way 50 | > of typing text which spans 51 | > several lines, if you prefer that. 52 | 53 | BTW, all these examples can be pasted to `slimrb -r slim/smart` to see how the generated output looks like. 54 | 55 | The plugin also improves upon Slim's automatic HTML escaping. 56 | As long as you leave the `:smart_text_escaping` enabled, 57 | any non-verbatim text (i.e., any implicit, explicit, and inline text) is automatically escaped for you. 58 | However, for your convenience, any HTML entities detected are still used verbatim. 59 | This way you are most likely to get what you really wanted, 60 | without having to worry about HTML escaping all the time. 61 | 62 | h1 Questions & Answers 63 | footer 64 | Copyright © #{Time.now.year} 65 | 66 | Another cool thing about the plugin is that it makes text mix fairly well with markup. 67 | The text lines are made to preserve newlines as needed, 68 | so it is easy to mix them with other tags, like emphasis or links: 69 | 70 | p 71 | Your credit card 72 | strong will not 73 | > be charged now. 74 | p 75 | Check 76 | a href='/faq' our FAQ 77 | > for more info. 78 | 79 | (Note the use of the explicit text indicator `>` to distinguish lowercase text from tags). 80 | 81 | However, sometimes you do not want any whitespace around the inline tag at all. 82 | Fortunately the plugin takes care of the most common cases for you as well. 83 | The newline before the tag is suppressed if the preceding line ends 84 | with a character from the `:smart_text_end_chars` set (`([{` by default). 85 | Similarly, the newline after the tag is suppressed if the following line begins 86 | with a character from the `:smart_text_begin_chars` set (`,.;:!?)]}` by default). 87 | This makes it quite easy to naturally mix normal text with links or spans like this: 88 | 89 | p 90 | Please proceed to 91 | a href="/" our homepage 92 | . 93 | p 94 | Status: failed ( 95 | a href="#1" see details 96 | ). 97 | 98 | Note that the plugin is smart enough to know about tag shortcuts, too, 99 | so it will correctly deal even with cases like this: 100 | 101 | .class 102 | #id 103 | #{'More'} 104 | i text 105 | ... 106 | 107 | And that's it. 108 | Of course, all this is meant only to make working with short text snippets more convenient. 109 | For bulk text content, you are more than welcome to use one of the builtin embedded engines, 110 | such as Markdown or Textile. 111 | 112 | ## Options 113 | 114 | | Type | Name | Default | Purpose | 115 | | ---- | ---- | ------- | ------- | 116 | | Boolean | :implicit_text | true | Enable implicit text recognition | 117 | | Boolean | :smart_text | true | Enable smart text mode newline processing | 118 | | String | :smart_text_begin_chars | ',.;:!?)]}' | Characters suppressing leading newline in smart text | 119 | | String | :smart_text_end_chars | '([{' | Characters suppressing trailing newline in smart text | 120 | | Boolean | :smart_text_escaping | true | When set, HTML characters which need escaping are automatically escaped in smart text | 121 | -------------------------------------------------------------------------------- /doc/translator.md: -------------------------------------------------------------------------------- 1 | # Translator/I18n 2 | 3 | The translator plugin provides automatic translation of the templates using Gettext, Fast-Gettext or Rails I18n. Static text 4 | in the template is replaced by the translated version. 5 | 6 | Example: 7 | 8 | h1 Welcome to #{url}! 9 | 10 | Gettext translates the string from english to german where interpolations are replaced by %1, %2, ... 11 | 12 | "Welcome to %1!" -> "Willkommen auf %1!" 13 | 14 | and renders as 15 | 16 |

Willkommen auf github.com/slim-template/slim!

17 | 18 | Enable the translator plugin with 19 | 20 | require 'slim/translator' 21 | 22 | # Options 23 | 24 | | Type | Name | Default | Purpose | 25 | | ---- | ---- | ------- | ------- | 26 | | Boolean | :tr | true | Enable translator (Enabled if 'slim/translator' is required) | 27 | | Symbol | :tr_mode | :dynamic | When to translate: :static = at compile time, :dynamic = at runtime | 28 | | String | :tr_fn | Depending on installed translation library | Translation function, could be '_' for gettext | 29 | -------------------------------------------------------------------------------- /lib/slim.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'temple' 3 | require 'slim/parser' 4 | require 'slim/filter' 5 | require 'slim/do_inserter' 6 | require 'slim/end_inserter' 7 | require 'slim/embedded' 8 | require 'slim/interpolation' 9 | require 'slim/controls' 10 | require 'slim/splat/filter' 11 | require 'slim/splat/builder' 12 | require 'slim/code_attributes' 13 | require 'slim/engine' 14 | require 'slim/template' 15 | require 'slim/version' 16 | require 'slim/railtie' if defined?(Rails::Railtie) 17 | -------------------------------------------------------------------------------- /lib/slim/code_attributes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | # @api private 4 | class CodeAttributes < Filter 5 | define_options :merge_attrs 6 | 7 | # Handle attributes expression `[:html, :attrs, *attrs]` 8 | # 9 | # @param [Array] attrs Array of temple expressions 10 | # @return [Array] Compiled temple expression 11 | def on_html_attrs(*attrs) 12 | [:multi, *attrs.map { |a| compile(a) }] 13 | end 14 | 15 | # Handle attribute expression `[:html, :attr, name, value]` 16 | # 17 | # @param [String] name Attribute name 18 | # @param [Array] value Value expression 19 | # @return [Array] Compiled temple expression 20 | def on_html_attr(name, value) 21 | if value[0] == :slim && value[1] == :attrvalue && !options[:merge_attrs][name] 22 | # We handle the attribute as a boolean attribute 23 | escape, code = value[2], value[3] 24 | case code 25 | when 'true' 26 | [:html, :attr, name, [:multi]] 27 | when 'false', 'nil' 28 | [:multi] 29 | else 30 | tmp = unique_name 31 | [:multi, 32 | [:code, "#{tmp} = #{code}"], 33 | [:if, tmp, 34 | [:if, "#{tmp} == true", 35 | [:html, :attr, name, [:multi]], 36 | [:html, :attr, name, [:escape, escape, [:dynamic, tmp]]]]]] 37 | end 38 | else 39 | # Attribute with merging 40 | @attr = name 41 | super 42 | end 43 | end 44 | 45 | # Handle attribute expression `[:slim, :attrvalue, escape, code]` 46 | # 47 | # @param [Boolean] escape Escape html 48 | # @param [String] code Ruby code 49 | # @return [Array] Compiled temple expression 50 | def on_slim_attrvalue(escape, code) 51 | # We perform attribute merging on Array values 52 | if delimiter = options[:merge_attrs][@attr] 53 | tmp = unique_name 54 | [:multi, 55 | [:code, "#{tmp} = #{code}"], 56 | [:if, "Array === #{tmp}", 57 | [:multi, 58 | [:code, "#{tmp} = #{tmp}.flatten"], 59 | [:code, "#{tmp}.map!(&:to_s)"], 60 | [:code, "#{tmp}.reject!(&:empty?)"], 61 | [:escape, escape, [:dynamic, "#{tmp}.join(#{delimiter.inspect})"]]], 62 | [:escape, escape, [:dynamic, tmp]]]] 63 | else 64 | [:escape, escape, [:dynamic, code]] 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/slim/command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'slim' 3 | require 'optparse' 4 | 5 | module Slim 6 | Engine.set_options pretty: false 7 | 8 | # Slim commandline interface 9 | # @api private 10 | class Command 11 | def initialize(args) 12 | @args = args 13 | @options = {} 14 | end 15 | 16 | # Run command 17 | def run 18 | @opts = OptionParser.new(&method(:set_opts)) 19 | @opts.parse!(@args) 20 | process 21 | end 22 | 23 | private 24 | 25 | # Configure OptionParser 26 | def set_opts(opts) 27 | opts.on('-s', '--stdin', 'Read input from standard input instead of an input file') do 28 | @options[:input] = $stdin 29 | end 30 | 31 | opts.on('--trace', 'Show a full traceback on error') do 32 | @options[:trace] = true 33 | end 34 | 35 | opts.on('-c', '--compile', 'Compile only but do not run') do 36 | @options[:compile] = true 37 | end 38 | 39 | opts.on('-e', '--erb', 'Convert to ERB') do 40 | @options[:erb] = true 41 | end 42 | 43 | opts.on('--rails', 'Generate rails compatible code (Implies --compile)') do 44 | Engine.set_options disable_capture: true, generator: Temple::Generators::RailsOutputBuffer 45 | @options[:compile] = true 46 | end 47 | 48 | opts.on('-r', '--require library', 'Load library or plugin with -r slim/plugin') do |lib| 49 | require lib.strip 50 | end 51 | 52 | opts.on('-p', '--pretty', 'Produce pretty html') do 53 | Engine.set_options pretty: true 54 | end 55 | 56 | opts.on('-o', '--option name=code', String, 'Set slim option') do |str| 57 | parts = str.split('=', 2) 58 | Engine.options[parts.first.gsub(/\A:/, '').to_sym] = eval(parts.last) 59 | end 60 | 61 | opts.on('-l', '--locals Hash|YAML|JSON', String, 'Set local variables') do |locals| 62 | @options[:locals] = 63 | if locals =~ /\A\s*\{\s*\p{Word}+:/ 64 | eval(locals) 65 | else 66 | require 'yaml' 67 | YAML.load(locals) 68 | end 69 | end 70 | 71 | opts.on_tail('-h', '--help', 'Show this message') do 72 | puts opts 73 | exit 74 | end 75 | 76 | opts.on_tail('-v', '--version', 'Print version') do 77 | puts "Slim #{VERSION}" 78 | exit 79 | end 80 | end 81 | 82 | # Process command 83 | def process 84 | args = @args.dup 85 | unless @options[:input] 86 | file = args.shift 87 | if file 88 | @options[:file] = file 89 | @options[:input] = File.open(file, 'r') 90 | else 91 | @options[:file] = 'STDIN' 92 | @options[:input] = $stdin 93 | end 94 | end 95 | 96 | locals = @options.delete(:locals) || {} 97 | result = 98 | if @options[:erb] 99 | require 'slim/erb_converter' 100 | ERBConverter.new(file: @options[:file]).call(@options[:input].read) 101 | elsif @options[:compile] 102 | Engine.new(file: @options[:file]).call(@options[:input].read) 103 | else 104 | Template.new(@options[:file]) { @options[:input].read }.render(nil, locals) 105 | end 106 | 107 | rescue Exception => ex 108 | raise ex if @options[:trace] || SystemExit === ex 109 | $stderr.print "#{ex.class}: " if ex.class != RuntimeError 110 | $stderr.puts ex.message 111 | $stderr.puts ' Use --trace for backtrace.' 112 | exit 1 113 | else 114 | unless @options[:output] 115 | file = args.shift 116 | @options[:output] = file ? File.open(file, 'w') : $stdout 117 | end 118 | @options[:output].puts(result) 119 | exit 0 120 | end 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /lib/slim/controls.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | # @api private 4 | class Controls < Filter 5 | define_options :disable_capture 6 | 7 | IF_RE = /\A(if|unless)\b|\bdo\s*(\|[^\|]*\|)?\s*$/ 8 | 9 | # Handle control expression `[:slim, :control, code, content]` 10 | # 11 | # @param [String] code Ruby code 12 | # @param [Array] content Temple expression 13 | # @return [Array] Compiled temple expression 14 | def on_slim_control(code, content) 15 | [:multi, 16 | [:code, code], 17 | compile(content)] 18 | end 19 | 20 | # Handle output expression `[:slim, :output, escape, code, content]` 21 | # 22 | # @param [Boolean] escape Escape html 23 | # @param [String] code Ruby code 24 | # @param [Array] content Temple expression 25 | # @return [Array] Compiled temple expression 26 | def on_slim_output(escape, code, content) 27 | if code =~ IF_RE 28 | tmp = unique_name 29 | 30 | [:multi, 31 | # Capture the result of the code in a variable. We can't do 32 | # `[:dynamic, code]` because it's probably not a complete 33 | # expression (which is a requirement for Temple). 34 | [:block, "#{tmp} = #{code}", 35 | 36 | # Capture the content of a block in a separate buffer. This means 37 | # that `yield` will not output the content to the current buffer, 38 | # but rather return the output. 39 | # 40 | # The capturing can be disabled with the option :disable_capture. 41 | # Output code in the block writes directly to the output buffer then. 42 | # Rails handles this by replacing the output buffer for helpers. 43 | options[:disable_capture] ? compile(content) : [:capture, unique_name, compile(content)]], 44 | 45 | # Output the content. 46 | [:escape, escape, [:dynamic, tmp]]] 47 | else 48 | [:multi, [:escape, escape, [:dynamic, code]], content] 49 | end 50 | end 51 | 52 | # Handle text expression `[:slim, :text, type, content]` 53 | # 54 | # @param [Symbol] type Text type 55 | # @param [Array] content Temple expression 56 | # @return [Array] Compiled temple expression 57 | def on_slim_text(type, content) 58 | compile(content) 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/slim/do_inserter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | # In Slim you don't need the do keyword sometimes. This 4 | # filter adds the missing keyword. 5 | # 6 | # - 10.times 7 | # | Hello 8 | # 9 | # @api private 10 | class DoInserter < Filter 11 | BLOCK_REGEX = /(\A(if|unless|else|elsif|when|in|begin|rescue|ensure|case)\b)|\bdo\s*(\|[^\|]*\|\s*)?\Z/ 12 | 13 | # Handle control expression `[:slim, :control, code, content]` 14 | # 15 | # @param [String] code Ruby code 16 | # @param [Array] content Temple expression 17 | # @return [Array] Compiled temple expression 18 | def on_slim_control(code, content) 19 | code += ' do' unless code =~ BLOCK_REGEX || empty_exp?(content) 20 | [:slim, :control, code, compile(content)] 21 | end 22 | 23 | # Handle output expression `[:slim, :output, escape, code, content]` 24 | # 25 | # @param [Boolean] escape Escape html 26 | # @param [String] code Ruby code 27 | # @param [Array] content Temple expression 28 | # @return [Array] Compiled temple expression 29 | def on_slim_output(escape, code, content) 30 | code += ' do' unless code =~ BLOCK_REGEX || empty_exp?(content) 31 | [:slim, :output, escape, code, compile(content)] 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/slim/embedded.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | # @api private 4 | class TextCollector < Filter 5 | def call(exp) 6 | @collected = ''.dup 7 | super(exp) 8 | @collected 9 | end 10 | 11 | def on_slim_interpolate(text) 12 | @collected << text 13 | nil 14 | end 15 | end 16 | 17 | # @api private 18 | class NewlineCollector < Filter 19 | def call(exp) 20 | @collected = [:multi] 21 | super(exp) 22 | @collected 23 | end 24 | 25 | def on_newline 26 | @collected << [:newline] 27 | nil 28 | end 29 | end 30 | 31 | # @api private 32 | class OutputProtector < Filter 33 | def call(exp) 34 | @protect, @collected, @tag = [], ''.dup, object_id.abs.to_s(36) 35 | super(exp) 36 | @collected 37 | end 38 | 39 | def on_static(text) 40 | @collected << text 41 | nil 42 | end 43 | 44 | def on_slim_output(escape, text, content) 45 | @collected << "%#{@tag}%#{@protect.length}%" 46 | @protect << [:slim, :output, escape, text, content] 47 | nil 48 | end 49 | 50 | def unprotect(text) 51 | block = [:multi] 52 | while text =~ /%#{@tag}%(\d+)%/ 53 | block << [:static, $`] 54 | block << @protect[$1.to_i] 55 | text = $' 56 | end 57 | block << [:static, text] 58 | end 59 | end 60 | 61 | # Temple filter which processes embedded engines 62 | # @api private 63 | class Embedded < Filter 64 | @engines = {} 65 | 66 | class << self 67 | attr_reader :engines 68 | 69 | # Register embedded engine 70 | # 71 | # @param [String] name Name of the engine 72 | # @param [Class] klass Engine class 73 | # @param option_filter List of options to pass to engine. 74 | # Last argument can be default option hash. 75 | def register(name, klass, *option_filter) 76 | name = name.to_sym 77 | local_options = option_filter.last.respond_to?(:to_hash) ? option_filter.pop.to_hash : {} 78 | define_options(name, *option_filter) 79 | klass.define_options(name) 80 | engines[name.to_sym] = proc do |options| 81 | klass.new({}.update(options).delete_if {|k,v| !option_filter.include?(k) && k != name }.update(local_options)) 82 | end 83 | end 84 | 85 | def create(name, options) 86 | constructor = engines[name] || raise(Temple::FilterError, "Embedded engine #{name} not found") 87 | constructor.call(options) 88 | end 89 | end 90 | 91 | define_options :enable_engines, :disable_engines 92 | 93 | def initialize(opts = {}) 94 | super 95 | @engines = {} 96 | @enabled = normalize_engine_list(options[:enable_engines]) 97 | @disabled = normalize_engine_list(options[:disable_engines]) 98 | end 99 | 100 | def on_slim_embedded(name, body, attrs) 101 | name = name.to_sym 102 | raise(Temple::FilterError, "Embedded engine #{name} is disabled") unless enabled?(name) 103 | @engines[name] ||= self.class.create(name, options) 104 | @engines[name].on_slim_embedded(name, body, attrs) 105 | end 106 | 107 | def enabled?(name) 108 | (!@enabled || @enabled.include?(name)) && 109 | (!@disabled || !@disabled.include?(name)) 110 | end 111 | 112 | protected 113 | 114 | def normalize_engine_list(list) 115 | raise(ArgumentError, "Option :enable_engines/:disable_engines must be String or Symbol list") unless !list || Array === list 116 | list && list.map(&:to_sym) 117 | end 118 | 119 | class Engine < Filter 120 | protected 121 | 122 | def collect_text(body) 123 | @text_collector ||= TextCollector.new 124 | @text_collector.call(body) 125 | end 126 | 127 | def collect_newlines(body) 128 | @newline_collector ||= NewlineCollector.new 129 | @newline_collector.call(body) 130 | end 131 | end 132 | 133 | # Basic tilt engine 134 | class TiltEngine < Engine 135 | def on_slim_embedded(engine, body, attrs) 136 | tilt_engine = Tilt[engine] || raise(Temple::FilterError, "Tilt engine #{engine} is not available.") 137 | tilt_options = options[engine.to_sym] || {} 138 | tilt_options[:default_encoding] ||= 'utf-8' 139 | [:multi, tilt_render(tilt_engine, tilt_options, collect_text(body)), collect_newlines(body)] 140 | end 141 | 142 | protected 143 | 144 | def tilt_render(tilt_engine, tilt_options, text) 145 | [:static, tilt_engine.new(tilt_options) { text }.render] 146 | end 147 | end 148 | 149 | # Sass engine which supports :pretty option 150 | class SassEngine < TiltEngine 151 | define_options :pretty 152 | 153 | protected 154 | 155 | def tilt_render(tilt_engine, tilt_options, text) 156 | text = tilt_engine.new(tilt_options.merge( 157 | style: options[:pretty] ? :expanded : :compressed 158 | )) { text }.render 159 | text = text.chomp 160 | [:static, text] 161 | end 162 | end 163 | 164 | # Static template with interpolated ruby code 165 | class InterpolateTiltEngine < TiltEngine 166 | def collect_text(body) 167 | output_protector.call(interpolation.call(body)) 168 | end 169 | 170 | def tilt_render(tilt_engine, tilt_options, text) 171 | output_protector.unprotect(tilt_engine.new(tilt_options) { text }.render) 172 | end 173 | 174 | private 175 | 176 | def interpolation 177 | @interpolation ||= Interpolation.new 178 | end 179 | 180 | def output_protector 181 | @output_protector ||= OutputProtector.new 182 | end 183 | end 184 | 185 | # Tag wrapper engine 186 | # Generates a html tag and wraps another engine (specified via :engine option) 187 | class TagEngine < Engine 188 | disable_option_validator! 189 | set_options attributes: {} 190 | 191 | def on_slim_embedded(engine, body, attrs) 192 | unless options[:attributes].empty? 193 | options[:attributes].map do |k, v| 194 | attrs << [:html, :attr, k, [:static, v]] 195 | end 196 | end 197 | 198 | if options[:engine] 199 | opts = {}.update(options) 200 | opts.delete(:engine) 201 | opts.delete(:tag) 202 | opts.delete(:attributes) 203 | @engine ||= options[:engine].new(opts) 204 | body = @engine.on_slim_embedded(engine, body, attrs) 205 | end 206 | 207 | [:html, :tag, options[:tag], attrs, body] 208 | end 209 | end 210 | 211 | # Javascript wrapper engine. 212 | # Like TagEngine, but can wrap content in html comment or cdata. 213 | class JavaScriptEngine < TagEngine 214 | disable_option_validator! 215 | 216 | set_options tag: :script 217 | 218 | def on_slim_embedded(engine, body, attrs) 219 | super(engine, [:html, :js, body], attrs) 220 | end 221 | end 222 | 223 | # Embeds ruby code 224 | class RubyEngine < Engine 225 | def on_slim_embedded(engine, body, attrs) 226 | [:multi, [:newline], [:code, "#{collect_text(body)}\n"]] 227 | end 228 | end 229 | 230 | # These engines are executed at compile time, embedded ruby is interpolated 231 | register :markdown, InterpolateTiltEngine 232 | register :textile, InterpolateTiltEngine 233 | register :rdoc, InterpolateTiltEngine 234 | 235 | # These engines are executed at compile time 236 | register :coffee, JavaScriptEngine, engine: TiltEngine 237 | register :sass, TagEngine, :pretty, tag: :style, engine: SassEngine 238 | register :scss, TagEngine, :pretty, tag: :style, engine: SassEngine 239 | 240 | # Embedded javascript/css 241 | register :javascript, JavaScriptEngine 242 | register :css, TagEngine, tag: :style 243 | 244 | # Embedded ruby code 245 | register :ruby, RubyEngine 246 | end 247 | end 248 | -------------------------------------------------------------------------------- /lib/slim/end_inserter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | # In Slim you don't need to close any blocks: 4 | # 5 | # - if Slim.awesome? 6 | # | But of course it is! 7 | # 8 | # However, the parser is not smart enough (and that's a good thing) to 9 | # automatically insert end's where they are needed. Luckily, this filter 10 | # does *exactly* that (and it does it well!) 11 | # 12 | # @api private 13 | class EndInserter < Filter 14 | IF_RE = /\A(if|begin|unless|else|elsif|when|in|rescue|ensure)\b|\bdo\s*(\|[^\|]*\|)?\s*$/ 15 | ELSE_RE = /\A(else|elsif|when|in|rescue|ensure)\b/ 16 | END_RE = /\Aend\b/ 17 | 18 | # Handle multi expression `[:multi, *exps]` 19 | # 20 | # @return [Array] Corrected Temple expression with ends inserted 21 | def on_multi(*exps) 22 | result = [:multi] 23 | # This variable is true if the previous line was 24 | # (1) a control code and (2) contained indented content. 25 | prev_indent = false 26 | 27 | exps.each do |exp| 28 | if control?(exp) 29 | raise(Temple::FilterError, 'Explicit end statements are forbidden') if exp[2] =~ END_RE 30 | 31 | # Two control code in a row. If this one is *not* 32 | # an else block, we should close the previous one. 33 | append_end(result) if prev_indent && exp[2] !~ ELSE_RE 34 | 35 | # Indent if the control code starts a block. 36 | prev_indent = exp[2] =~ IF_RE 37 | elsif exp[0] != :newline && prev_indent 38 | # This is *not* a control code, so we should close the previous one. 39 | # Ignores newlines because they will be inserted after each line. 40 | append_end(result) 41 | prev_indent = false 42 | end 43 | 44 | result << compile(exp) 45 | end 46 | 47 | # The last line can be a control code too. 48 | prev_indent ? append_end(result) : result 49 | end 50 | 51 | private 52 | 53 | # Appends an end 54 | def append_end(result) 55 | result << [:code, 'end'] 56 | end 57 | 58 | # Checks if an expression is a Slim control code 59 | def control?(exp) 60 | exp[0] == :slim && exp[1] == :control 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/slim/engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # The Slim module contains all Slim related classes (e.g. Engine, Parser). 3 | # Plugins might also reside within the Slim module (e.g. Include, Smart). 4 | # @api public 5 | module Slim 6 | # Slim engine which transforms slim code to executable ruby code 7 | # @api public 8 | class Engine < Temple::Engine 9 | # This overwrites some Temple default options or sets default options for Slim specific filters. 10 | # It is recommended to set the default settings only once in the code and avoid duplication. Only use 11 | # `define_options` when you have to override some default settings. 12 | define_options pretty: false, 13 | sort_attrs: true, 14 | format: :xhtml, 15 | attr_quote: '"', 16 | merge_attrs: {'class' => ' '}, 17 | generator: Temple::Generators::StringBuffer, 18 | default_tag: 'div' 19 | 20 | filter :Encoding 21 | filter :RemoveBOM 22 | use Slim::Parser 23 | use Slim::Embedded 24 | use Slim::Interpolation 25 | use Slim::Splat::Filter 26 | use Slim::DoInserter 27 | use Slim::EndInserter 28 | use Slim::Controls 29 | html :AttributeSorter 30 | html :AttributeMerger 31 | use Slim::CodeAttributes 32 | use(:AttributeRemover) { Temple::HTML::AttributeRemover.new(remove_empty_attrs: options[:merge_attrs].keys) } 33 | html :Pretty 34 | filter :Ambles 35 | filter :Escapable 36 | filter :StaticAnalyzer 37 | filter :ControlFlow 38 | filter :MultiFlattener 39 | filter :StaticMerger 40 | use(:Generator) { options[:generator] } 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/slim/erb_converter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'slim' 3 | 4 | module Slim 5 | # Slim to ERB converter 6 | # 7 | # @example Conversion 8 | # Slim::ERBConverter.new(options).call(slim_code) # outputs erb_code 9 | # 10 | # @api public 11 | class ERBConverter < Engine 12 | replace :StaticMerger, Temple::Filters::CodeMerger 13 | replace :Generator, Temple::Generators::ERB 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/slim/filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | # Base class for Temple filters used in Slim 4 | # 5 | # This base filter passes everything through and allows 6 | # to override only some methods without affecting the rest 7 | # of the expression. 8 | # 9 | # @api private 10 | class Filter < Temple::HTML::Filter 11 | # Pass-through handler 12 | def on_slim_text(type, content) 13 | [:slim, :text, type, compile(content)] 14 | end 15 | 16 | # Pass-through handler 17 | def on_slim_embedded(type, content, attrs) 18 | [:slim, :embedded, type, compile(content), attrs] 19 | end 20 | 21 | # Pass-through handler 22 | def on_slim_control(code, content) 23 | [:slim, :control, code, compile(content)] 24 | end 25 | 26 | # Pass-through handler 27 | def on_slim_output(escape, code, content) 28 | [:slim, :output, escape, code, compile(content)] 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/slim/grammar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | # Slim expression grammar 4 | # @api private 5 | module Grammar 6 | extend Temple::Grammar 7 | 8 | TextTypes << :verbatim | :explicit | :implicit | :inline 9 | 10 | Expression << 11 | [:slim, :control, String, Expression] | 12 | [:slim, :output, Bool, String, Expression] | 13 | [:slim, :interpolate, String] | 14 | [:slim, :embedded, String, Expression, HTMLAttrGroup] | 15 | [:slim, :text, TextTypes, Expression] | 16 | [:slim, :attrvalue, Bool, String] 17 | 18 | HTMLAttr << 19 | [:slim, :splat, String] 20 | 21 | HTMLAttrGroup << 22 | [:html, :attrs, 'HTMLAttr*'] 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/slim/include.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'slim' 3 | 4 | module Slim 5 | # Handles inlined includes 6 | # 7 | # Slim files are compiled, non-Slim files are included as text with `#{interpolation}` 8 | # 9 | # @api private 10 | class Include < Slim::Filter 11 | define_options :file, include_dirs: [Dir.pwd, '.'] 12 | 13 | def on_html_tag(tag, attributes, content = nil) 14 | return super if tag != 'include' 15 | name = content.to_a.flatten.select {|s| String === s }.join 16 | raise ArgumentError, 'Invalid include statement' unless attributes == [:html, :attrs] && !name.empty? 17 | unless file = find_file(name) 18 | name = "#{name}.slim" if name !~ /\.slim\Z/i 19 | file = find_file(name) 20 | end 21 | raise Temple::FilterError, "'#{name}' not found in #{options[:include_dirs].join(':')}" unless file 22 | content = File.read(file) 23 | if file =~ /\.slim\Z/i 24 | Thread.current[:slim_include_engine].call(content) 25 | else 26 | [:slim, :interpolate, content] 27 | end 28 | end 29 | 30 | protected 31 | 32 | def find_file(name) 33 | current_dir = File.dirname(File.expand_path(options[:file])) 34 | options[:include_dirs].map {|dir| File.expand_path(File.join(dir, name), current_dir) }.find {|file| File.file?(file) } 35 | end 36 | end 37 | 38 | class Engine 39 | after Slim::Parser, Slim::Include 40 | after Slim::Include, :stop do |exp| 41 | throw :stop, exp if Thread.current[:slim_include_level] > 1 42 | exp 43 | end 44 | 45 | # @api private 46 | alias call_without_include call 47 | 48 | # @api private 49 | def call(input) 50 | Thread.current[:slim_include_engine] = self 51 | Thread.current[:slim_include_level] ||= 0 52 | Thread.current[:slim_include_level] += 1 53 | catch(:stop) { call_without_include(input) } 54 | ensure 55 | Thread.current[:slim_include_engine] = nil if (Thread.current[:slim_include_level] -= 1) == 0 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/slim/interpolation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | # Perform interpolation of #{var_name} in the 4 | # expressions `[:slim, :interpolate, string]`. 5 | # 6 | # @api private 7 | class Interpolation < Filter 8 | # Handle interpolate expression `[:slim, :interpolate, string]` 9 | # 10 | # @param [String] string Static interpolate 11 | # @return [Array] Compiled temple expression 12 | def on_slim_interpolate(string) 13 | # Interpolate variables in text (#{variable}). 14 | # Split the text into multiple dynamic and static parts. 15 | block = [:multi] 16 | begin 17 | case string 18 | when /\A\\#\{/ 19 | # Escaped interpolation 20 | block << [:static, '#{'] 21 | string = $' 22 | when /\A#\{((?>[^{}]|(\{(?>[^{}]|\g<1>)*\}))*)\}/ 23 | # Interpolation 24 | string, code = $', $1 25 | escape = code !~ /\A\{.*\}\Z/ 26 | block << [:slim, :output, escape, escape ? code : code[1..-2], [:multi]] 27 | when /\A([#\\]?[^#\\]*([#\\][^\\#\{][^#\\]*)*)/ 28 | # Static text 29 | block << [:static, $&] 30 | string = $' 31 | end 32 | end until string.empty? 33 | block 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/slim/logic_less.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'slim' 3 | require 'slim/logic_less/filter' 4 | require 'slim/logic_less/context' 5 | 6 | Slim::Engine.after Slim::Interpolation, Slim::LogicLess 7 | -------------------------------------------------------------------------------- /lib/slim/logic_less/context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | class LogicLess 4 | # @api private 5 | class Context 6 | def initialize(dict, lookup) 7 | @scope = [Scope.new(dict, lookup)] 8 | end 9 | 10 | def [](name) 11 | scope[name] 12 | end 13 | 14 | def lambda(name) 15 | scope.lambda(name) do |*dict| 16 | if dict.empty? 17 | yield 18 | else 19 | new_scope do 20 | dict.inject(''.dup) do |result, d| 21 | scope.dict = d 22 | result << yield 23 | end 24 | end 25 | end 26 | end 27 | end 28 | 29 | def section(name) 30 | if dict = scope[name] 31 | if !dict.respond_to?(:has_key?) && dict.respond_to?(:each) 32 | new_scope do 33 | dict.each do |d| 34 | scope.dict = d 35 | yield 36 | end 37 | end 38 | else 39 | new_scope(dict) { yield } 40 | end 41 | end 42 | end 43 | 44 | def inverted_section(name) 45 | value = scope[name] 46 | yield if !value || (value.respond_to?(:empty?) && value.empty?) 47 | end 48 | 49 | def to_s 50 | scope.to_s 51 | end 52 | 53 | private 54 | 55 | class Scope 56 | attr_reader :lookup 57 | attr_writer :dict 58 | 59 | def initialize(dict, lookup, parent = nil) 60 | @dict, @lookup, @parent = dict, lookup, parent 61 | end 62 | 63 | def lambda(name, &block) 64 | @lookup.each do |lookup| 65 | case lookup 66 | when :method 67 | return @dict.public_send(name, &block) if @dict.respond_to?(name, false) 68 | when :symbol 69 | return @dict[name].call(&block) if has_key?(name) 70 | when :string 71 | return @dict[name.to_s].call(&block) if has_key?(name.to_s) 72 | when :instance_variable 73 | var_name = "@#{name}" 74 | return @dict.instance_variable_get(var_name).call(&block) if instance_variable?(var_name) 75 | end 76 | end 77 | @parent.lambda(name, &block) if @parent 78 | end 79 | 80 | def [](name) 81 | @lookup.each do |lookup| 82 | case lookup 83 | when :method 84 | return @dict.public_send(name) if @dict.respond_to?(name, false) 85 | when :symbol 86 | return @dict[name] if has_key?(name) 87 | when :string 88 | return @dict[name.to_s] if has_key?(name.to_s) 89 | when :instance_variable 90 | var_name = "@#{name}" 91 | return @dict.instance_variable_get(var_name) if instance_variable?(var_name) 92 | end 93 | end 94 | @parent[name] if @parent 95 | end 96 | 97 | def to_s 98 | @dict.to_s 99 | end 100 | 101 | private 102 | 103 | def has_key?(name) 104 | @dict.respond_to?(:has_key?) && @dict.has_key?(name) 105 | end 106 | 107 | def instance_variable?(name) 108 | @dict.instance_variable_defined?(name) 109 | rescue NameError 110 | false 111 | end 112 | end 113 | 114 | def scope 115 | @scope.last 116 | end 117 | 118 | def new_scope(dict = nil) 119 | @scope << Scope.new(dict, scope.lookup, scope) 120 | yield 121 | ensure 122 | @scope.pop 123 | end 124 | end 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /lib/slim/logic_less/filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | # Handle logic less mode 4 | # This filter can be activated with the option "logic_less" 5 | # @api private 6 | class LogicLess < Filter 7 | # Default dictionary access order, change it with the option :dictionary_access 8 | DEFAULT_ACCESS_ORDER = [:symbol, :string, :method, :instance_variable].freeze 9 | 10 | define_options logic_less: true, 11 | dictionary: 'self', 12 | dictionary_access: DEFAULT_ACCESS_ORDER 13 | 14 | def initialize(opts = {}) 15 | super 16 | access = [options[:dictionary_access]].flatten.compact 17 | access.each do |type| 18 | raise ArgumentError, "Invalid dictionary access #{type.inspect}" unless DEFAULT_ACCESS_ORDER.include?(type) 19 | end 20 | raise ArgumentError, 'Option dictionary access is missing' if access.empty? 21 | @access = access.inspect 22 | end 23 | 24 | def call(exp) 25 | if options[:logic_less] 26 | @context = unique_name 27 | [:multi, 28 | [:code, "#{@context} = ::Slim::LogicLess::Context.new(#{options[:dictionary]}, #{@access})"], 29 | super] 30 | else 31 | exp 32 | end 33 | end 34 | 35 | # Interpret control blocks as sections or inverted sections 36 | def on_slim_control(name, content) 37 | method = 38 | if name =~ /\A!\s*(.*)/ 39 | name = $1 40 | 'inverted_section' 41 | else 42 | 'section' 43 | end 44 | [:block, "#{@context}.#{method}(#{name.to_sym.inspect}) do", compile(content)] 45 | end 46 | 47 | def on_slim_output(escape, name, content) 48 | [:slim, :output, escape, empty_exp?(content) ? access(name) : 49 | "#{@context}.lambda(#{name.to_sym.inspect}) do", compile(content)] 50 | end 51 | 52 | def on_slim_attrvalue(escape, value) 53 | [:slim, :attrvalue, escape, access(value)] 54 | end 55 | 56 | def on_slim_splat(code) 57 | [:slim, :splat, access(code)] 58 | end 59 | 60 | def on_dynamic(code) 61 | raise Temple::FilterError, 'Embedded code is forbidden in logic less mode' 62 | end 63 | 64 | def on_code(code) 65 | raise Temple::FilterError, 'Embedded code is forbidden in logic less mode' 66 | end 67 | 68 | private 69 | 70 | def access(name) 71 | case name 72 | when 'yield' 73 | 'yield' 74 | when 'self' 75 | "#{@context}.to_s" 76 | else 77 | "#{@context}[#{name.to_sym.inspect}]" 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/slim/railtie.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Slim 4 | class Railtie < ::Rails::Railtie 5 | initializer 'initialize slim template handler' do 6 | ActiveSupport.on_load(:action_view) do 7 | Slim::RailsTemplate = Temple::Templates::Rails(Slim::Engine, 8 | register_as: :slim, 9 | # Use rails-specific generator. This is necessary 10 | # to support block capturing and streaming. 11 | generator: Temple::Generators::RailsOutputBuffer, 12 | # Disable the internal slim capturing. 13 | # Rails takes care of the capturing by itself. 14 | disable_capture: true, 15 | streaming: true) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/slim/smart.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'slim' 3 | require 'slim/smart/filter' 4 | require 'slim/smart/escaper' 5 | require 'slim/smart/parser' 6 | 7 | Slim::Engine.replace Slim::Parser, Slim::Smart::Parser 8 | Slim::Engine.after Slim::Smart::Parser, Slim::Smart::Filter 9 | Slim::Engine.after Slim::Interpolation, Slim::Smart::Escaper 10 | -------------------------------------------------------------------------------- /lib/slim/smart/escaper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | module Smart 4 | # Perform smart entity escaping in the 5 | # expressions `[:slim, :text, type, Expression]`. 6 | # 7 | # @api private 8 | class Escaper < ::Slim::Filter 9 | define_options smart_text_escaping: true 10 | 11 | def call(exp) 12 | if options[:smart_text_escaping] 13 | super 14 | else 15 | exp 16 | end 17 | end 18 | 19 | def on_slim_text(type, content) 20 | [:escape, type != :verbatim, [:slim, :text, type, compile(content)]] 21 | end 22 | 23 | def on_static(string) 24 | # Prevent obvious &foo; and Ӓ and ÿ entities from escaping. 25 | block = [:multi] 26 | until string.empty? 27 | case string 28 | when /\A&([a-z][a-z0-9]*|#x[0-9a-f]+|#\d+);/i 29 | # Entity. 30 | block << [:escape, false, [:static, $&]] 31 | string = $' 32 | when /\A&?[^&]*/ 33 | # Other text. 34 | block << [:static, $&] 35 | string = $' 36 | end 37 | end 38 | block 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/slim/smart/filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | module Smart 4 | # Perform newline processing in the 5 | # expressions `[:slim, :text, type, Expression]`. 6 | # 7 | # @api private 8 | class Filter < ::Slim::Filter 9 | define_options smart_text: true, 10 | smart_text_end_chars: '([{', 11 | smart_text_begin_chars: ',.;:!?)]}' 12 | 13 | def initialize(opts = {}) 14 | super 15 | @active = @prepend = @append = false 16 | @prepend_re = /\A#{chars_re(options[:smart_text_begin_chars])}/ 17 | @append_re = /#{chars_re(options[:smart_text_end_chars])}\Z/ 18 | end 19 | 20 | def call(exp) 21 | if options[:smart_text] 22 | super 23 | else 24 | exp 25 | end 26 | end 27 | 28 | def on_multi(*exps) 29 | # The [:multi] blocks serve two purposes. 30 | # On outer level, they collect the building blocks like 31 | # tags, verbatim text, and implicit/explicit text. 32 | # Within a text block, they collect the individual 33 | # lines in [:slim, :interpolate, string] blocks. 34 | # 35 | # Our goal here is to decide when we want to prepend and 36 | # append newlines to those individual interpolated lines. 37 | # We basically want the text to come out as it was originally entered, 38 | # while removing newlines next to the enclosing tags. 39 | # 40 | # On outer level, we choose to prepend every time, except 41 | # right after the opening tag or after other text block. 42 | # We also use the append flag to recognize the last expression 43 | # before the closing tag, as we don't want to append newline there. 44 | # 45 | # Within text block, we prepend only before the first line unless 46 | # the outer level tells us not to, and we append only after the last line, 47 | # unless the outer level tells us it is the last line before the closing tag. 48 | # Of course, this is later subject to the special begin/end characters 49 | # which may further suppress the newline at the corresponding line boundary. 50 | # Also note that the lines themselves are already correctly separated by newlines, 51 | # so we don't have to worry about that at all. 52 | block = [:multi] 53 | prev = nil 54 | last_exp = exps.reject { |exp| exp.first == :newline }.last unless @active && @append 55 | exps.each do |exp| 56 | @append = exp.equal?(last_exp) 57 | if @active 58 | @prepend = false if prev 59 | else 60 | @prepend = prev && (prev.first != :slim || prev[1] != :text) 61 | end 62 | block << compile(exp) 63 | prev = exp unless exp.first == :newline 64 | end 65 | block 66 | end 67 | 68 | def on_slim_text(type, content) 69 | @active = type != :verbatim 70 | [:slim, :text, type, compile(content)] 71 | ensure 72 | @active = false 73 | end 74 | 75 | def on_slim_text_inline(content) 76 | # Inline text is not wrapped in multi block, so set it up as if it was. 77 | @prepend = false 78 | @append = true 79 | on_slim_text(:inline, content) 80 | end 81 | 82 | def on_slim_interpolate(string) 83 | if @active 84 | string = "\n" + string if @prepend && string !~ @prepend_re 85 | string += "\n" if @append && string !~ @append_re 86 | end 87 | [:slim, :interpolate, string] 88 | end 89 | 90 | private 91 | 92 | def chars_re(string) 93 | Regexp.union(string.split(//)) 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/slim/smart/parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | module Smart 4 | # @api private 5 | class Parser < ::Slim::Parser 6 | define_options implicit_text: true 7 | 8 | def initialize(opts = {}) 9 | super 10 | word_re = options[:implicit_text] ? '[_a-z0-9]' : '\p{Word}' 11 | attr_keys = Regexp.union(@attr_shortcut.keys.sort_by { |k| -k.size }) 12 | @attr_shortcut_re = /\A(#{attr_keys}+)((?:\p{Word}|-)*)/ 13 | tag_keys = Regexp.union((@tag_shortcut.keys - @attr_shortcut.keys).sort_by { |k| -k.size }) 14 | @tag_re = /\A(?:#{attr_keys}(?=-*\p{Word})|#{tag_keys}|\*(?=[^\s]+)|(#{word_re}(?:#{word_re}|:|-)*#{word_re}|#{word_re}+))/ 15 | end 16 | 17 | def unknown_line_indicator 18 | if @line =~ /\A>( ?)/ 19 | # Found explicit text block. 20 | @stacks.last << [:slim, :text, :explicit, parse_text_block($', @indents.last + $1.size + 1)] 21 | else 22 | unless options[:implicit_text] 23 | syntax_error! 'Illegal shortcut' if @line =~ @attr_shortcut_re 24 | super 25 | end 26 | # Found implicit smart text block. 27 | if line = @lines.first 28 | indent = (line =~ /\A\s*\Z/ ? @indents.last + 1 : get_indent(line)) 29 | end 30 | @stacks.last << [:slim, :text, :implicit, parse_text_block(@line, indent)] 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/slim/splat/builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | class InvalidAttributeNameError < StandardError; end 4 | module Splat 5 | # @api private 6 | class Builder 7 | # https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 8 | INVALID_ATTRIBUTE_NAME_REGEX = /[ \0"'>\/=]/ 9 | 10 | def initialize(options) 11 | @options = options 12 | @attrs = {} 13 | end 14 | 15 | def code_attr(name, escape, value) 16 | if delim = @options[:merge_attrs][name] 17 | value = Array === value ? value.join(delim) : value.to_s 18 | attr(name, escape_html(escape, value)) unless value.empty? 19 | elsif @options[:hyphen_attrs].include?(name) && Hash === value 20 | hyphen_attr(name, escape, value) 21 | elsif value != false && value != nil 22 | attr(name, escape_html(escape, value)) 23 | end 24 | end 25 | 26 | def splat_attrs(splat) 27 | splat.each do |name, value| 28 | code_attr(name.to_s, true, value) 29 | end 30 | end 31 | 32 | def attr(name, value) 33 | if name =~ INVALID_ATTRIBUTE_NAME_REGEX 34 | raise InvalidAttributeNameError, "Invalid attribute name '#{name}' was rendered" 35 | end 36 | if @attrs[name] 37 | if delim = @options[:merge_attrs][name] 38 | @attrs[name] = @attrs[name].to_s + delim + value.to_s 39 | else 40 | raise("Multiple #{name} attributes specified") 41 | end 42 | else 43 | @attrs[name] = value 44 | end 45 | end 46 | 47 | def build_tag(&block) 48 | tag = @attrs.delete('tag').to_s 49 | tag = @options[:default_tag] if tag.empty? 50 | if block 51 | # This is a bit of a hack to get a universal capturing. 52 | # 53 | # TODO: Add this as a helper somewhere to solve these capturing issues 54 | # once and for all. 55 | # 56 | # If we have Slim capturing disabled and the scope defines the method `capture` (i.e. Rails) 57 | # we use this method to capture the content. 58 | # 59 | # otherwise we just use normal Slim capturing (yield). 60 | # 61 | # See https://github.com/slim-template/slim/issues/591 62 | # https://github.com/slim-template/slim#helpers-capturing-and-includes 63 | # 64 | content = 65 | if @options[:disable_capture] && (scope = block.binding.eval('self')).respond_to?(:capture) 66 | scope.capture(&block) 67 | else 68 | yield 69 | end 70 | "<#{tag}#{build_attrs}>#{content}" 71 | else 72 | "<#{tag}#{build_attrs} />" 73 | end 74 | end 75 | 76 | def build_attrs 77 | attrs = @options[:sort_attrs] ? @attrs.sort_by(&:first) : @attrs 78 | attrs.map do |k, v| 79 | if v == true 80 | if @options[:format] == :xhtml 81 | " #{k}=#{@options[:attr_quote]}#{@options[:attr_quote]}" 82 | else 83 | " #{k}" 84 | end 85 | else 86 | " #{k}=#{@options[:attr_quote]}#{v}#{@options[:attr_quote]}" 87 | end 88 | end.join 89 | end 90 | 91 | private 92 | 93 | def hyphen_attr(name, escape, value) 94 | if Hash === value 95 | if @options[:hyphen_underscore_attrs] 96 | value.each do |n, v| 97 | hyphen_attr("#{name}-#{n.to_s.tr('_', '-')}", escape, v) 98 | end 99 | else 100 | value.each do |n, v| 101 | hyphen_attr("#{name}-#{n}", escape, v) 102 | end 103 | end 104 | else 105 | attr(name, escape_html(escape, value)) 106 | end 107 | end 108 | 109 | def escape_html(escape, value) 110 | return value if !escape || value == true 111 | @options[:use_html_safe] ? Temple::Utils.escape_html_safe(value) : Temple::Utils.escape_html(value) 112 | end 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /lib/slim/splat/filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | module Splat 4 | # @api private 5 | class Filter < ::Slim::Filter 6 | define_options :merge_attrs, :attr_quote, :sort_attrs, :default_tag, :format, :disable_capture, 7 | hyphen_attrs: %w(data aria), use_html_safe: ''.respond_to?(:html_safe?), 8 | hyphen_underscore_attrs: false 9 | 10 | def call(exp) 11 | @splat_options = nil 12 | exp = compile(exp) 13 | if @splat_options 14 | opts = options.to_hash.reject { |k, v| !Filter.options.valid_key?(k) }.inspect 15 | [:multi, [:code, "#{@splat_options} = #{opts}"], exp] 16 | else 17 | exp 18 | end 19 | end 20 | 21 | # Handle tag expression `[:html, :tag, name, attrs, content]` 22 | # 23 | # @param [String] name Tag name 24 | # @param [Array] attrs Temple expression 25 | # @param [Array] content Temple expression 26 | # @return [Array] Compiled temple expression 27 | def on_html_tag(name, attrs, content = nil) 28 | return super if name != '*' 29 | builder, block = make_builder(attrs[2..-1]) 30 | if content 31 | [:multi, 32 | block, 33 | [:slim, :output, false, 34 | "#{builder}.build_tag #{empty_exp?(content) ? '{}' : 'do'}", 35 | compile(content)]] 36 | else 37 | [:multi, 38 | block, 39 | [:dynamic, "#{builder}.build_tag"]] 40 | end 41 | end 42 | 43 | # Handle attributes expression `[:html, :attrs, *attrs]` 44 | # 45 | # @param [Array] attrs Array of temple expressions 46 | # @return [Array] Compiled temple expression 47 | def on_html_attrs(*attrs) 48 | if attrs.any? { |attr| splat?(attr) } 49 | builder, block = make_builder(attrs) 50 | [:multi, 51 | block, 52 | [:dynamic, "#{builder}.build_attrs"]] 53 | else 54 | super 55 | end 56 | end 57 | 58 | protected 59 | 60 | def splat?(attr) 61 | # Splat attribute given 62 | attr[0] == :slim && attr[1] == :splat || 63 | # Hyphenated attribute also needs splat handling 64 | (attr[0] == :html && attr[1] == :attr && options[:hyphen_attrs].include?(attr[2]) && 65 | attr[3][0] == :slim && attr[3][1] == :attrvalue) 66 | end 67 | 68 | def make_builder(attrs) 69 | @splat_options ||= unique_name 70 | builder = unique_name 71 | result = [:multi, [:code, "#{builder} = ::Slim::Splat::Builder.new(#{@splat_options})"]] 72 | attrs.each do |attr| 73 | result << 74 | if attr[0] == :html && attr[1] == :attr 75 | if attr[3][0] == :slim && attr[3][1] == :attrvalue 76 | [:code, "#{builder}.code_attr(#{attr[2].inspect}, #{attr[3][2]}, (#{attr[3][3]}))"] 77 | else 78 | tmp = unique_name 79 | [:multi, 80 | [:capture, tmp, compile(attr[3])], 81 | [:code, "#{builder}.attr(#{attr[2].inspect}, #{tmp})"]] 82 | end 83 | elsif attr[0] == :slim && attr[1] == :splat 84 | [:code, "#{builder}.splat_attrs((#{attr[2]}))"] 85 | else 86 | attr 87 | end 88 | end 89 | [builder, result] 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/slim/template.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | # Tilt template implementation for Slim 4 | # @api public 5 | Template = Temple::Templates::Tilt(Slim::Engine, register_as: :slim) 6 | end 7 | -------------------------------------------------------------------------------- /lib/slim/translator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'slim' 3 | 4 | module Slim 5 | # @api private 6 | class Translator < Filter 7 | define_options :tr, 8 | tr_mode: :dynamic, 9 | tr_fn: '_' 10 | 11 | if defined?(::I18n) 12 | set_options tr_fn: '::Slim::Translator.i18n_text', 13 | tr: true 14 | elsif defined?(::GetText) 15 | set_options tr_fn: '::GetText._', 16 | tr: true 17 | elsif defined?(::FastGettext) 18 | set_options tr_fn: '::FastGettext::Translation._', 19 | tr: true 20 | end 21 | 22 | def self.i18n_text(text) 23 | I18n.t!(text) 24 | rescue I18n::MissingTranslationData 25 | text 26 | end 27 | 28 | def self.i18n_key(text) 29 | key = text.parameterize.underscore 30 | I18n.t!(key) 31 | rescue I18n::MissingTranslationData 32 | text 33 | end 34 | 35 | def call(exp) 36 | options[:tr] ? super : exp 37 | end 38 | 39 | def initialize(opts = {}) 40 | super 41 | case options[:tr_mode] 42 | when :static 43 | @translator = StaticTranslator.new(tr_fn: options[:tr_fn]) 44 | when :dynamic 45 | @translator = DynamicTranslator.new(tr_fn: options[:tr_fn]) 46 | else 47 | raise ArgumentError, "Invalid translator mode #{options[:tr_mode].inspect}" 48 | end 49 | end 50 | 51 | def on_slim_text(type, exp) 52 | [:slim, :text, type, @translator.call(exp)] 53 | end 54 | 55 | private 56 | 57 | class StaticTranslator < Filter 58 | define_options :tr_fn 59 | 60 | def initialize(opts = {}) 61 | super 62 | @translate = eval("proc {|string| #{options[:tr_fn]}(string) }") 63 | end 64 | 65 | def call(exp) 66 | @text, @captures = ''.dup, [] 67 | result = compile(exp) 68 | 69 | text = @translate.call(@text) 70 | while text =~ /%(\d+)/ 71 | result << [:static, $`] << @captures[$1.to_i - 1] 72 | text = $' 73 | end 74 | result << [:static, text] 75 | end 76 | 77 | def on_static(text) 78 | @text << text 79 | [:multi] 80 | end 81 | 82 | def on_slim_output(escape, code, content) 83 | @captures << [:slim, :output, escape, code, content] 84 | @text << "%#{@captures.size}" 85 | [:multi] 86 | end 87 | end 88 | 89 | class DynamicTranslator < Filter 90 | define_options :tr_fn 91 | 92 | def call(exp) 93 | @captures_count, @captures_var, @text = 0, unique_name, ''.dup 94 | 95 | result = compile(exp) 96 | 97 | if @captures_count > 0 98 | result.insert(1, [:code, "#{@captures_var}=[]"]) 99 | result << [:slim, :output, false, "#{options[:tr_fn]}(#{@text.inspect}).gsub(/%(\\d+)/) { #{@captures_var}[$1.to_i-1] }", [:multi]] 100 | else 101 | result << [:slim, :output, false, "#{options[:tr_fn]}(#{@text.inspect})", [:multi]] 102 | end 103 | end 104 | 105 | def on_static(text) 106 | @text << text 107 | [:multi] 108 | end 109 | 110 | def on_slim_output(escape, code, content) 111 | @captures_count += 1 112 | @text << "%#{@captures_count}" 113 | [:capture, "#{@captures_var}[#{@captures_count - 1}]", [:slim, :output, escape, code, content]] 114 | end 115 | end 116 | end 117 | end 118 | 119 | Slim::Engine.before Slim::EndInserter, Slim::Translator 120 | -------------------------------------------------------------------------------- /lib/slim/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Slim 3 | # Slim version string 4 | # @api public 5 | VERSION = '5.2.1' 6 | end 7 | -------------------------------------------------------------------------------- /slim.gemspec: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/lib/slim/version' 2 | require 'date' 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'slim' 6 | s.version = Slim::VERSION 7 | s.date = Date.today.to_s 8 | s.authors = ['Daniel Mendler', 'Andrew Stone', 'Fred Wu'] 9 | s.email = ['mail@daniel-mendler.de', 'andy@stonean.com', 'ifredwu@gmail.com'] 10 | s.summary = 'Slim is a template language.' 11 | s.description = 'Slim is a template language whose goal is reduce the syntax to the essential parts without becoming cryptic.' 12 | s.homepage = 'https://slim-template.github.io/' 13 | s.license = 'MIT' 14 | 15 | s.metadata = { 16 | "bug_tracker_uri" => "https://github.com/slim-template/slim/issues", 17 | "changelog_uri" => "https://github.com/slim-template/slim/blob/main/CHANGES", 18 | "documentation_uri" => "https://rubydoc.info/gems/slim/frames", 19 | "homepage_uri" => "https://slim-template.github.io/", 20 | "source_code_uri" => "https://github.com/slim-template/slim", 21 | "wiki_uri" => "https://github.com/slim-template/slim/wiki", 22 | "funding_uri" => "https://github.com/sponsors/slim-template" 23 | } 24 | 25 | s.files = `git ls-files`.split("\n") 26 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 27 | s.require_paths = %w(lib) 28 | 29 | s.required_ruby_version = '>= 2.5.0' 30 | 31 | s.add_runtime_dependency('temple', ['~> 0.10.0']) 32 | s.add_runtime_dependency('tilt', ['>= 2.1.0']) 33 | end 34 | -------------------------------------------------------------------------------- /test/core/helper.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'simplecov' 3 | SimpleCov.start 4 | rescue LoadError 5 | end 6 | 7 | require 'minitest/autorun' 8 | require 'slim' 9 | require 'slim/grammar' 10 | 11 | Slim::Engine.after Slim::Parser, Temple::Filters::Validator, grammar: Slim::Grammar 12 | Slim::Engine.before :Pretty, Temple::Filters::Validator 13 | 14 | class TestSlim < Minitest::Test 15 | def setup 16 | @env = Env.new 17 | end 18 | 19 | def render(source, options = {}, &block) 20 | scope = options.delete(:scope) 21 | locals = options.delete(:locals) 22 | Slim::Template.new(options[:file], options) { source }.render(scope || @env, locals, &block) 23 | end 24 | 25 | class HtmlSafeString < String 26 | def html_safe? 27 | true 28 | end 29 | 30 | def to_s 31 | self 32 | end 33 | end 34 | 35 | def with_html_safe 36 | String.send(:define_method, :html_safe?) { false } 37 | String.send(:define_method, :html_safe) { HtmlSafeString.new(self) } 38 | yield 39 | ensure 40 | String.send(:undef_method, :html_safe?) if String.method_defined?(:html_safe?) 41 | String.send(:undef_method, :html_safe) if String.method_defined?(:html_safe) 42 | end 43 | 44 | def assert_html(expected, source, options = {}, &block) 45 | assert_equal expected, render(source, options, &block) 46 | end 47 | 48 | def assert_syntax_error(message, source, options = {}) 49 | render(source, options) 50 | raise 'Syntax error expected' 51 | rescue Slim::Parser::SyntaxError => ex 52 | assert_equal message, ex.message 53 | message =~ /([^\s]+), Line (\d+)/ 54 | assert_backtrace ex, "#{$1}:#{$2}" 55 | end 56 | 57 | def assert_ruby_error(error, from, source, options = {}) 58 | render(source, options) 59 | raise 'Ruby error expected' 60 | rescue error => ex 61 | assert_backtrace(ex, from) 62 | end 63 | 64 | def assert_backtrace(ex, from) 65 | ex.backtrace[0] =~ /([^\s]+:\d+)/ 66 | assert_equal from, $1 67 | end 68 | 69 | def assert_ruby_syntax_error(from, source, options = {}) 70 | render(source, options) 71 | raise 'Ruby syntax error expected' 72 | rescue SyntaxError => ex 73 | ex.message =~ /([^\s]+:\d+):/ 74 | assert_equal from, $1 75 | end 76 | 77 | def assert_runtime_error(message, source, options = {}) 78 | render(source, options) 79 | raise Exception, 'Runtime error expected' 80 | rescue RuntimeError => ex 81 | assert_equal message, ex.message 82 | end 83 | end 84 | 85 | class Env 86 | attr_reader :var, :x 87 | 88 | def initialize 89 | @var = 'instance' 90 | @x = 0 91 | end 92 | 93 | def id_helper 94 | "notice" 95 | end 96 | 97 | def hash 98 | {a: 'The letter a', b: 'The letter b'} 99 | end 100 | 101 | def show_first?(show = false) 102 | show 103 | end 104 | 105 | def define_macro(name, &block) 106 | @macro ||= {} 107 | @macro[name.to_s] = block 108 | '' 109 | end 110 | 111 | def call_macro(name, *args) 112 | @macro[name.to_s].call(*args) 113 | end 114 | 115 | def hello_world(text = "Hello World from @env", opts = {}) 116 | text = text + (opts.to_a * " ") if opts.any? 117 | if block_given? 118 | "#{text} #{yield} #{text}" 119 | else 120 | text 121 | end 122 | end 123 | 124 | def message(*args) 125 | args.join(' ') 126 | end 127 | 128 | def action_path(*args) 129 | "/action-#{args.join('-')}" 130 | end 131 | 132 | def in_keyword 133 | "starts with keyword" 134 | end 135 | 136 | def evil_method 137 | "" 138 | end 139 | 140 | def output_number 141 | 1337 142 | end 143 | 144 | def succ_x 145 | @x = @x.succ 146 | end 147 | 148 | end 149 | 150 | class ViewEnv 151 | def output_number 152 | 1337 153 | end 154 | 155 | def person 156 | [{name: 'Joe'}, {name: 'Jack'}] 157 | end 158 | 159 | def people 160 | %w(Andy Fred Daniel).collect{|n| Person.new(n)} 161 | end 162 | 163 | def cities 164 | %w{Atlanta Melbourne Karlsruhe} 165 | end 166 | 167 | def people_with_locations 168 | array = [] 169 | people.each_with_index do |p,i| 170 | p.location = Location.new cities[i] 171 | array << p 172 | end 173 | array 174 | end 175 | end 176 | 177 | require 'forwardable' 178 | 179 | class Person 180 | extend Forwardable 181 | 182 | attr_accessor :name 183 | 184 | def initialize(name) 185 | @name = name 186 | end 187 | 188 | def location=(location) 189 | @location = location 190 | end 191 | 192 | def_delegators :@location, :city 193 | end 194 | 195 | class Location 196 | attr_accessor :city 197 | 198 | def initialize(city) 199 | @city = city 200 | end 201 | end 202 | -------------------------------------------------------------------------------- /test/core/test_code_blocks.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSlimCodeBlocks < TestSlim 4 | def test_render_with_output_code_block 5 | source = %q{ 6 | p 7 | = hello_world "Hello Ruby!" do 8 | | Hello from within a block! 9 | } 10 | 11 | assert_html '

Hello Ruby! Hello from within a block! Hello Ruby!

', source 12 | end 13 | 14 | def test_render_with_output_code_block_without_do 15 | source = %q{ 16 | p 17 | = hello_world "Hello Ruby!" 18 | | Hello from within a block! 19 | } 20 | 21 | assert_html '

Hello Ruby! Hello from within a block! Hello Ruby!

', source 22 | end 23 | 24 | def test_render_variable_ending_with_do 25 | source = %q{ 26 | - appelido=10 27 | p= appelido 28 | - appelido 29 | } 30 | 31 | assert_html '

10

', source 32 | end 33 | 34 | 35 | def test_render_with_output_code_within_block 36 | source = %q{ 37 | p 38 | = hello_world "Hello Ruby!" do 39 | = hello_world "Hello from within a block!" 40 | } 41 | 42 | assert_html '

Hello Ruby! Hello from within a block! Hello Ruby!

', source 43 | end 44 | 45 | def test_render_with_output_code_within_block_without_do 46 | source = %q{ 47 | p 48 | = hello_world "Hello Ruby!" 49 | = hello_world "Hello from within a block!" 50 | } 51 | 52 | assert_html '

Hello Ruby! Hello from within a block! Hello Ruby!

', source 53 | end 54 | 55 | def test_render_with_output_code_within_block_2 56 | source = %q{ 57 | p 58 | = hello_world "Hello Ruby!" do 59 | = hello_world "Hello from within a block!" do 60 | = hello_world "And another one!" 61 | } 62 | 63 | assert_html '

Hello Ruby! Hello from within a block! And another one! Hello from within a block! Hello Ruby!

', source 64 | end 65 | 66 | def test_render_with_output_code_within_block_2_without_do 67 | source = %q{ 68 | p 69 | = hello_world "Hello Ruby!" 70 | = hello_world "Hello from within a block!" 71 | = hello_world "And another one!" 72 | } 73 | 74 | assert_html '

Hello Ruby! Hello from within a block! And another one! Hello from within a block! Hello Ruby!

', source 75 | end 76 | 77 | def test_output_block_with_arguments 78 | source = %q{ 79 | p 80 | = define_macro :person do |first_name, last_name| 81 | .first_name = first_name 82 | .last_name = last_name 83 | == call_macro :person, 'John', 'Doe' 84 | == call_macro :person, 'Max', 'Mustermann' 85 | } 86 | 87 | assert_html '

John
Doe
Max
Mustermann

', source 88 | end 89 | 90 | 91 | def test_render_with_control_code_loop 92 | source = %q{ 93 | p 94 | - 3.times do 95 | | Hey! 96 | } 97 | 98 | assert_html '

Hey!Hey!Hey!

', source 99 | end 100 | 101 | def test_render_with_control_code_loop_without_do 102 | source = %q{ 103 | p 104 | - 3.times 105 | | Hey! 106 | } 107 | 108 | assert_html '

Hey!Hey!Hey!

', source 109 | end 110 | 111 | def test_captured_code_block_with_conditional 112 | source = %q{ 113 | = hello_world "Hello Ruby!" do 114 | - if true 115 | | Hello from within a block! 116 | } 117 | 118 | assert_html 'Hello Ruby! Hello from within a block! Hello Ruby!', source 119 | end 120 | 121 | def test_captured_code_block_with_conditional_without_do 122 | source = %q{ 123 | = hello_world "Hello Ruby!" 124 | - if true 125 | | Hello from within a block! 126 | } 127 | 128 | assert_html 'Hello Ruby! Hello from within a block! Hello Ruby!', source 129 | end 130 | 131 | def test_if_without_content 132 | source = %q{ 133 | - if true 134 | } 135 | assert_html '', source 136 | end 137 | 138 | def test_unless_without_content 139 | source = %q{ 140 | - unless true 141 | } 142 | assert_html '', source 143 | end 144 | 145 | def test_if_with_comment 146 | source = %q{ 147 | - if true 148 | / comment 149 | } 150 | assert_html '', source 151 | end 152 | 153 | def test_control_do_with_comment 154 | source = %q{ 155 | - hello_world "Hello" 156 | / comment 157 | } 158 | assert_html '', source 159 | end 160 | 161 | def test_output_do_with_comment 162 | source = %q{ 163 | = hello_world "Hello" 164 | / comment 165 | } 166 | assert_html 'Hello', source 167 | end 168 | 169 | def test_output_if_without_content 170 | source = %q{ 171 | = if true 172 | } 173 | assert_html '', source 174 | end 175 | 176 | def test_output_if_with_comment 177 | source = %q{ 178 | = if true 179 | / comment 180 | } 181 | assert_html '', source 182 | end 183 | 184 | def test_output_format_with_if 185 | source = %q{ 186 | h3.subtitle 187 | - if true 188 | a href="#" Title true 189 | - else 190 | a href="#" Title false 191 | } 192 | assert_html '

Title true

', source 193 | end 194 | end 195 | -------------------------------------------------------------------------------- /test/core/test_code_escaping.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSlimCodeEscaping < TestSlim 4 | def test_escaping_evil_method 5 | source = %q{ 6 | p = evil_method 7 | } 8 | 9 | assert_html '

<script>do_something_evil();</script>

', source 10 | end 11 | 12 | def test_render_without_html_safe 13 | source = %q{ 14 | p = "Hello World\\n, meet \\"Slim\\"." 15 | } 16 | 17 | assert_html "

<strong>Hello World\n, meet \"Slim\"</strong>.

", source 18 | end 19 | 20 | def test_render_without_html_safe2 21 | source = %q{ 22 | p = "Hello World\\n, meet 'Slim'." 23 | } 24 | 25 | assert_html "

<strong>Hello World\n, meet 'Slim'</strong>.

", source 26 | end 27 | 28 | def test_render_with_html_safe_false 29 | source = %q{ 30 | p = "Hello World\\n, meet \\"Slim\\"." 31 | } 32 | 33 | with_html_safe do 34 | assert_html "

<strong>Hello World\n, meet \"Slim\"</strong>.

", source, use_html_safe: true 35 | end 36 | end 37 | 38 | def test_render_with_html_safe_true 39 | source = %q{ 40 | p = "Hello World\\n, meet \\"Slim\\".".html_safe 41 | } 42 | 43 | with_html_safe do 44 | assert_html "

Hello World\n, meet \"Slim\".

", source, use_html_safe: true 45 | end 46 | end 47 | 48 | def test_render_splat_with_html_safe_true 49 | source = %q{ 50 | p *{ title: '&'.html_safe } 51 | } 52 | 53 | with_html_safe do 54 | assert_html "

", source, use_html_safe: true 55 | end 56 | end 57 | 58 | def test_render_splat_with_html_safe_false 59 | source = %q{ 60 | p *{ title: '&' } 61 | } 62 | 63 | with_html_safe do 64 | assert_html "

", source, use_html_safe: true 65 | end 66 | end 67 | 68 | def test_render_splat_injecting_evil_attr_name 69 | source = %q{ 70 | p *{ ">

'test' } 71 | } 72 | 73 | with_html_safe do 74 | assert_raises Slim::InvalidAttributeNameError do 75 | render(source, use_html_safe: true) 76 | end 77 | end 78 | end 79 | 80 | 81 | def test_render_attribute_with_html_safe_true 82 | source = %q{ 83 | p title=('&'.html_safe) 84 | } 85 | 86 | with_html_safe do 87 | assert_html "

", source, use_html_safe: true 88 | end 89 | end 90 | 91 | def test_render_with_disable_escape_false 92 | source = %q{ 93 | = "

Hello

" 94 | == "

World

" 95 | } 96 | 97 | assert_html "<p>Hello</p>

World

", source 98 | end 99 | 100 | def test_render_with_disable_escape_true 101 | source = %q{ 102 | = "

Hello

" 103 | == "

World

" 104 | } 105 | 106 | assert_html "

Hello

World

", source, disable_escape: true 107 | end 108 | 109 | def test_escaping_evil_method_with_pretty 110 | source = %q{ 111 | p = evil_method 112 | } 113 | 114 | assert_html "

\n <script>do_something_evil();</script>\n

", source, pretty: true 115 | end 116 | 117 | def test_render_without_html_safe_with_pretty 118 | source = %q{ 119 | p = "Hello World\\n, meet \\"Slim\\"." 120 | } 121 | 122 | assert_html "

\n <strong>Hello World\n , meet \"Slim\"</strong>.\n

", source, pretty: true 123 | end 124 | 125 | def test_render_with_html_safe_false_with_pretty 126 | source = %q{ 127 | p = "Hello World\\n, meet \\"Slim\\"." 128 | } 129 | 130 | with_html_safe do 131 | assert_html "

\n <strong>Hello World\n , meet \"Slim\"</strong>.\n

", source, use_html_safe: true, pretty: true 132 | end 133 | end 134 | 135 | def test_render_with_html_safe_true_with_pretty 136 | source = %q{ 137 | p = "Hello World\\n, meet \\"Slim\\".".html_safe 138 | } 139 | 140 | with_html_safe do 141 | assert_html "

\n Hello World\n , meet \"Slim\".\n

", source, use_html_safe: true, pretty: true 142 | end 143 | end 144 | 145 | def test_render_with_disable_escape_false_with_pretty 146 | source = %q{ 147 | = "

Hello

" 148 | == "

World

" 149 | } 150 | 151 | assert_html "<p>Hello</p>

World

", source, pretty: true 152 | end 153 | 154 | def test_render_with_disable_escape_true_with_pretty 155 | source = %q{ 156 | = "

Hello

" 157 | == "

World

" 158 | } 159 | 160 | assert_html "

Hello

World

", source, disable_escape: true, pretty: true 161 | end 162 | end 163 | -------------------------------------------------------------------------------- /test/core/test_code_evaluation.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSlimCodeEvaluation < TestSlim 4 | def test_render_with_call_to_set_attributes 5 | source = %q{ 6 | p id="#{id_helper}" class="hello world" = hello_world 7 | } 8 | 9 | assert_html '

Hello World from @env

', source 10 | end 11 | 12 | def test_render_with_call_to_set_custom_attributes 13 | source = %q{ 14 | p data-id="#{id_helper}" data-class="hello world" 15 | = hello_world 16 | } 17 | 18 | assert_html '

Hello World from @env

', source 19 | end 20 | 21 | def test_render_with_call_to_set_attributes_and_call_to_set_content 22 | source = %q{ 23 | p id="#{id_helper}" class="hello world" = hello_world 24 | } 25 | 26 | assert_html '

Hello World from @env

', source 27 | end 28 | 29 | def test_render_with_parameterized_call_to_set_attributes_and_call_to_set_content 30 | source = %q{ 31 | p id="#{id_helper}" class="hello world" = hello_world("Hello Ruby!") 32 | } 33 | 34 | assert_html '

Hello Ruby!

', source 35 | end 36 | 37 | def test_render_with_spaced_parameterized_call_to_set_attributes_and_call_to_set_content 38 | source = %q{ 39 | p id="#{id_helper}" class="hello world" = hello_world "Hello Ruby!" 40 | } 41 | 42 | assert_html '

Hello Ruby!

', source 43 | end 44 | 45 | def test_render_with_spaced_parameterized_call_to_set_attributes_and_call_to_set_content_2 46 | source = %q{ 47 | p id="#{id_helper}" class="hello world" = hello_world "Hello Ruby!", dummy: "value" 48 | } 49 | 50 | assert_html '

Hello Ruby!dummy value

', source 51 | end 52 | 53 | def test_hash_call_in_attribute 54 | source = %q{ 55 | p id="#{hash[:a]}" Test it 56 | } 57 | 58 | assert_html '

Test it

', source 59 | end 60 | 61 | def test_instance_variable_in_attribute_without_quotes 62 | source = %q{ 63 | p id=@var 64 | } 65 | 66 | assert_html '

', source 67 | end 68 | 69 | def test_method_call_in_attribute_without_quotes 70 | source = %q{ 71 | form action=action_path(:page, :save) method='post' 72 | } 73 | 74 | assert_html '
', source 75 | end 76 | 77 | def test_ruby_attribute_with_unbalanced_delimiters 78 | source = %q{ 79 | div crazy=action_path('[') id="crazy_delimiters" 80 | } 81 | 82 | assert_html '
', source 83 | end 84 | 85 | def test_method_call_in_delimited_attribute_without_quotes 86 | source = %q{ 87 | form(action=action_path(:page, :save) method='post') 88 | } 89 | 90 | assert_html '
', source 91 | end 92 | 93 | def test_method_call_in_delimited_attribute_without_quotes2 94 | source = %q{ 95 | form(method='post' action=action_path(:page, :save)) 96 | } 97 | 98 | assert_html '
', source 99 | end 100 | 101 | def test_hash_call_in_attribute_without_quotes 102 | source = %q{ 103 | p id=hash[:a] Test it 104 | } 105 | 106 | assert_html '

Test it

', source 107 | end 108 | 109 | def test_hash_call_in_delimited_attribute 110 | source = %q{ 111 | p(id=hash[:a]) Test it 112 | } 113 | 114 | assert_html '

Test it

', source 115 | end 116 | 117 | def test_hash_call_in_attribute_with_ruby_evaluation 118 | source = %q{ 119 | p id=(hash[:a] + hash[:a]) Test it 120 | } 121 | 122 | assert_html '

Test it

', source 123 | end 124 | 125 | def test_hash_call_in_delimited_attribute_with_ruby_evaluation 126 | source = %q{ 127 | p(id=(hash[:a] + hash[:a])) Test it 128 | } 129 | 130 | assert_html '

Test it

', source 131 | end 132 | 133 | def test_hash_call_in_delimited_attribute_with_ruby_evaluation_2 134 | source = %q{ 135 | p[id=(hash[:a] + hash[:a])] Test it 136 | } 137 | 138 | assert_html '

Test it

', source 139 | end 140 | 141 | def test_hash_call_in_delimited_attribute_with_ruby_evaluation_3 142 | source = %q{ 143 | p(id=(hash[:a] + hash[:a]) class=hash[:a]) Test it 144 | } 145 | 146 | assert_html '

Test it

', source 147 | end 148 | 149 | def test_hash_call_in_delimited_attribute_with_ruby_evaluation_4_ 150 | source = %q{ 151 | p(id=hash[:a] class=hash[:a]) Test it 152 | } 153 | 154 | assert_html '

Test it

', source 155 | end 156 | 157 | def test_computation_in_attribute 158 | source = %q{ 159 | p id=(1 + 1)*5 Test it 160 | } 161 | 162 | assert_html '

Test it

', source 163 | end 164 | 165 | def test_code_attribute_does_not_modify_argument 166 | require 'ostruct' 167 | template = 'span class=attribute' 168 | model = OpenStruct.new(attribute: [:a, :b, [:c, :d]]) 169 | output = Slim::Template.new { template }.render(model) 170 | assert_equal('', output) 171 | assert_equal([:a, :b, [:c, :d]], model.attribute) 172 | end 173 | 174 | def test_number_type_interpolation 175 | source = %q{ 176 | p = output_number 177 | } 178 | 179 | assert_html '

1337

', source 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /test/core/test_code_output.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSlimCodeOutput < TestSlim 4 | def test_render_with_call 5 | source = %q{ 6 | p 7 | = hello_world 8 | } 9 | 10 | assert_html '

Hello World from @env

', source 11 | end 12 | 13 | def test_render_with_trailing_whitespace 14 | source = %q{ 15 | p 16 | => hello_world 17 | } 18 | 19 | assert_html '

Hello World from @env

', source 20 | end 21 | 22 | def test_render_with_trailing_whitespace_after_tag 23 | source = %q{ 24 | p=> hello_world 25 | } 26 | 27 | assert_html '

Hello World from @env

', source 28 | end 29 | 30 | def test_no_escape_render_with_trailing_whitespace 31 | source = %q{ 32 | p 33 | ==> hello_world 34 | } 35 | 36 | assert_html '

Hello World from @env

', source 37 | end 38 | 39 | def test_no_escape_render_with_trailing_whitespace_after_tag 40 | source = %q{ 41 | p==> hello_world 42 | } 43 | 44 | assert_html '

Hello World from @env

', source 45 | end 46 | 47 | def test_render_with_conditional_call 48 | source = %q{ 49 | p 50 | = hello_world if true 51 | } 52 | 53 | assert_html '

Hello World from @env

', source 54 | end 55 | 56 | def test_render_with_parameterized_call 57 | source = %q{ 58 | p 59 | = hello_world("Hello Ruby!") 60 | } 61 | 62 | assert_html '

Hello Ruby!

', source 63 | end 64 | 65 | def test_render_with_spaced_parameterized_call 66 | source = %q{ 67 | p 68 | = hello_world "Hello Ruby!" 69 | } 70 | 71 | assert_html '

Hello Ruby!

', source 72 | end 73 | 74 | def test_render_with_spaced_parameterized_call_2 75 | source = %q{ 76 | p 77 | = hello_world "Hello Ruby!", dummy: "value" 78 | } 79 | 80 | assert_html '

Hello Ruby!dummy value

', source 81 | end 82 | 83 | def test_render_with_call_and_inline_text 84 | source = %q{ 85 | h1 This is my title 86 | p 87 | = hello_world 88 | } 89 | 90 | assert_html '

This is my title

Hello World from @env

', source 91 | end 92 | 93 | def test_render_with_attribute_starts_with_keyword 94 | source = %q{ 95 | p = hello_world in_keyword 96 | } 97 | 98 | assert_html '

starts with keyword

', source 99 | end 100 | 101 | def test_hash_call 102 | source = %q{ 103 | p = hash[:a] 104 | } 105 | 106 | assert_html '

The letter a

', source 107 | end 108 | 109 | def test_tag_output_without_space 110 | source = %q{ 111 | p= hello_world 112 | p=hello_world 113 | } 114 | 115 | assert_html '

Hello World from @env

Hello World from @env

', source 116 | end 117 | 118 | def test_class_output_without_space 119 | source = %q{ 120 | .test=hello_world 121 | #test==hello_world 122 | } 123 | 124 | assert_html '
Hello World from @env
Hello World from @env
', source 125 | end 126 | 127 | def test_attribute_output_without_space 128 | source = %q{ 129 | p id="test"=hello_world 130 | p(id="test")==hello_world 131 | } 132 | 133 | assert_html '

Hello World from @env

Hello World from @env

', source 134 | end 135 | 136 | def test_render_with_backslash_end 137 | # Keep trailing spaces! 138 | source = %q{ 139 | p = \ 140 | "Hello" + \ 141 | " Ruby!" 142 | - variable = 1 + \ 143 | 2 + \ 144 | 3 145 | = variable + \ 146 | 1 147 | } 148 | 149 | assert_html '

Hello Ruby!

7', source 150 | end 151 | 152 | def test_render_with_comma_end 153 | source = %q{ 154 | p = message("Hello", 155 | "Ruby!") 156 | } 157 | 158 | assert_html '

Hello Ruby!

', source 159 | end 160 | 161 | def test_render_with_no_trailing_character 162 | source = %q{ 163 | p 164 | = hello_world} 165 | 166 | assert_html '

Hello World from @env

', source 167 | end 168 | end 169 | -------------------------------------------------------------------------------- /test/core/test_code_structure.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSlimCodeStructure < TestSlim 4 | def test_render_with_conditional 5 | source = %q{ 6 | div 7 | - if show_first? 8 | p The first paragraph 9 | - else 10 | p The second paragraph 11 | } 12 | 13 | assert_html '

The second paragraph

', source 14 | end 15 | 16 | def test_render_with_begin 17 | source = %q{ 18 | - if true 19 | - begin 20 | p A 21 | - if true 22 | - begin 23 | p B 24 | - if true 25 | - begin 26 | p C 27 | - rescue 28 | p D 29 | } 30 | 31 | assert_html '

A

B

C

', source 32 | end 33 | 34 | def test_render_with_consecutive_conditionals 35 | source = %q{ 36 | div 37 | - if show_first? true 38 | p The first paragraph 39 | - if show_first? true 40 | p The second paragraph 41 | } 42 | 43 | assert_html '

The first paragraph

The second paragraph

', source 44 | end 45 | 46 | def test_render_with_parameterized_conditional 47 | source = %q{ 48 | div 49 | - if show_first? false 50 | p The first paragraph 51 | - else 52 | p The second paragraph 53 | } 54 | 55 | assert_html '

The second paragraph

', source 56 | end 57 | 58 | def test_render_with_when_string_in_condition 59 | source = %q{ 60 | - if true 61 | | Hello 62 | 63 | - unless 'when' == nil 64 | | world 65 | } 66 | 67 | assert_html 'Hello world', source 68 | end 69 | 70 | def test_render_with_conditional_and_following_nonconditonal 71 | source = %q{ 72 | div 73 | - if true 74 | p The first paragraph 75 | - var = 42 76 | = var 77 | } 78 | 79 | assert_html '

The first paragraph

42
', source 80 | end 81 | 82 | def test_render_with_inline_condition 83 | source = %q{ 84 | p = hello_world if true 85 | } 86 | 87 | assert_html '

Hello World from @env

', source 88 | end 89 | 90 | def test_render_with_case 91 | source = %q{ 92 | p 93 | - case 42 94 | - when 41 95 | | 41 96 | - when 42 97 | | 42 98 | | is the answer 99 | p 100 | - case 41 101 | - when 41 102 | | 41 103 | - when 42 104 | | 42 105 | | is the answer 106 | p 107 | - case 42 when 41 108 | | 41 109 | - when 42 110 | | 42 111 | | is the answer 112 | p 113 | - case 41 when 41 114 | | 41 115 | - when 42 116 | | 42 117 | | is the answer 118 | } 119 | 120 | assert_html '

42 is the answer

41 is the answer

42 is the answer

41 is the answer

', source 121 | end 122 | 123 | if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7") 124 | def test_render_with_case_in 125 | source = %q{ 126 | p 127 | - case [:greet, "world"] 128 | - in :greet, value if false 129 | = "Goodbye #{value}" 130 | - in :greet, value unless true 131 | = "Top of the morning to you, #{value}" 132 | - in :greet, value 133 | = "Hello #{value}" 134 | } 135 | 136 | assert_html '

Hello world

', source 137 | end 138 | end 139 | 140 | def test_render_with_slim_comments 141 | source = %q{ 142 | p Hello 143 | / This is a comment 144 | Another comment 145 | p World 146 | } 147 | 148 | assert_html '

Hello

World

', source 149 | end 150 | 151 | def test_render_with_yield 152 | source = %q{ 153 | div 154 | == yield :menu 155 | } 156 | 157 | assert_html '
This is the menu
', source do 158 | 'This is the menu' 159 | end 160 | end 161 | 162 | def test_render_with_begin_rescue 163 | source = %q{ 164 | - begin 165 | p Begin 166 | - rescue 167 | p Rescue 168 | p After 169 | } 170 | 171 | assert_html '

Begin

After

', source 172 | end 173 | 174 | def test_render_with_begin_rescue_exception 175 | source = %q{ 176 | - begin 177 | p Begin 178 | - raise 'Boom' 179 | p After Boom 180 | - rescue => ex 181 | p = ex.message 182 | p After 183 | } 184 | 185 | assert_html '

Begin

Boom

After

', source 186 | end 187 | 188 | def test_render_with_begin_rescue_ensure 189 | source = %q{ 190 | - begin 191 | p Begin 192 | - raise 'Boom' 193 | p After Boom 194 | - rescue => ex 195 | p = ex.message 196 | - ensure 197 | p Ensure 198 | p After 199 | } 200 | 201 | assert_html '

Begin

Boom

Ensure

After

', source 202 | end 203 | end 204 | -------------------------------------------------------------------------------- /test/core/test_commands.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | require 'open3' 3 | require 'tempfile' 4 | 5 | class TestSlimCommands < Minitest::Test 6 | # nothing complex 7 | STATIC_TEMPLATE = "p Hello World!\n" 8 | 9 | # requires a `name` variable to exist at render time 10 | DYNAMIC_TEMPLATE = "p Hello \#{name}!\n" 11 | 12 | # a more complex example 13 | LONG_TEMPLATE = "h1 Hello\np\n | World!\n small Tiny text" 14 | 15 | # exception raising example 16 | EXCEPTION_TEMPLATE = '- raise NotImplementedError' 17 | 18 | def test_option_help 19 | out, err = exec_slimrb '--help' 20 | 21 | assert err.empty? 22 | assert_match %r{Show this message}, out 23 | end 24 | 25 | def test_option_version 26 | out, err = exec_slimrb '--version' 27 | 28 | assert err.empty? 29 | assert_match %r{\ASlim #{Regexp.escape Slim::VERSION}$}, out 30 | end 31 | 32 | def test_render 33 | prepare_common_test STATIC_TEMPLATE do |out, err| 34 | assert err.empty? 35 | assert_equal "

Hello World!

\n", out 36 | end 37 | end 38 | 39 | # superficial test, we don't want to test Tilt/Temple 40 | def test_compile 41 | prepare_common_test STATIC_TEMPLATE, '--compile' do |out, err| 42 | assert err.empty? 43 | assert_match %r{\"

Hello World!<\/p>\".freeze}, out 44 | end 45 | end 46 | 47 | def test_erb 48 | prepare_common_test DYNAMIC_TEMPLATE, '--erb' do |out, err| 49 | assert err.empty? 50 | assert_equal "

Hello <%= ::Temple::Utils.escape_html((name)) %>!

\n", out 51 | end 52 | end 53 | 54 | def test_rails 55 | prepare_common_test DYNAMIC_TEMPLATE, '--rails' do |out, err| 56 | assert err.empty? 57 | 58 | if Gem::Version.new(Temple::VERSION) >= Gem::Version.new('0.9') 59 | assert out.include? %Q{@output_buffer = output_buffer || ActionView::OutputBuffer.new;} 60 | else 61 | assert out.include? %Q{@output_buffer = ActiveSupport::SafeBuffer.new;} 62 | end 63 | assert out.include? %Q{@output_buffer.safe_concat(("

Hello ".freeze));} 64 | assert out.include? %Q{@output_buffer.safe_concat(((::Temple::Utils.escape_html((name))).to_s));} 65 | assert out.include? %Q{@output_buffer.safe_concat(("!

".freeze));} 66 | end 67 | end 68 | 69 | def test_pretty 70 | prepare_common_test LONG_TEMPLATE, '--pretty' do |out, err| 71 | assert err.empty? 72 | assert_equal "

\n Hello\n

\n

\n World!Tiny text\n

\n", out 73 | end 74 | end 75 | 76 | def test_locals_json 77 | data = '{"name":"from slim"}' 78 | prepare_common_test DYNAMIC_TEMPLATE, '--locals', data do |out, err| 79 | assert err.empty? 80 | assert_equal "

Hello from slim!

\n", out 81 | end 82 | end 83 | 84 | def test_locals_yaml 85 | data = "name: from slim" 86 | prepare_common_test DYNAMIC_TEMPLATE, '--locals', data do |out, err| 87 | assert err.empty? 88 | assert_equal "

Hello from slim!

\n", out 89 | end 90 | end 91 | 92 | def test_locals_hash 93 | data = '{name:"from slim"}' 94 | prepare_common_test DYNAMIC_TEMPLATE, '--locals', data do |out, err| 95 | assert err.empty? 96 | assert_equal "

Hello from slim!

\n", out 97 | end 98 | end 99 | 100 | def test_require 101 | with_tempfile 'puts "Not in slim"', 'rb' do |lib| 102 | prepare_common_test STATIC_TEMPLATE, '--require', lib, stdin_file: false, file_file: false do |out, err| 103 | assert err.empty? 104 | assert_equal "Not in slim\n

Hello World!

\n", out 105 | end 106 | end 107 | end 108 | 109 | def test_error 110 | prepare_common_test EXCEPTION_TEMPLATE, stdin_file: false do |out, err| 111 | assert out.empty? 112 | assert_match %r{NotImplementedError: NotImplementedError}, err 113 | assert_match %r{Use --trace for backtrace}, err 114 | end 115 | end 116 | 117 | def test_trace_error 118 | prepare_common_test EXCEPTION_TEMPLATE, '--trace', stdin_file: false do |out, err| 119 | assert out.empty? 120 | assert_match %r{bin\/slimrb}, err 121 | end 122 | end 123 | 124 | private 125 | 126 | # Whether you call slimrb with a file argument or pass the slim content 127 | # via $stdin; whether you want the output written to $stdout or into 128 | # another file given as argument, the output is the same. 129 | # 130 | # This method prepares a test with this exact behaviour: 131 | # 132 | # It yields the tupel (out, err) once after the `content` was passed 133 | # in via $stdin and once it was passed as a (temporary) file argument. 134 | # 135 | # In effect, this method executes a test (given as block) 4 times: 136 | # 137 | # 1. read from $stdin, write to $stdout 138 | # 2. read from file, write to $stdout 139 | # 3. read from $stdin, write to file 140 | # 4. read from file, write to file 141 | def prepare_common_test(content, *args) 142 | options = Hash === args.last ? args.pop : {} 143 | 144 | # case 1. $stdin → $stdout 145 | unless options[:stdin_stdout] == false 146 | out, err = exec_slimrb(*args, '--stdin') do |i| 147 | i.write content 148 | end 149 | yield out, err 150 | end 151 | 152 | # case 2. file → $stdout 153 | unless options[:file_stdout] == false 154 | with_tempfile content do |in_file| 155 | out, err = exec_slimrb(*args, in_file) 156 | yield out, err 157 | end 158 | end 159 | 160 | # case 3. $stdin → file 161 | unless options[:stdin_file] == false 162 | with_tempfile content do |out_file| 163 | _, err = exec_slimrb(*args, '--stdin', out_file) do |i| 164 | i.write content 165 | end 166 | yield File.read(out_file), err 167 | end 168 | end 169 | 170 | # case 3. file → file 171 | unless options[:file_file] == false 172 | with_tempfile '' do |out_file| 173 | with_tempfile content do |in_file| 174 | _, err = exec_slimrb(*args, in_file, out_file) do |i| 175 | i.write content 176 | end 177 | yield File.read(out_file), err 178 | end 179 | end 180 | end 181 | end 182 | 183 | # Calls bin/slimrb as a subprocess. 184 | # 185 | # Yields $stdin to the caller and returns a tupel (out,err) with the 186 | # contents of $stdout and $stderr. 187 | # 188 | # (I'd like to use Minitest::Assertions#capture_subprecess_io here, 189 | # but then there's no way to insert data via $stdin.) 190 | def exec_slimrb(*args) 191 | out, err = nil, nil 192 | 193 | Open3.popen3 'ruby', 'bin/slimrb', *args do |i,o,e,t| 194 | yield i if block_given? 195 | i.close 196 | out, err = o.read, e.read 197 | end 198 | 199 | return out, err 200 | end 201 | 202 | # Creates a temporary file with the given content and yield the path 203 | # to this file. The file itself is only available inside the block and 204 | # will be deleted afterwards. 205 | def with_tempfile(content=nil, extname='slim') 206 | f = Tempfile.new ['slim', ".#{extname}"] 207 | if content 208 | f.write content 209 | f.flush # ensure content is actually saved to disk 210 | f.rewind 211 | end 212 | 213 | yield f.path 214 | ensure 215 | f.close 216 | f.unlink 217 | end 218 | 219 | end 220 | -------------------------------------------------------------------------------- /test/core/test_embedded_engines.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | require 'erb' 3 | 4 | class TestSlimEmbeddedEngines < TestSlim 5 | 6 | def test_render_with_markdown 7 | source = %q{ 8 | markdown: 9 | #Header 10 | Hello from #{"Markdown!"} 11 | 12 | #{1+2} 13 | 14 | [#{1}](#{"#2"}) 15 | 16 | * one 17 | * two 18 | } 19 | if ::Tilt['md'].name =~ /Redcarpet/ 20 | # redcarpet 21 | assert_html "

Header

\n\n

Hello from Markdown!

\n\n

3

\n\n

1

\n\n\n", source 22 | elsif ::Tilt['md'].name =~ /RDiscount/ 23 | # rdiscount 24 | assert_html "

Header

\n\n

Hello from Markdown!

\n\n

3

\n\n

1

\n\n\n\n", source 25 | else 26 | # kramdown, :auto_ids by default 27 | assert_html "

Header

\n

Hello from Markdown!

\n\n

3

\n\n

1

\n\n\n", source 28 | 29 | Slim::Embedded.with_options(markdown: {auto_ids: false}) do 30 | assert_html "

Header

\n

Hello from Markdown!

\n\n

3

\n\n

1

\n\n\n", source 31 | end 32 | 33 | assert_html "

Header

\n

Hello from Markdown!

\n\n

3

\n\n

1

\n\n\n", source 34 | end 35 | end 36 | 37 | def test_render_with_css 38 | source = %q{ 39 | css: 40 | h1 { color: blue } 41 | } 42 | assert_html "", source 43 | end 44 | 45 | def test_render_with_css_empty_attributes 46 | source = %q{ 47 | css []: 48 | h1 { color: blue } 49 | } 50 | assert_html "", source 51 | end 52 | 53 | def test_render_with_css_attribute 54 | source = %q{ 55 | css scoped = "true": 56 | h1 { color: blue } 57 | } 58 | assert_html "", source 59 | end 60 | 61 | def test_render_with_css_multiple_attributes 62 | source = %q{ 63 | css class="myClass" scoped = "true" : 64 | h1 { color: blue } 65 | } 66 | assert_html "", source 67 | end 68 | 69 | def test_render_with_javascript 70 | source = %q{ 71 | javascript: 72 | $(function() {}); 73 | 74 | 75 | alert('hello') 76 | p Hi 77 | } 78 | assert_html %{

Hi

}, source 79 | end 80 | 81 | def test_render_with_javascript_empty_attributes 82 | source = %q{ 83 | javascript (): 84 | alert('hello') 85 | } 86 | assert_html %{}, source 87 | end 88 | 89 | def test_render_with_javascript_attribute 90 | source = %q{ 91 | javascript [class = "myClass"]: 92 | alert('hello') 93 | } 94 | assert_html %{}, source 95 | end 96 | 97 | def test_render_with_javascript_multiple_attributes 98 | source = %q{ 99 | javascript { class = "myClass" id="myId" other-attribute = 'my_other_attribute' } : 100 | alert('hello') 101 | } 102 | assert_html %{}, source 103 | end 104 | 105 | def test_render_with_javascript_with_tabs 106 | source = "javascript:\n\t$(function() {});\n\talert('hello')\np Hi" 107 | assert_html "

Hi

", source 108 | end 109 | 110 | def test_render_with_javascript_including_variable 111 | source = %q{ 112 | - func = "alert('hello');" 113 | javascript: 114 | $(function() { #{func} }); 115 | } 116 | assert_html %q||, source 117 | end 118 | 119 | def test_render_with_javascript_with_explicit_html_comment 120 | Slim::Engine.with_options(js_wrapper: :comment) do 121 | source = "javascript:\n\t$(function() {});\n\talert('hello')\np Hi" 122 | assert_html "

Hi

", source 123 | end 124 | end 125 | 126 | def test_render_with_javascript_with_explicit_cdata_comment 127 | Slim::Engine.with_options(js_wrapper: :cdata) do 128 | source = "javascript:\n\t$(function() {});\n\talert('hello')\np Hi" 129 | assert_html "

Hi

", source 130 | end 131 | end 132 | 133 | def test_render_with_javascript_with_format_xhtml_comment 134 | Slim::Engine.with_options(js_wrapper: :guess, format: :xhtml) do 135 | source = "javascript:\n\t$(function() {});\n\talert('hello')\np Hi" 136 | assert_html "

Hi

", source 137 | end 138 | end 139 | 140 | def test_render_with_javascript_with_format_html_comment 141 | Slim::Engine.with_options(js_wrapper: :guess, format: :html) do 142 | source = "javascript:\n\t$(function() {});\n\talert('hello')\np Hi" 143 | assert_html "

Hi

", source 144 | end 145 | end 146 | 147 | def test_render_with_ruby 148 | source = %q{ 149 | ruby: 150 | variable = 1 + 151 | 2 152 | = variable 153 | } 154 | assert_html '3', source 155 | end 156 | 157 | def test_render_with_ruby_heredoc 158 | source = %q{ 159 | ruby: 160 | variable = <<-MSG 161 | foobar 162 | MSG 163 | = variable 164 | } 165 | assert_html "foobar\n", source 166 | end 167 | 168 | # TODO: Reactivate sass tests 169 | if false 170 | def test_render_with_scss 171 | source = %q{ 172 | scss: 173 | $color: #f00; 174 | body { color: $color; } 175 | } 176 | assert_html "", source 177 | end 178 | 179 | def test_render_with_scss_attribute 180 | source = %q{ 181 | scss [class="myClass"]: 182 | $color: #f00; 183 | body { color: $color; } 184 | } 185 | assert_html "", source 186 | end 187 | 188 | def test_render_with_sass 189 | source = %q{ 190 | sass: 191 | $color: #f00 192 | body 193 | color: $color 194 | } 195 | assert_html "", source 196 | end 197 | 198 | def test_render_with_sass_attribute 199 | source = %q{ 200 | sass [class="myClass"]: 201 | $color: #f00 202 | body 203 | color: $color 204 | } 205 | assert_html "", source 206 | end 207 | end 208 | 209 | def test_disabled_embedded_engine 210 | source = %{ 211 | ruby: 212 | Embedded Ruby 213 | } 214 | assert_runtime_error 'Embedded engine ruby is disabled', source, enable_engines: [:javascript] 215 | assert_runtime_error 'Embedded engine ruby is disabled', source, enable_engines: %w(javascript) 216 | 217 | source = %{ 218 | ruby: 219 | Embedded Ruby 220 | } 221 | assert_runtime_error 'Embedded engine ruby is disabled', source, enable_engines: [:javascript] 222 | assert_runtime_error 'Embedded engine ruby is disabled', source, enable_engines: %w(javascript) 223 | 224 | source = %{ 225 | ruby: 226 | Embedded Ruby 227 | } 228 | assert_runtime_error 'Embedded engine ruby is disabled', source, disable_engines: [:ruby] 229 | assert_runtime_error 'Embedded engine ruby is disabled', source, disable_engines: %w(ruby) 230 | end 231 | 232 | def test_enabled_embedded_engine 233 | source = %q{ 234 | javascript: 235 | $(function() {}); 236 | } 237 | assert_html '', source, disable_engines: [:ruby] 238 | assert_html '', source, disable_engines: %w(ruby) 239 | 240 | source = %q{ 241 | javascript: 242 | $(function() {}); 243 | } 244 | assert_html '', source, enable_engines: [:javascript] 245 | assert_html '', source, enable_engines: %w(javascript) 246 | end 247 | end 248 | -------------------------------------------------------------------------------- /test/core/test_encoding.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSlimEncoding < TestSlim 4 | def test_windows_crlf 5 | source = "a href='#' something\r\nbr\r\na href='#' others\r\n" 6 | result = "something
others" 7 | assert_html result, source 8 | end 9 | 10 | def test_binary 11 | source = "| \xFF\xFF".dup 12 | source.force_encoding(Encoding::BINARY) 13 | 14 | result = "\xFF\xFF".dup 15 | result.force_encoding(Encoding::BINARY) 16 | 17 | out = render(source, default_encoding: 'binary') 18 | out.force_encoding(Encoding::BINARY) 19 | 20 | assert_equal result, out 21 | end 22 | 23 | def test_bom 24 | source = "\xEF\xBB\xBFh1 Hello World!" 25 | result = '

Hello World!

' 26 | assert_html result, source 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/core/test_erb_converter.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | require 'slim/erb_converter' 3 | 4 | class TestSlimERBConverter < TestSlim 5 | def test_converter 6 | source = %q{ 7 | doctype 5 8 | html 9 | head 10 | title Hello World! 11 | /! Meta tags 12 | with long explanatory 13 | multiline comment 14 | meta name="description" content="template language" 15 | /! Stylesheets 16 | link href="style.css" media="screen" rel="stylesheet" type="text/css" 17 | link href="colors.css" media="screen" rel="stylesheet" type="text/css" 18 | /! Javascripts 19 | script src="jquery.js" 20 | script src="jquery.ui.js" 21 | /[if lt IE 9] 22 | script src="old-ie1.js" 23 | script src="old-ie2.js" 24 | css: 25 | body { background-color: red; } 26 | body 27 | #container 28 | p Hello 29 | World! 30 | p= "dynamic text with\nnewline" 31 | } 32 | 33 | result = %q{ 34 | 35 | 36 | 37 | Hello World! 38 | 43 | 44 | 45 | 46 | 47 | 48 | 55 | 56 |
57 |

Hello 58 | 59 | World!

60 |

<%= ::Temple::Utils.escape_html(("dynamic text with\nnewline")) %> 61 |

} 62 | 63 | assert_equal result, Slim::ERBConverter.new.call(source) 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /test/core/test_html_attributes.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSlimHTMLAttributes < TestSlim 4 | def test_ternary_operation_in_attribute 5 | source = %q{ 6 | p id="#{(false ? 'notshown' : 'shown')}" = output_number 7 | } 8 | 9 | assert_html '

1337

', source 10 | end 11 | 12 | def test_ternary_operation_in_attribute_2 13 | source = %q{ 14 | p id=(false ? 'notshown' : 'shown') = output_number 15 | } 16 | 17 | assert_html '

1337

', source 18 | end 19 | 20 | def test_class_attribute_merging 21 | source = %{ 22 | .alpha class="beta" Test it 23 | } 24 | assert_html '
Test it
', source 25 | end 26 | 27 | def test_class_attribute_merging_with_nil 28 | source = %{ 29 | .alpha class="beta" class=nil class="gamma" Test it 30 | } 31 | assert_html '
Test it
', source 32 | end 33 | 34 | def test_class_attribute_merging_with_empty_static 35 | source = %{ 36 | .alpha class="beta" class="" class="gamma" Test it 37 | } 38 | assert_html '
Test it
', source 39 | end 40 | 41 | def test_id_attribute_merging 42 | source = %{ 43 | #alpha id="beta" Test it 44 | } 45 | assert_html '
Test it
', source, merge_attrs: {'class' => ' ', 'id' => '_' } 46 | end 47 | 48 | def test_id_attribute_merging2 49 | source = %{ 50 | #alpha id="beta" Test it 51 | } 52 | assert_html '
Test it
', source, merge_attrs: {'class' => ' ', 'id' => '-' } 53 | end 54 | 55 | def test_boolean_attribute_false 56 | source = %{ 57 | - cond=false 58 | option selected=false Text 59 | option selected=cond Text2 60 | } 61 | 62 | assert_html '', source 63 | end 64 | 65 | def test_boolean_attribute_true 66 | source = %{ 67 | - cond=true 68 | option selected=true Text 69 | option selected=cond Text2 70 | } 71 | 72 | assert_html '', source 73 | end 74 | 75 | def test_boolean_attribute_nil 76 | source = %{ 77 | - cond=nil 78 | option selected=nil Text 79 | option selected=cond Text2 80 | } 81 | 82 | assert_html '', source 83 | end 84 | 85 | def test_boolean_attribute_string2 86 | source = %{ 87 | option selected="selected" Text 88 | } 89 | 90 | assert_html '', source 91 | end 92 | 93 | def test_boolean_attribute_shortcut 94 | source = %{ 95 | option(class="clazz" selected) Text 96 | option(selected class="clazz") Text 97 | } 98 | 99 | assert_html '', source 100 | end 101 | 102 | def test_array_attribute_merging 103 | source = %{ 104 | .alpha class="beta" class=[[""], :gamma, nil, :delta, [true, false]] 105 | .alpha class=:beta,:gamma 106 | } 107 | 108 | assert_html '
', source 109 | end 110 | 111 | def test_hyphenated_attribute 112 | source = %{ 113 | .alpha data={a: 'alpha', b: 'beta', c_d: 'gamma', c: {e: 'epsilon'}} 114 | } 115 | 116 | assert_html '
', source 117 | end 118 | 119 | def test_hyphenated_underscore_attribute 120 | source = %{ 121 | .alpha data={a: 'alpha', b: 'beta', c_d: 'gamma', c: {e: 'epsilon'}} 122 | } 123 | 124 | assert_html '
', source, hyphen_underscore_attrs: true 125 | end 126 | 127 | def test_splat_without_content 128 | source = %q{ 129 | *hash 130 | p*hash 131 | } 132 | 133 | assert_html '

', source 134 | end 135 | 136 | def test_shortcut_splat 137 | source = %q{ 138 | *hash This is my title 139 | } 140 | 141 | assert_html '
This is my title
', source 142 | end 143 | 144 | def test_splat 145 | source = %q{ 146 | h1 *hash class=[] This is my title 147 | } 148 | 149 | assert_html '

This is my title

', source 150 | end 151 | 152 | def test_closed_splat 153 | source = %q{ 154 | *hash / 155 | } 156 | 157 | assert_html '
', source 158 | end 159 | 160 | def test_splat_tag_name 161 | source = %q{ 162 | *{tag: 'h1', id: 'title'} This is my title 163 | } 164 | 165 | assert_html '

This is my title

', source 166 | end 167 | 168 | 169 | def test_splat_empty_tag_name 170 | source = %q{ 171 | *{tag: '', id: 'test'} This is my title 172 | } 173 | 174 | assert_html '
This is my title
', source 175 | end 176 | 177 | def test_closed_splat_tag 178 | source = %q{ 179 | *hash / 180 | } 181 | 182 | assert_html '
', source 183 | end 184 | 185 | def test_splat_with_id_shortcut 186 | source = %q{ 187 | #myid*hash This is my title 188 | } 189 | 190 | assert_html '
This is my title
', source 191 | end 192 | 193 | def test_splat_with_class_shortcut 194 | source = %q{ 195 | .myclass*hash This is my title 196 | } 197 | 198 | assert_html '
This is my title
', source 199 | end 200 | 201 | def test_splat_with_id_and_class_shortcuts 202 | source = %q{ 203 | #myid.myclass*hash This is my title 204 | } 205 | 206 | assert_html '
This is my title
', source 207 | end 208 | 209 | def test_splat_with_class_merging 210 | source = %q{ 211 | #myid.myclass *{class: [:secondclass, %w(x y z)]} *hash This is my title 212 | } 213 | 214 | assert_html '
This is my title
', source 215 | end 216 | 217 | def test_splat_with_boolean_attribute 218 | source = %q{ 219 | *{disabled: true, empty1: false, nonempty: '', empty2: nil} This is my title 220 | } 221 | 222 | assert_html '
This is my title
', source 223 | end 224 | 225 | def test_splat_merging_with_arrays 226 | source = %q{ 227 | *{a: 1, b: 2} *[[:c, 3], [:d, 4]] *[[:e, 5], [:f, 6]] This is my title 228 | } 229 | 230 | assert_html '
This is my title
', source 231 | end 232 | 233 | def test_splat_with_other_attributes 234 | source = %q{ 235 | h1 data-id="123" *hash This is my title 236 | } 237 | 238 | assert_html '

This is my title

', source 239 | end 240 | 241 | def test_attribute_merging 242 | source = %q{ 243 | a class=true class=false 244 | a class=false *{class:true} 245 | a class=true 246 | a class=false 247 | } 248 | 249 | assert_html '', source 250 | end 251 | 252 | def test_static_empty_attribute 253 | source = %q{ 254 | p(id="marvin" name="" class="" data-info="Illudium Q-36")= output_number 255 | } 256 | 257 | assert_html '

1337

', source 258 | end 259 | 260 | def test_dynamic_empty_attribute 261 | source = %q{ 262 | p(id="marvin" class=nil nonempty=("".to_s) data-info="Illudium Q-36")= output_number 263 | } 264 | 265 | assert_html '

1337

', source 266 | end 267 | 268 | def test_weird_attribute 269 | source = %q{ 270 | p 271 | img(src='img.png' whatsthis?!) 272 | img src='img.png' whatsthis?!="wtf" 273 | } 274 | assert_html '

', source 275 | end 276 | end 277 | -------------------------------------------------------------------------------- /test/core/test_html_escaping.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSlimHtmlEscaping < TestSlim 4 | def test_html_will_not_be_escaped 5 | source = %q{ 6 | p World, meet "Slim". 7 | } 8 | 9 | assert_html '

World, meet "Slim".

', source 10 | end 11 | 12 | def test_html_with_newline_will_not_be_escaped 13 | source = %q{ 14 | p 15 | | 16 | World, 17 | meet "Slim". 18 | } 19 | 20 | assert_html "

World,\n meet \"Slim\".

", source 21 | end 22 | 23 | def test_html_with_escaped_interpolation 24 | source = %q{ 25 | - x = '"' 26 | - content = '' 27 | p class="#{x}" test #{content} 28 | } 29 | 30 | assert_html '

test <x>

', source 31 | end 32 | 33 | def test_html_nested_escaping 34 | source = %q{ 35 | = hello_world do 36 | | escaped & 37 | } 38 | assert_html 'Hello World from @env escaped & Hello World from @env', source 39 | end 40 | 41 | def test_html_quoted_attr_escape 42 | source = %q{ 43 | p id="&" class=="&" 44 | } 45 | 46 | assert_html '

', source 47 | end 48 | 49 | def test_html_quoted_attr_escape_with_interpolation 50 | source = %q{ 51 | p id="&#{'"'}" class=="&#{'"'}" 52 | p id="&#{{'"'}}" class=="&#{{'"'}}" 53 | } 54 | 55 | assert_html '

', source 56 | end 57 | 58 | def test_html_ruby_attr_escape 59 | source = %q{ 60 | p id=('&'.to_s) class==('&'.to_s) 61 | } 62 | 63 | assert_html '

', source 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /test/core/test_parser_errors.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestParserErrors < TestSlim 4 | def test_correct_filename 5 | source = %q{ 6 | doctype 5 7 | div Invalid 8 | } 9 | 10 | assert_syntax_error "Unexpected indentation\n test.slim, Line 3, Column 2\n div Invalid\n ^\n", source, file: 'test.slim' 11 | end 12 | 13 | def test_unexpected_indentation 14 | source = %q{ 15 | doctype 5 16 | div Invalid 17 | } 18 | 19 | assert_syntax_error "Unexpected indentation\n (__TEMPLATE__), Line 3, Column 2\n div Invalid\n ^\n", source 20 | end 21 | 22 | def test_malformed_indentation 23 | source = %q{ 24 | p 25 | div Valid 26 | div Invalid 27 | } 28 | 29 | assert_syntax_error "Malformed indentation\n (__TEMPLATE__), Line 4, Column 1\n div Invalid\n ^\n", source 30 | end 31 | 32 | def test_malformed_indentation2 33 | source = %q{ 34 | div Valid 35 | div Invalid 36 | } 37 | 38 | assert_syntax_error "Malformed indentation\n (__TEMPLATE__), Line 3, Column 1\n div Invalid\n ^\n", source 39 | end 40 | 41 | def test_unknown_line_indicator 42 | source = %q{ 43 | p 44 | div Valid 45 | .valid 46 | #valid 47 | ?invalid 48 | } 49 | 50 | assert_syntax_error "Unknown line indicator\n (__TEMPLATE__), Line 6, Column 2\n ?invalid\n ^\n", source 51 | end 52 | 53 | def test_expected_closing_delimiter 54 | source = %q{ 55 | p 56 | img(src="img.jpg" title={title} 57 | } 58 | 59 | assert_syntax_error "Expected closing delimiter )\n (__TEMPLATE__), Line 3, Column 33\n img(src=\"img.jpg\" title={title}\n ^\n", source 60 | end 61 | 62 | def test_missing_quote_unexpected_end 63 | source = %q{ 64 | p 65 | img(src="img.jpg 66 | } 67 | 68 | assert_syntax_error "Unexpected end of file\n (__TEMPLATE__), Line 3, Column 0\n \n ^\n", source 69 | end 70 | 71 | def test_expected_closing_attribute_delimiter 72 | source = %q{ 73 | p 74 | img src=[hash[1] + hash[2] 75 | } 76 | 77 | assert_syntax_error "Expected closing delimiter ]\n (__TEMPLATE__), Line 3, Column 28\n img src=[hash[1] + hash[2]\n ^\n", source 78 | end 79 | 80 | def test_invalid_empty_attribute 81 | source = %q{ 82 | p 83 | img{src= } 84 | } 85 | 86 | assert_syntax_error "Invalid empty attribute\n (__TEMPLATE__), Line 3, Column 11\n img{src= }\n ^\n", source 87 | end 88 | 89 | def test_invalid_empty_attribute2 90 | source = %q{ 91 | p 92 | img{src=} 93 | } 94 | 95 | assert_syntax_error "Invalid empty attribute\n (__TEMPLATE__), Line 3, Column 10\n img{src=}\n ^\n", source 96 | end 97 | 98 | def test_invalid_empty_attribute3 99 | source = %q{ 100 | p 101 | img src= 102 | } 103 | 104 | assert_syntax_error "Invalid empty attribute\n (__TEMPLATE__), Line 3, Column 10\n img src=\n ^\n", source 105 | end 106 | 107 | def test_missing_tag_in_block_expansion 108 | source = %{ 109 | html: body: 110 | } 111 | 112 | assert_syntax_error "Expected tag\n (__TEMPLATE__), Line 2, Column 11\n html: body:\n ^\n", source 113 | end 114 | 115 | def test_invalid_tag_in_block_expansion 116 | source = %{ 117 | html: body: /comment 118 | } 119 | assert_syntax_error "Expected tag\n (__TEMPLATE__), Line 2, Column 12\n html: body: /comment\n ^\n", source 120 | 121 | source = %{ 122 | html: body:/comment 123 | } 124 | assert_syntax_error "Expected tag\n (__TEMPLATE__), Line 2, Column 11\n html: body:/comment\n ^\n", source 125 | end 126 | 127 | def test_unexpected_text_after_closed 128 | source = %{ 129 | img / text 130 | } 131 | 132 | assert_syntax_error "Unexpected text after closed tag\n (__TEMPLATE__), Line 2, Column 6\n img / text\n ^\n", source 133 | end 134 | 135 | def test_illegal_shortcuts 136 | source = %{ 137 | .#test 138 | } 139 | 140 | assert_syntax_error "Illegal shortcut\n (__TEMPLATE__), Line 2, Column 0\n .#test\n ^\n", source 141 | 142 | source = %{ 143 | div.#test 144 | } 145 | 146 | assert_syntax_error "Illegal shortcut\n (__TEMPLATE__), Line 2, Column 3\n div.#test\n ^\n", source 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /test/core/test_pretty.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSlimPretty < TestSlim 4 | def setup 5 | super 6 | Slim::Engine.set_options pretty: true 7 | end 8 | 9 | def teardown 10 | Slim::Engine.set_options pretty: false 11 | end 12 | 13 | def test_pretty 14 | source = %q{ 15 | doctype 5 16 | html 17 | head 18 | title Hello World! 19 | /! Meta tags 20 | with long explanatory 21 | multiline comment 22 | meta name="description" content="template language" 23 | /! Stylesheets 24 | link href="style.css" media="screen" rel="stylesheet" type="text/css" 25 | link href="colors.css" media="screen" rel="stylesheet" type="text/css" 26 | /! Javascripts 27 | script src="jquery.js" 28 | script src="jquery.ui.js" 29 | /[if lt IE 9] 30 | script src="old-ie1.js" 31 | script src="old-ie2.js" 32 | css: 33 | body { background-color: red; } 34 | body 35 | #container 36 | p Hello 37 | World! 38 | p= "dynamic text with\nnewline" 39 | } 40 | 41 | result = %q{ 42 | 43 | 44 | Hello World! 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 58 | 61 | 62 | 63 |
64 |

65 | Hello 66 | World! 67 |

68 |

69 | dynamic text with 70 | newline 71 |

72 |
73 | 74 | } 75 | 76 | assert_html result, source 77 | end 78 | 79 | def test_partials 80 | body = %q{body 81 | == render content} 82 | 83 | content = %q{div 84 | | content} 85 | 86 | source = %q{html 87 | == render body, scope: self, locals: { content: content }} 88 | 89 | result = %q{ 90 | 91 |
92 | content 93 |
94 | 95 | } 96 | 97 | assert_html result, source, scope: self, locals: {body: body, content: content } 98 | end 99 | 100 | def test_correct_line_number 101 | source = %q{ 102 | html 103 | head 104 | body 105 | p Slim 106 | = '' 107 | = '' 108 | = '' 109 | = unknown_ruby_method 110 | } 111 | 112 | assert_ruby_error NameError,"(__TEMPLATE__):9", source 113 | end 114 | 115 | def test_unindenting 116 | source = %q{ 117 | span before 118 | span = " middle " 119 | span after 120 | } 121 | 122 | result = %q{before middle after} 123 | 124 | assert_html result, source 125 | 126 | source = %q{ 127 | html 128 | body == "
\n link\n
" 129 | } 130 | 131 | result = %q{ 132 | 133 |
134 | link 135 |
136 | 137 | } 138 | assert_html result, source 139 | end 140 | 141 | def test_helper_unindent 142 | source = %q{ 143 | = define_macro :content 144 | div 145 | a link 146 | html 147 | body 148 | == call_macro :content 149 | } 150 | 151 | result = %q{ 152 | 153 | 154 | 155 |
156 | link 157 |
158 | 159 | } 160 | 161 | assert_html result, source 162 | end 163 | end 164 | -------------------------------------------------------------------------------- /test/core/test_ruby_errors.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSlimRubyErrors < TestSlim 4 | def test_multline_attribute 5 | source = %q{ 6 | p(data-1=1 7 | data2-=1) 8 | p 9 | = unknown_ruby_method 10 | } 11 | 12 | assert_ruby_error NameError, "test.slim:5", source, file: 'test.slim' 13 | end 14 | 15 | def test_broken_output_line 16 | source = %q{ 17 | p = hello_world + \ 18 | hello_world + \ 19 | unknown_ruby_method 20 | } 21 | 22 | assert_ruby_error NameError, "test.slim:4", source, file: 'test.slim' 23 | end 24 | 25 | def test_broken_output_line2 26 | source = %q{ 27 | p = hello_world + \ 28 | hello_world 29 | p Hello 30 | = unknown_ruby_method 31 | } 32 | 33 | assert_ruby_error NameError,"(__TEMPLATE__):5", source 34 | end 35 | 36 | def test_output_block 37 | source = %q{ 38 | p = hello_world "Hello Ruby" do 39 | = unknown_ruby_method 40 | } 41 | 42 | assert_ruby_error NameError,"(__TEMPLATE__):3", source 43 | end 44 | 45 | def test_output_block2 46 | source = %q{ 47 | p = hello_world "Hello Ruby" do 48 | = "Hello from block" 49 | p Hello 50 | = unknown_ruby_method 51 | } 52 | 53 | assert_ruby_error NameError, "(__TEMPLATE__):5", source 54 | end 55 | 56 | def test_text_block 57 | source = %q{ 58 | p Text line 1 59 | Text line 2 60 | = unknown_ruby_method 61 | } 62 | 63 | assert_ruby_error NameError,"(__TEMPLATE__):4", source 64 | end 65 | 66 | def test_text_block2 67 | source = %q{ 68 | | 69 | Text line 1 70 | Text line 2 71 | = unknown_ruby_method 72 | } 73 | 74 | assert_ruby_error NameError,"(__TEMPLATE__):5", source 75 | end 76 | 77 | def test_comment 78 | source = %q{ 79 | / Comment line 1 80 | Comment line 2 81 | = unknown_ruby_method 82 | } 83 | 84 | assert_ruby_error NameError,"(__TEMPLATE__):4", source 85 | end 86 | 87 | def test_embedded_ruby1 88 | source = %q{ 89 | ruby: 90 | a = 1 91 | b = 2 92 | = a + b 93 | = unknown_ruby_method 94 | } 95 | 96 | assert_ruby_error NameError,"(__TEMPLATE__):7", source 97 | end 98 | 99 | def test_embedded_ruby2 100 | source = %q{ 101 | ruby: 102 | a = 1 103 | unknown_ruby_method 104 | } 105 | 106 | assert_ruby_error NameError,"(__TEMPLATE__):4", source 107 | end 108 | 109 | def test_embedded_ruby3 110 | source = %q{ 111 | h1 before 112 | ruby: 113 | a = 1 114 | 115 | h1 between 116 | ruby: 117 | b = a + 1 118 | 119 | unknown_ruby_method 120 | 121 | c = 3 122 | h1 third 123 | } 124 | 125 | assert_ruby_error NameError,"(__TEMPLATE__):10", source 126 | end 127 | 128 | def test_embedded_markdown 129 | source = %q{ 130 | markdown: 131 | #Header 132 | Hello from #{"Markdown!"} 133 | "Second Line!" 134 | = unknown_ruby_method 135 | } 136 | 137 | assert_ruby_error NameError,"(__TEMPLATE__):6", source 138 | end 139 | 140 | def test_embedded_javascript 141 | source = %q{ 142 | javascript: 143 | alert(); 144 | alert(); 145 | = unknown_ruby_method 146 | } 147 | 148 | assert_ruby_error NameError,"(__TEMPLATE__):5", source 149 | end 150 | 151 | def test_invalid_nested_code 152 | source = %q{ 153 | p 154 | - test = 123 155 | = "Hello from within a block! " 156 | } 157 | assert_ruby_syntax_error "(__TEMPLATE__):3", source 158 | end 159 | 160 | def test_invalid_nested_output 161 | source = %q{ 162 | p 163 | = "Hello Ruby!" 164 | = "Hello from within a block! " 165 | } 166 | assert_ruby_syntax_error "(__TEMPLATE__):3", source 167 | end 168 | 169 | def test_explicit_end 170 | source = %q{ 171 | div 172 | - if show_first? 173 | p The first paragraph 174 | - end 175 | } 176 | 177 | assert_runtime_error 'Explicit end statements are forbidden', source 178 | end 179 | 180 | def test_multiple_id_attribute 181 | source = %{ 182 | #alpha id="beta" Test it 183 | } 184 | assert_runtime_error 'Multiple id attributes specified', source 185 | end 186 | 187 | def test_splat_multiple_id_attribute 188 | source = %{ 189 | #alpha *{id:"beta"} Test it 190 | } 191 | assert_runtime_error 'Multiple id attributes specified', source 192 | end 193 | 194 | # def test_invalid_option 195 | # render('', foobar: 42) 196 | # raise Exception, 'ArgumentError expected' 197 | # rescue ArgumentError => ex 198 | # assert_equal 'Option :foobar is not supported by Slim::Engine', ex.message 199 | # end 200 | end 201 | -------------------------------------------------------------------------------- /test/core/test_slim_template.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class ::MockError < NameError 4 | end 5 | 6 | class TestSlimTemplate < TestSlim 7 | def test_default_mime_type 8 | assert_equal 'text/html', Slim::Template.default_mime_type 9 | end 10 | 11 | def test_registered_extension 12 | assert_equal Slim::Template, Tilt['test.slim'] 13 | end 14 | 15 | def test_preparing_and_evaluating 16 | template = Slim::Template.new { |t| "p Hello World!\n" } 17 | assert_equal "

Hello World!

", template.render 18 | end 19 | 20 | def test_evaluating_in_an_object_scope 21 | template = Slim::Template.new { "p = 'Hey ' + @name + '!'\n" } 22 | scope = Object.new 23 | scope.instance_variable_set :@name, 'Joe' 24 | assert_equal "

Hey Joe!

", template.render(scope) 25 | end 26 | 27 | def test_passing_a_block_for_yield 28 | template = Slim::Template.new { "p = 'Hey ' + yield + '!'\n" } 29 | assert_equal "

Hey Joe!

", template.render { 'Joe' } 30 | end 31 | 32 | def test_backtrace_file_and_line_reporting_without_locals 33 | data = File.read(__FILE__).split("\n__END__\n").last 34 | fail unless data[0] == ?h 35 | template = Slim::Template.new('test.slim', 10) { data } 36 | begin 37 | template.render 38 | fail 'should have raised an exception' 39 | rescue => ex 40 | assert_kind_of NameError, ex 41 | assert_backtrace(ex, 'test.slim:12') 42 | end 43 | end 44 | 45 | def test_backtrace_file_and_line_reporting_with_locals 46 | data = File.read(__FILE__).split("\n__END__\n").last 47 | fail unless data[0] == ?h 48 | template = Slim::Template.new('test.slim') { data } 49 | begin 50 | template.render(Object.new, name: 'Joe', foo: 'bar') 51 | fail 'should have raised an exception' 52 | rescue => ex 53 | assert_kind_of MockError, ex 54 | assert_backtrace(ex, 'test.slim:5') 55 | end 56 | end 57 | 58 | def test_compiling_template_source_to_a_method 59 | template = Slim::Template.new { |t| "Hello World!" } 60 | template.render 61 | method = template.send(:compiled_method, []) 62 | assert_kind_of UnboundMethod, method 63 | end 64 | 65 | def test_passing_locals 66 | template = Slim::Template.new { "p = 'Hey ' + name + '!'\n" } 67 | assert_equal "

Hey Joe!

", template.render(Object.new, name: 'Joe') 68 | end 69 | end 70 | 71 | __END__ 72 | html 73 | body 74 | h1 = "Hey #{name}" 75 | 76 | = raise MockError 77 | 78 | p we never get here 79 | -------------------------------------------------------------------------------- /test/core/test_splat_prefix_option.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSplatPrefixOption < TestSlim 4 | 5 | def prefixes 6 | ['*','**','*!','*%','*^','*$'] 7 | end 8 | 9 | def options(prefix) 10 | { splat_prefix: prefix } 11 | end 12 | 13 | def test_splat_without_content 14 | prefixes.each do |prefix| 15 | source = %Q{ 16 | #{prefix}hash 17 | p#{prefix}hash 18 | } 19 | 20 | assert_html '

', source, options(prefix) 21 | end 22 | end 23 | 24 | def test_shortcut_splat 25 | prefixes.each do |prefix| 26 | source = %Q{ 27 | #{prefix}hash This is my title 28 | } 29 | 30 | assert_html '
This is my title
', source, options(prefix) 31 | end 32 | end 33 | 34 | def test_splat 35 | prefixes.each do |prefix| 36 | source = %Q{ 37 | h1 #{prefix}hash class=[] This is my title 38 | } 39 | 40 | assert_html '

This is my title

', source, options(prefix) 41 | end 42 | end 43 | 44 | def test_closed_splat 45 | prefixes.each do |prefix| 46 | source = %Q{ 47 | #{prefix}hash / 48 | } 49 | 50 | assert_html '
', source, options(prefix) 51 | end 52 | end 53 | 54 | def test_splat_tag_name 55 | prefixes.each do |prefix| 56 | source = %Q{ 57 | #{prefix}{tag: 'h1', id: 'title'} This is my title 58 | } 59 | 60 | assert_html '

This is my title

', source, options(prefix) 61 | end 62 | end 63 | 64 | 65 | def test_splat_empty_tag_name 66 | prefixes.each do |prefix| 67 | source = %Q{ 68 | #{prefix}{tag: '', id: 'test'} This is my title 69 | } 70 | 71 | assert_html '
This is my title
', source, options(prefix) 72 | end 73 | end 74 | 75 | def test_closed_splat_tag 76 | prefixes.each do |prefix| 77 | source = %Q{ 78 | #{prefix}hash / 79 | } 80 | 81 | assert_html '
', source, options(prefix) 82 | end 83 | end 84 | 85 | def test_splat_with_id_shortcut 86 | prefixes.each do |prefix| 87 | source = %Q{ 88 | #myid#{prefix}hash This is my title 89 | } 90 | 91 | assert_html '
This is my title
', source, options(prefix) 92 | end 93 | end 94 | 95 | def test_splat_with_class_shortcut 96 | prefixes.each do |prefix| 97 | source = %Q{ 98 | .myclass#{prefix}hash This is my title 99 | } 100 | 101 | assert_html '
This is my title
', source, options(prefix) 102 | end 103 | end 104 | 105 | def test_splat_with_id_and_class_shortcuts 106 | prefixes.each do |prefix| 107 | source = %Q{ 108 | #myid.myclass#{prefix}hash This is my title 109 | } 110 | 111 | assert_html '
This is my title
', source, options(prefix) 112 | end 113 | end 114 | 115 | def test_splat_with_class_merging 116 | prefixes.each do |prefix| 117 | source = %Q{ 118 | #myid.myclass #{prefix}{class: [:secondclass, %w(x y z)]} #{prefix}hash This is my title 119 | } 120 | 121 | assert_html '
This is my title
', source, options(prefix) 122 | end 123 | end 124 | 125 | def test_splat_with_boolean_attribute 126 | prefixes.each do |prefix| 127 | source = %Q{ 128 | #{prefix}{disabled: true, empty1: false, nonempty: '', empty2: nil} This is my title 129 | } 130 | 131 | assert_html '
This is my title
', source, options(prefix) 132 | end 133 | end 134 | 135 | def test_splat_merging_with_arrays 136 | prefixes.each do |prefix| 137 | source = %Q{ 138 | #{prefix}{a: 1, b: 2} #{prefix}[[:c, 3], [:d, 4]] #{prefix}[[:e, 5], [:f, 6]] This is my title 139 | } 140 | 141 | assert_html '
This is my title
', source, options(prefix) 142 | end 143 | end 144 | 145 | def test_splat_with_other_attributes 146 | prefixes.each do |prefix| 147 | source = %Q{ 148 | h1 data-id="123" #{prefix}hash This is my title 149 | } 150 | 151 | assert_html '

This is my title

', source, options(prefix) 152 | end 153 | end 154 | 155 | end 156 | -------------------------------------------------------------------------------- /test/core/test_tabs.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSlimTabs < TestSlim 4 | 5 | def teardown 6 | Slim::Engine.set_options tabsize: 4 7 | end 8 | 9 | def test_single_tab1_expansion 10 | 11 | Slim::Engine.set_options tabsize: 1 12 | 13 | source = %Q{ 14 | | 15 | \t0 16 | \t1 17 | \t2 18 | \t3 19 | \t4 20 | \t5 21 | \t6 22 | \t7 23 | \t8 24 | } 25 | 26 | result = %q{ 27 | 0 28 | 1 29 | 2 30 | 3 31 | 4 32 | 5 33 | 6 34 | 7 35 | 8 36 | }.strip 37 | 38 | assert_html result, source 39 | end 40 | 41 | def test_single_tab4_expansion 42 | 43 | Slim::Engine.set_options tabsize: 4 44 | 45 | source = %Q{ 46 | | 47 | \t0 48 | \t1 49 | \t2 50 | \t3 51 | \t4 52 | \t5 53 | \t6 54 | \t7 55 | \t8 56 | } 57 | 58 | result = %q{ 59 | 0 60 | 1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 6 66 | 7 67 | 8 68 | }.strip 69 | 70 | assert_html result, source 71 | end 72 | 73 | def test_multi_tab1_expansion 74 | 75 | Slim::Engine.set_options tabsize: 1 76 | 77 | source = %Q{ 78 | | 79 | \t0 80 | \t\t1 81 | \t \t2 82 | \t \t3 83 | \t \t4 84 | \t\t1 85 | \t \t2 86 | \t \t3 87 | \t \t4 88 | \t\t1 89 | \t \t2 90 | \t \t3 91 | \t \t4 92 | \t\t1 93 | \t \t2 94 | \t \t3 95 | \t \t4 96 | } 97 | 98 | result = %q{ 99 | 0 100 | 1 101 | 2 102 | 3 103 | 4 104 | 1 105 | 2 106 | 3 107 | 4 108 | 1 109 | 2 110 | 3 111 | 4 112 | 1 113 | 2 114 | 3 115 | 4 116 | }.strip 117 | 118 | assert_html result, source 119 | end 120 | 121 | def test_multi_tab4_expansion 122 | 123 | Slim::Engine.set_options tabsize: 4 124 | 125 | source = %Q{ 126 | | 127 | \t0 128 | \t\t1 129 | \t \t2 130 | \t \t3 131 | \t \t4 132 | \t\t1 133 | \t \t2 134 | \t \t3 135 | \t \t4 136 | \t\t1 137 | \t \t2 138 | \t \t3 139 | \t \t4 140 | \t\t1 141 | \t \t2 142 | \t \t3 143 | \t \t4 144 | } 145 | 146 | result = %q{ 147 | 0 148 | 1 149 | 2 150 | 3 151 | 4 152 | 1 153 | 2 154 | 3 155 | 4 156 | 1 157 | 2 158 | 3 159 | 4 160 | 1 161 | 2 162 | 3 163 | 4 164 | }.strip 165 | 166 | assert_html result, source 167 | end 168 | 169 | end 170 | -------------------------------------------------------------------------------- /test/core/test_text_interpolation.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSlimTextInterpolation < TestSlim 4 | def test_interpolation_in_attribute 5 | source = %q{ 6 | p id="a#{id_helper}b" = hello_world 7 | } 8 | 9 | assert_html '

Hello World from @env

', source 10 | end 11 | 12 | def test_nested_interpolation_in_attribute 13 | source = %q{ 14 | p id="#{"abc#{1+1}" + "("}" = hello_world 15 | } 16 | 17 | assert_html '

Hello World from @env

', source 18 | end 19 | 20 | def test_interpolation_in_text 21 | source = %q{ 22 | p 23 | | #{hello_world} with "quotes" 24 | p 25 | | 26 | A message from the compiler: #{hello_world} 27 | } 28 | 29 | assert_html '

Hello World from @env with "quotes"

A message from the compiler: Hello World from @env

', source 30 | end 31 | 32 | def test_interpolation_in_tag 33 | source = %q{ 34 | p #{hello_world} 35 | } 36 | 37 | assert_html '

Hello World from @env

', source 38 | end 39 | 40 | def test_escape_interpolation 41 | source = %q{ 42 | p \\#{hello_world} 43 | p text1 \\#{hello_world} text2 44 | } 45 | 46 | assert_html '

#{hello_world}

text1 #{hello_world} text2

', source 47 | end 48 | 49 | def test_complex_interpolation 50 | source = %q{ 51 | p Message: #{message('hello', "user #{output_number}")} 52 | } 53 | 54 | assert_html '

Message: hello user 1337

', source 55 | end 56 | 57 | def test_interpolation_with_escaping 58 | source = %q{ 59 | | #{evil_method} 60 | } 61 | 62 | assert_html '<script>do_something_evil();</script>', source 63 | end 64 | 65 | def test_interpolation_without_escaping 66 | source = %q{ 67 | | #{{evil_method}} 68 | } 69 | 70 | assert_html '', source 71 | end 72 | 73 | def test_interpolation_with_escaping_and_delimiter 74 | source = %q{ 75 | | #{(evil_method)} 76 | } 77 | assert_html '<script>do_something_evil();</script>', source 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /test/core/test_thread_options.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSlimThreadOptions < TestSlim 4 | def test_thread_options 5 | source = %q{p.test} 6 | 7 | assert_html '

', source 8 | assert_html "

", source, attr_quote: "'" 9 | 10 | Slim::Engine.with_options(attr_quote: "'") do 11 | assert_html "

", source 12 | assert_html '

', source, attr_quote: '"' 13 | end 14 | 15 | assert_html '

', source 16 | assert_html "

", source, attr_quote: "'" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/core/test_unicode.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestSlimUnicode < TestSlim 4 | def test_unicode_tags 5 | source = "Статья года" 6 | result = "<Статья>года" 7 | assert_html result, source 8 | end 9 | 10 | def test_unicode_attrs 11 | source = "Статья года=123 content" 12 | result = "<Статья года=\"123\">content" 13 | assert_html result, source 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/include/files/recursive.slim: -------------------------------------------------------------------------------- 1 | | rec -------------------------------------------------------------------------------- /test/include/files/slimfile.slim: -------------------------------------------------------------------------------- 1 | | slim1 2 | include recursive 3 | | slim2 -------------------------------------------------------------------------------- /test/include/files/subdir/test.slim: -------------------------------------------------------------------------------- 1 | | subdir -------------------------------------------------------------------------------- /test/include/files/textfile: -------------------------------------------------------------------------------- 1 | 1+2=#{1+2} -------------------------------------------------------------------------------- /test/include/test_include.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | require 'slim/include' 3 | 4 | class TestSlimInclude < TestSlim 5 | def test_include 6 | source = %q{ 7 | br/ 8 | a: include slimfile 9 | b: include textfile 10 | c: include slimfile.slim 11 | d: include subdir/test 12 | } 13 | assert_html '
slim1recslim21+2=3slim1recslim2subdir', source, include_dirs: [File.expand_path('files', File.dirname(__FILE__))] 14 | end 15 | 16 | def test_include_with_newline 17 | source = %q{ 18 | a: include slimfile 19 | 20 | .content 21 | } 22 | assert_html 'slim1recslim2
', source, include_dirs: [File.expand_path('files', File.dirname(__FILE__))] 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/literate/helper.rb: -------------------------------------------------------------------------------- 1 | require 'slim' 2 | require 'slim/logic_less' 3 | require 'slim/translator' 4 | require 'slim/grammar' 5 | require 'minitest/autorun' 6 | 7 | Slim::Engine.after Slim::Parser, Temple::Filters::Validator, grammar: Slim::Grammar 8 | Slim::Engine.before :Pretty, Temple::Filters::Validator 9 | Slim::Engine.set_options tr: false, logic_less: false 10 | 11 | class Minitest::Spec 12 | def render(source, options = {}, &block) 13 | Slim::Template.new(options) { source }.render(self, &block) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/literate/run.rb: -------------------------------------------------------------------------------- 1 | require 'temple' 2 | 3 | class LiterateTest < Temple::Engine 4 | class Parser < Temple::Parser 5 | def call(lines) 6 | stack = [[:multi]] 7 | until lines.empty? 8 | case lines.shift 9 | when /\A(#+)\s*(.*)\Z/ 10 | stack.pop(stack.size - $1.size) 11 | block = [:multi] 12 | stack.last << [:section, $2, block] 13 | stack << block 14 | when /\A~{3,}\s*(\w+)\s*\Z/ 15 | lang = $1 16 | code = [] 17 | until lines.empty? 18 | case lines.shift 19 | when /\A~{3,}\s*\Z/ 20 | break 21 | when /\A.*\Z/ 22 | code << $& 23 | end 24 | end 25 | stack.last << [lang.to_sym, code.join("\n")] 26 | when /\A\s*\Z/ 27 | when /\A\s*(.*?)\s*Z/ 28 | stack.last << [:comment, $1] 29 | end 30 | end 31 | stack.first 32 | end 33 | end 34 | 35 | class Compiler < Temple::Filter 36 | def call(exp) 37 | @opts, @in_testcase = {}, false 38 | "require 'helper'\n\n#{compile(exp)}" 39 | end 40 | 41 | def on_section(title, body) 42 | old_opts = @opts.dup 43 | raise Temple::FilterError, 'New section between slim and html block' if @in_testcase 44 | "describe #{title.inspect} do\n #{compile(body).gsub("\n", "\n ")}\nend\n" 45 | ensure 46 | @opts = old_opts 47 | end 48 | 49 | def on_multi(*exps) 50 | exps.map {|exp| compile(exp) }.join("\n") 51 | end 52 | 53 | def on_comment(text) 54 | "#{@in_testcase ? ' ' : ''}# #{text}" 55 | end 56 | 57 | def on_slim(code) 58 | raise Temple::FilterError, 'Slim block must be followed by html block' if @in_testcase 59 | @in_testcase = true 60 | "it 'should render' do\n slim = #{code.inspect}" 61 | end 62 | 63 | def on_html(code) 64 | raise Temple::FilterError, 'Html block must be preceded by slim block' unless @in_testcase 65 | @in_testcase = false 66 | result = " html = #{code.inspect}\n".dup 67 | if @opts.empty? 68 | result << " _(render(slim)).must_equal html\nend\n" 69 | else 70 | result << " options = #{@opts.inspect}\n _(render(slim, options)).must_equal html\nend\n" 71 | end 72 | end 73 | 74 | def on_options(code) 75 | raise Temple::FilterError, 'Options set inside test case' if @in_testcase 76 | @opts.update(eval("{#{code}}")) 77 | "# #{code.gsub("\n", "\n# ")}" 78 | end 79 | 80 | def on(*exp) 81 | raise Temple::InvalidExpression, exp 82 | end 83 | end 84 | 85 | use Parser 86 | use Compiler 87 | use(:Evaluator) {|code| eval(code) } 88 | end 89 | 90 | Dir.glob(File.join(File.dirname(__FILE__), '*.md')) do |file| 91 | LiterateTest.new.call(File.readlines(file)) 92 | end 93 | -------------------------------------------------------------------------------- /test/logic_less/test_logic_less.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | require 'slim/logic_less' 3 | 4 | class TestSlimLogicLess < TestSlim 5 | class Scope 6 | def initialize 7 | @hash = { 8 | person: [ 9 | { name: 'Joe', age: 1, selected: true }, 10 | { name: 'Jack', age: 2 } 11 | ] 12 | } 13 | end 14 | end 15 | 16 | def test_lambda 17 | source = %q{ 18 | p 19 | == person 20 | .name = name 21 | == simple 22 | .hello= hello 23 | == list 24 | li = key 25 | } 26 | 27 | hash = { 28 | hello: 'Hello!', 29 | person: lambda do |&block| 30 | %w(Joe Jack).map do |name| 31 | "#{block.call(name: name)}" 32 | end.join 33 | end, 34 | simple: lambda do |&block| 35 | "
#{block.call}
" 36 | end, 37 | list: lambda do |&block| 38 | list = [{key: 'First'}, {key: 'Second'}] 39 | "
    #{block.call(*list)}
" 40 | end 41 | } 42 | 43 | assert_html '

Joe
Jack
Hello!
  • First
  • Second

', source, scope: hash 44 | end 45 | 46 | def test_symbol_hash 47 | source = %q{ 48 | p 49 | - person 50 | .name = name 51 | } 52 | 53 | hash = { 54 | person: [ 55 | { name: 'Joe', }, 56 | { name: 'Jack', } 57 | ] 58 | } 59 | 60 | assert_html '

Joe
Jack

', source, scope: hash 61 | end 62 | 63 | def test_string_access 64 | source = %q{ 65 | p 66 | - person 67 | .name = name 68 | } 69 | 70 | hash = { 71 | 'person' => [ 72 | { 'name' => 'Joe', }, 73 | { 'name' => 'Jack', } 74 | ] 75 | } 76 | 77 | assert_html '

Joe
Jack

', source, scope: hash, dictionary_access: :string 78 | end 79 | 80 | def test_symbol_access 81 | source = %q{ 82 | p 83 | - person 84 | .name = name 85 | } 86 | 87 | hash = { 88 | person: [ 89 | { name: 'Joe', }, 90 | { name: 'Jack', } 91 | ] 92 | } 93 | 94 | assert_html '

Joe
Jack

', source, scope: hash, dictionary_access: :symbol 95 | end 96 | 97 | def test_method_access 98 | source = %q{ 99 | p 100 | - person 101 | .name = name 102 | } 103 | 104 | object = Object.new 105 | def object.person 106 | %w(Joe Jack).map do |name| 107 | person = Object.new 108 | person.instance_variable_set(:@name, name) 109 | def person.name 110 | @name 111 | end 112 | person 113 | end 114 | end 115 | 116 | assert_html '

Joe
Jack

', source, scope: object, dictionary_access: :method 117 | end 118 | 119 | def test_method_access_without_private 120 | source = %q{ 121 | p 122 | - person 123 | .age = age 124 | } 125 | 126 | object = Object.new 127 | def object.person 128 | person = Object.new 129 | def person.age 130 | 42 131 | end 132 | person.singleton_class.class_eval { private :age } 133 | person 134 | end 135 | 136 | assert_html '

', source, scope: object, dictionary_access: :method 137 | end 138 | 139 | def test_instance_variable_access 140 | source = %q{ 141 | p 142 | - person 143 | .name = name 144 | } 145 | 146 | object = Object.new 147 | object.instance_variable_set(:@person, %w(Joe Jack).map do |name| 148 | person = Object.new 149 | person.instance_variable_set(:@name, name) 150 | person 151 | end) 152 | 153 | assert_html '

Joe
Jack

', source, scope: object, dictionary_access: :instance_variable 154 | end 155 | 156 | def test_to_s_access 157 | source = %q{ 158 | p 159 | - people 160 | .name = self 161 | } 162 | 163 | hash = { 164 | people: [ 165 | 'Joe', 166 | 'Jack' 167 | ] 168 | } 169 | 170 | assert_html '

Joe
Jack

', source, scope: hash, dictionary_access: :symbol 171 | end 172 | 173 | def test_string_hash 174 | source = %q{ 175 | p 176 | - person 177 | .name = name 178 | } 179 | 180 | hash = { 181 | 'person' => [ 182 | { 'name' => 'Joe', }, 183 | { 'name' => 'Jack', } 184 | ] 185 | } 186 | 187 | assert_html '

Joe
Jack

', source, scope: hash 188 | end 189 | 190 | def test_dictionary_option 191 | source = %q{ 192 | p 193 | - person 194 | .name = name 195 | } 196 | 197 | assert_html '

Joe
Jack

', source, scope: Scope.new, dictionary: '@hash' 198 | end 199 | 200 | def test_flag_section 201 | source = %q{ 202 | p 203 | - show_person 204 | - person 205 | .name = name 206 | - show_person 207 | | shown 208 | } 209 | 210 | hash = { 211 | show_person: true, 212 | person: [ 213 | { name: 'Joe', }, 214 | { name: 'Jack', } 215 | ] 216 | } 217 | 218 | assert_html '

Joe
Jack
shown

', source, scope: hash 219 | end 220 | 221 | def test_inverted_section 222 | source = %q{ 223 | p 224 | - person 225 | .name = name 226 | -! person 227 | | No person 228 | - !person 229 | | No person 2 230 | } 231 | 232 | hash = {} 233 | 234 | assert_html '

No person No person 2

', source, scope: hash 235 | end 236 | 237 | def test_escaped_interpolation 238 | source = %q{ 239 | p text with \#{123} test 240 | } 241 | 242 | assert_html '

text with #{123} test

', source 243 | end 244 | 245 | def test_ruby_attributes 246 | source = %q{ 247 | p 248 | - person 249 | b name=name Person 250 | a id=name = age 251 | span class=name 252 | Person 253 | } 254 | 255 | assert_html '

Person1Person2

', source, scope: Scope.new, dictionary: '@hash' 256 | end 257 | 258 | def test_boolean_attributes 259 | source = %q{ 260 | p 261 | - person 262 | input checked=selected = name 263 | } 264 | 265 | assert_html '

JoeJack

', source, scope: Scope.new, dictionary: '@hash' 266 | end 267 | 268 | def test_sections 269 | source = %q{ 270 | p 271 | - person 272 | .name = name 273 | } 274 | assert_html '

Joe
Jack

', source, dictionary: 'ViewEnv.new' 275 | end 276 | 277 | def test_with_array 278 | source = %q{ 279 | ul 280 | - people_with_locations 281 | li = name 282 | li = city 283 | } 284 | assert_html '
  • Andy
  • Atlanta
  • Fred
  • Melbourne
  • Daniel
  • Karlsruhe
', source, dictionary: 'ViewEnv.new' 285 | end 286 | 287 | def test_method 288 | source = %q{ 289 | a href=output_number Link 290 | } 291 | assert_html 'Link', source, dictionary: 'ViewEnv.new' 292 | end 293 | 294 | def test_conditional_parent 295 | source = %q{ 296 | - prev_page 297 | li.previous 298 | a href=prev_page Older 299 | - next_page 300 | li.next 301 | a href=next_page Newer} 302 | assert_html'', source, scope: {prev_page: 'prev', next_page: 'next'} 303 | end 304 | 305 | def test_render_with_yield 306 | source = %q{ 307 | div 308 | == yield 309 | } 310 | 311 | assert_html '
This is the menu
', source do 312 | 'This is the menu' 313 | end 314 | end 315 | 316 | def test_render_parent_lambda 317 | source = %q{ 318 | - list 319 | == fn 320 | p = string 321 | } 322 | 323 | assert_html '

str

str

str

', 324 | source, scope: { 325 | fn: ->(&block) { "#{block.call}" }, 326 | list: [ "item1", "item2", "item3" ], 327 | string: "str" 328 | } 329 | end 330 | end 331 | -------------------------------------------------------------------------------- /test/rails/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | require 'rake' 6 | 7 | Dummy::Application.load_tasks 8 | -------------------------------------------------------------------------------- /test/rails/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | // file required by sprockets >= 4.0 2 | -------------------------------------------------------------------------------- /test/rails/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /test/rails/app/controllers/entries_controller.rb: -------------------------------------------------------------------------------- 1 | class EntriesController < ApplicationController 2 | def edit 3 | @entry = Entry.new 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/rails/app/controllers/slim_controller.rb: -------------------------------------------------------------------------------- 1 | class SlimController < ApplicationController 2 | def normal 3 | end 4 | 5 | def xml 6 | end 7 | 8 | def no_layout 9 | render layout: false 10 | end 11 | 12 | def variables 13 | @hello = "Hello Slim with variables!" 14 | end 15 | 16 | def partial 17 | end 18 | 19 | def streaming 20 | @hello = "Hello Streaming!" 21 | render :content_for, stream: true 22 | end 23 | 24 | def integers 25 | @integer = 1337 26 | end 27 | 28 | def thread_options 29 | default_shortcut = {'#' => {attr: 'id'}, '.' => {attr: 'class'} } 30 | Slim::Engine.with_options(shortcut: default_shortcut.merge({'@' => { attr: params[:attr] }})) do 31 | render 32 | end 33 | end 34 | 35 | def variant 36 | request.variant = :testvariant 37 | render :normal 38 | end 39 | 40 | def content_for 41 | @hello = "Hello Slim!" 42 | end 43 | 44 | def helper 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/rails/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def headline(&block) 3 | "

#{capture(&block)}

".html_safe 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/rails/app/models/entry.rb: -------------------------------------------------------------------------------- 1 | class Entry 2 | include ActiveModel::Conversion 3 | extend ActiveModel::Naming 4 | 5 | attr_accessor :name 6 | 7 | def initialize(attributes = {}) 8 | attributes.each do |name, value| 9 | send("#{name}=", value) 10 | end 11 | end 12 | 13 | def persisted? 14 | false 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/rails/app/views/entries/edit.html.slim: -------------------------------------------------------------------------------- 1 | = form_for @entry do |f| 2 | label: b Name 3 | = f.text_field :name 4 | -------------------------------------------------------------------------------- /test/rails/app/views/layouts/application.html+testvariant.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | 3 | html 4 | head 5 | title Variant 6 | = csrf_meta_tag 7 | 8 | body 9 | = yield :page_heading 10 | .content= yield 11 | -------------------------------------------------------------------------------- /test/rails/app/views/layouts/application.html.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | 3 | html 4 | head 5 | title Dummy 6 | = csrf_meta_tag 7 | 8 | body 9 | = yield :page_heading 10 | .content= yield 11 | -------------------------------------------------------------------------------- /test/rails/app/views/slim/_partial.html.slim: -------------------------------------------------------------------------------- 1 | p With a partial! -------------------------------------------------------------------------------- /test/rails/app/views/slim/attributes.html.slim: -------------------------------------------------------------------------------- 1 | / Exercises Temple `:capture` handling (used here for preparing HTML attributes) with the 2 | / Rails-specific Temple generators 3 | .static class="#{['a', 'b'].join('-')}" 4 | -------------------------------------------------------------------------------- /test/rails/app/views/slim/content_for.html.slim: -------------------------------------------------------------------------------- 1 | p Page content 2 | - content_for :page_heading, "Heading set from a view" 3 | - content_for :hello_slim do 4 | p= @hello 5 | 6 | h1= yield :hello_slim 7 | h2= yield :hello_slim -------------------------------------------------------------------------------- /test/rails/app/views/slim/erb.html.erb: -------------------------------------------------------------------------------- 1 |

Hello Erb!

-------------------------------------------------------------------------------- /test/rails/app/views/slim/form_for.html.slim: -------------------------------------------------------------------------------- 1 | = form_for @entry do |f| 2 | = f.text_field :name 3 | -------------------------------------------------------------------------------- /test/rails/app/views/slim/helper.html.slim: -------------------------------------------------------------------------------- 1 | p 2 | = headline do 3 | ' Hello 4 | = 'User' 5 | -------------------------------------------------------------------------------- /test/rails/app/views/slim/integers.html.slim: -------------------------------------------------------------------------------- 1 | p= @integer -------------------------------------------------------------------------------- /test/rails/app/views/slim/no_layout.html.slim: -------------------------------------------------------------------------------- 1 | h1 Hello Slim without a layout! -------------------------------------------------------------------------------- /test/rails/app/views/slim/normal.html.slim: -------------------------------------------------------------------------------- 1 | h1 Hello Slim! -------------------------------------------------------------------------------- /test/rails/app/views/slim/partial.html.slim: -------------------------------------------------------------------------------- 1 | h1 Hello Slim! 2 | = render "partial" -------------------------------------------------------------------------------- /test/rails/app/views/slim/splat.html.slim: -------------------------------------------------------------------------------- 1 | #splat 2 | *{tag: 'splat'} Hello 3 | -------------------------------------------------------------------------------- /test/rails/app/views/slim/splat_with_delimiter.slim: -------------------------------------------------------------------------------- 1 | .cute *{class: "nice"} Hello -------------------------------------------------------------------------------- /test/rails/app/views/slim/thread_options.html.slim: -------------------------------------------------------------------------------- 1 | p@empty Test 2 | -------------------------------------------------------------------------------- /test/rails/app/views/slim/variables.html.slim: -------------------------------------------------------------------------------- 1 | h1= @hello -------------------------------------------------------------------------------- /test/rails/app/views/slim/xml.slim: -------------------------------------------------------------------------------- 1 | h1 Hello Slim! -------------------------------------------------------------------------------- /test/rails/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Dummy::Application 5 | -------------------------------------------------------------------------------- /test/rails/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'logger' 4 | require 'active_model/railtie' 5 | require 'action_controller/railtie' 6 | require 'action_view/railtie' 7 | #require 'active_record/railtie' 8 | #require 'action_mailer/railtie' 9 | #require 'sprockets/railtie' 10 | require 'slim' 11 | 12 | module Dummy 13 | class Application < Rails::Application 14 | # Settings in config/environments/* take precedence over those specified here. 15 | # Application configuration should go into files in config/initializers 16 | # -- all .rb files in that directory are automatically loaded. 17 | 18 | # Custom directories with classes and modules you want to be autoloadable. 19 | # config.autoload_paths += %W(#{config.root}/extras) 20 | 21 | # Only load the plugins named here, in the order given (default is alphabetical). 22 | # :all can be used as a placeholder for all plugins not explicitly named. 23 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 24 | 25 | # Activate observers that should always be running. 26 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 27 | 28 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 29 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 30 | # config.time_zone = 'Central Time (US & Canada)' 31 | 32 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 33 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 34 | # config.i18n.default_locale = :de 35 | 36 | # JavaScript files you want as :defaults (application.js is always included). 37 | # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) 38 | 39 | # Configure the default encoding used in templates for Ruby 1.9. 40 | config.encoding = "utf-8" 41 | 42 | # Configure sensitive parameters which will be filtered from the log file. 43 | config.filter_parameters += [:password] 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/rails/config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | gemfile = File.expand_path('../../../../../../Gemfile', __FILE__) 3 | 4 | if File.exist?(gemfile) 5 | ENV['BUNDLE_GEMFILE'] = gemfile 6 | require 'bundler' 7 | Bundler.setup(:default, :integration) 8 | end 9 | 10 | $:.unshift File.expand_path('../../../../../../lib', __FILE__) -------------------------------------------------------------------------------- /test/rails/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | Dummy::Application.initialize! 6 | -------------------------------------------------------------------------------- /test/rails/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Show full error reports and disable caching 11 | config.consider_all_requests_local = true 12 | config.action_controller.perform_caching = false 13 | 14 | # Raise exceptions instead of rendering exception templates 15 | config.action_dispatch.show_exceptions = false 16 | 17 | # Disable request forgery protection in test environment 18 | config.action_controller.allow_forgery_protection = false 19 | 20 | # Tell Action Mailer not to deliver emails to the real world. 21 | # The :test delivery method accumulates sent emails in the 22 | # ActionMailer::Base.deliveries array. 23 | #config.action_mailer.delivery_method = :test 24 | 25 | # Use SQL instead of Active Record's schema dumper when creating the test database. 26 | # This is necessary if your schema can't be completely dumped by the schema dumper, 27 | # like if you have constraints or database-specific column types 28 | # config.active_record.schema_format = :sql 29 | 30 | # Print deprecation notices to the stderr 31 | config.active_support.deprecation = :stderr 32 | 33 | config.eager_load = false 34 | end 35 | -------------------------------------------------------------------------------- /test/rails/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /test/rails/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /test/rails/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /test/rails/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # Dummy::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /test/rails/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /test/rails/config/routes.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.routes.draw do 2 | # The priority is based upon order of creation: 3 | # first created -> highest priority. 4 | 5 | resources :entries 6 | 7 | # Sample of regular route: 8 | # match 'products/:id' => 'catalog#view' 9 | # Keep in mind you can assign values other than :controller and :action 10 | 11 | # Sample of named route: 12 | # match 'products/:id/purchase' => 'catalog#purchase', as: :purchase 13 | # This route can be invoked with purchase_url(id: product.id) 14 | 15 | # Sample resource route (maps HTTP verbs to controller actions automatically): 16 | # resources :products 17 | 18 | # Sample resource route with options: 19 | # resources :products do 20 | # member do 21 | # get 'short' 22 | # post 'toggle' 23 | # end 24 | # 25 | # collection do 26 | # get 'sold' 27 | # end 28 | # end 29 | 30 | # Sample resource route with sub-resources: 31 | # resources :products do 32 | # resources :comments, :sales 33 | # resource :seller 34 | # end 35 | 36 | # Sample resource route with more complex sub-resources 37 | # resources :products do 38 | # resources :comments 39 | # resources :sales do 40 | # get 'recent', on: :collection 41 | # end 42 | # end 43 | 44 | # Sample resource route within a namespace: 45 | # namespace :admin do 46 | # # Directs /admin/products/* to Admin::ProductsController 47 | # # (app/controllers/admin/products_controller.rb) 48 | # resources :products 49 | # end 50 | 51 | # You can have the root of your site routed with "root" 52 | # just remember to delete public/index.html. 53 | # root to: "welcome#index" 54 | 55 | # See how all your routes lay out with "rake routes" 56 | 57 | # This is a legacy wild controller route that's not recommended for RESTful applications. 58 | # Note: This route will make all actions in every controller accessible via GET requests. 59 | get ':controller(/:action(/:id(.:format)))' 60 | end 61 | -------------------------------------------------------------------------------- /test/rails/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /test/rails/test/helper.rb: -------------------------------------------------------------------------------- 1 | # Configure Rails Envinronment 2 | ENV["RAILS_ENV"] = "test" 3 | 4 | require File.expand_path("../../config/environment.rb", __FILE__) 5 | require "rails/test_help" 6 | require "nokogiri" 7 | 8 | require 'rails-controller-testing' 9 | Rails::Controller::Testing.install 10 | 11 | Rails.backtrace_cleaner.remove_silencers! 12 | 13 | class ActionDispatch::IntegrationTest 14 | 15 | protected 16 | 17 | def assert_xpath(xpath, message="Unable to find '#{xpath}' in response body.") 18 | assert_response :success, "Response type is not :success (code 200..299)." 19 | 20 | body = @response.body 21 | assert !body.empty?, "No response body found." 22 | 23 | doc = Nokogiri::HTML(body) rescue nil 24 | assert_not_nil doc, "Cannot parse response body." 25 | 26 | assert doc.xpath(xpath).size >= 1, message 27 | end 28 | 29 | def assert_html(expected, options = {}) 30 | expected = "Dummy#{options[:heading]}
#{expected}
" unless options[:skip_layout] 31 | assert_equal expected, @response.body 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /test/rails/test/test_slim.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../helper', __FILE__) 2 | 3 | class TestSlim < ActionDispatch::IntegrationTest 4 | test "normal view" do 5 | get "/slim/normal" 6 | assert_response :success 7 | assert_template "slim/normal" 8 | assert_template "layouts/application" 9 | assert_html "

Hello Slim!

" 10 | end 11 | 12 | test "variant" do 13 | get "/slim/variant" 14 | assert_response :success 15 | assert_template "slim/normal" 16 | assert_template "layouts/application" 17 | assert_equal @response.body, "Variant

Hello Slim!

" 18 | end 19 | 20 | test "xml view" do 21 | get "/slim/xml" 22 | assert_response :success 23 | assert_template "slim/xml" 24 | assert_template "layouts/application" 25 | assert_html "

Hello Slim!

" 26 | end 27 | 28 | test "helper" do 29 | get "/slim/helper" 30 | assert_response :success 31 | assert_template "slim/helper" 32 | assert_template "layouts/application" 33 | assert_html "

Hello User

" 34 | end 35 | 36 | test "normal erb view" do 37 | get "/slim/erb" 38 | assert_html "

Hello Erb!

" 39 | end 40 | 41 | test "view without a layout" do 42 | get "/slim/no_layout" 43 | assert_template "slim/no_layout" 44 | assert_html "

Hello Slim without a layout!

", skip_layout: true 45 | end 46 | 47 | test "view with variables" do 48 | get "/slim/variables" 49 | assert_html "

Hello Slim with variables!

" 50 | end 51 | 52 | test "partial view" do 53 | get "/slim/partial" 54 | assert_html "

Hello Slim!

With a partial!

" 55 | end 56 | 57 | # TODO Reenable streaming test 58 | # test "streaming" do 59 | # get "/slim/streaming" 60 | # output = "2f\r\nDummy\r\nd\r\n\r\n17\r\nHeading set from a view\r\n15\r\n
\r\n53\r\n

Page content

Hello Streaming!

Hello Streaming!

\r\n14\r\n
\r\n0\r\n\r\n" 61 | # assert_equal output, @response.body 62 | # end 63 | 64 | test "render integers" do 65 | get "/slim/integers" 66 | assert_html "

1337

" 67 | end 68 | 69 | test "render thread_options" do 70 | get "/slim/thread_options", params: { attr: 'role'} 71 | assert_html '

Test

' 72 | get "/slim/thread_options", params: { attr: 'id'} # Overwriting doesn't work because of caching 73 | assert_html '

Test

' 74 | end 75 | 76 | test "content_for" do 77 | get "/slim/content_for" 78 | assert_html "

Page content

Hello Slim!

Hello Slim!

", heading: 'Heading set from a view' 79 | end 80 | 81 | test "form_for" do 82 | get "/entries/edit/1" 83 | assert_match %r{action="/entries"}, @response.body 84 | assert_match %r{}, @response.body 85 | assert_xpath '//input[@id="entry_name" and @name="entry[name]" and @type="text"]' 86 | end 87 | 88 | test "attributes" do 89 | get "/slim/attributes" 90 | assert_html "
" 91 | end 92 | 93 | test "splat" do 94 | get "/slim/splat" 95 | assert_html "
Hello
" 96 | end 97 | 98 | test "splat with delimiter" do 99 | get "/slim/splat_with_delimiter" 100 | assert_html "
Hello
" 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /test/sinatra/contest.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009 Damian Janowski and Michel Martens for Citrusbyte 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | require "rubygems" 21 | require "minitest/autorun" 22 | 23 | # Contest adds +teardown+, +test+ and +context+ as class methods, and the 24 | # instance methods +setup+ and +teardown+ now iterate on the corresponding 25 | # blocks. Note that all setup and teardown blocks must be defined with the 26 | # block syntax. Adding setup or teardown instance methods defeats the purpose 27 | # of this library. 28 | class Minitest::Test 29 | def self.setup(&block) setup_blocks << block end 30 | def self.teardown(&block) teardown_blocks << block end 31 | def self.setup_blocks() @setup_blocks ||= [] end 32 | def self.teardown_blocks() @teardown_blocks ||= [] end 33 | 34 | def setup_blocks(base = self.class) 35 | setup_blocks base.superclass if base.superclass.respond_to? :setup_blocks 36 | base.setup_blocks.each do |block| 37 | instance_eval(&block) 38 | end 39 | end 40 | 41 | def teardown_blocks(base = self.class) 42 | teardown_blocks base.superclass if base.superclass.respond_to? :teardown_blocks 43 | base.teardown_blocks.each do |block| 44 | instance_eval(&block) 45 | end 46 | end 47 | 48 | alias setup setup_blocks 49 | alias teardown teardown_blocks 50 | 51 | def self.context(*name, &block) 52 | subclass = Class.new(self) 53 | remove_tests(subclass) 54 | subclass.class_eval(&block) if block_given? 55 | const_set(context_name(name.join(" ")), subclass) 56 | end 57 | 58 | def self.test(name, &block) 59 | define_method(test_name(name), &block) 60 | end 61 | 62 | class << self 63 | alias_method :should, :test 64 | alias_method :describe, :context 65 | end 66 | 67 | private 68 | 69 | def self.context_name(name) 70 | # "Test#{sanitize_name(name).gsub(/(^| )(\w)/) { $2.upcase }}".to_sym 71 | name = "Test#{sanitize_name(name).gsub(/(^| )(\w)/) { $2.upcase }}" 72 | name.tr(" ", "_").to_sym 73 | end 74 | 75 | def self.test_name(name) 76 | name = "test_#{sanitize_name(name).gsub(/\s+/,'_')}_0" 77 | name = name.succ while method_defined? name 78 | name.to_sym 79 | end 80 | 81 | def self.sanitize_name(name) 82 | # name.gsub(/\W+/, ' ').strip 83 | name.gsub(/\W+/, ' ') 84 | end 85 | 86 | def self.remove_tests(subclass) 87 | subclass.public_instance_methods.grep(/^test_/).each do |meth| 88 | subclass.send(:undef_method, meth.to_sym) 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /test/sinatra/helper.rb: -------------------------------------------------------------------------------- 1 | if ENV['COVERAGE'] 2 | require 'simplecov' 3 | SimpleCov.start do 4 | add_filter '/test/' 5 | add_group 'sinatra-contrib', 'sinatra-contrib' 6 | add_group 'rack-protection', 'rack-protection' 7 | end 8 | end 9 | 10 | ENV['APP_ENV'] = 'test' 11 | RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE 12 | 13 | require 'rack' 14 | 15 | testdir = File.dirname(__FILE__) 16 | $LOAD_PATH.unshift testdir unless $LOAD_PATH.include?(testdir) 17 | 18 | libdir = File.dirname(File.dirname(__FILE__)) + '/lib' 19 | $LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir) 20 | 21 | require 'minitest' 22 | require 'contest' 23 | require 'rack/test' 24 | require 'sinatra' 25 | require 'sinatra/base' 26 | 27 | class Sinatra::Base 28 | include Minitest::Assertions 29 | # Allow assertions in request context 30 | def assertions 31 | @assertions ||= 0 32 | end 33 | 34 | attr_writer :assertions 35 | end 36 | 37 | class Rack::Builder 38 | def include?(middleware) 39 | @ins.any? { |m| middleware === m } 40 | end 41 | end 42 | 43 | Sinatra::Base.set :environment, :test 44 | 45 | class Minitest::Test 46 | include Rack::Test::Methods 47 | 48 | class << self 49 | alias_method :it, :test 50 | alias_method :section, :context 51 | end 52 | 53 | def self.example(desc = nil, &block) 54 | @example_count = 0 unless instance_variable_defined? :@example_count 55 | @example_count += 1 56 | it(desc || "Example #{@example_count}", &block) 57 | end 58 | 59 | alias_method :response, :last_response 60 | 61 | setup do 62 | Sinatra::Base.set :environment, :test 63 | end 64 | 65 | # Sets up a Sinatra::Base subclass defined with the block 66 | # given. Used in setup or individual spec methods to establish 67 | # the application. 68 | def mock_app(base=Sinatra::Base, &block) 69 | @app = Sinatra.new(base, &block) 70 | end 71 | 72 | def app 73 | Rack::Lint.new(@app) 74 | end 75 | 76 | def slim_app(&block) 77 | mock_app do 78 | set :views, File.dirname(__FILE__) + '/views' 79 | get('/', &block) 80 | end 81 | get '/' 82 | end 83 | 84 | def body 85 | response.body.to_s 86 | end 87 | 88 | def assert_body(value) 89 | if value.respond_to? :to_str 90 | assert_equal value.lstrip.gsub(/\s*\n\s*/, ""), body.lstrip.gsub(/\s*\n\s*/, "") 91 | else 92 | assert_match value, body 93 | end 94 | end 95 | 96 | def assert_status(expected) 97 | assert_equal Integer(expected), Integer(status) 98 | end 99 | 100 | def assert_like(a,b) 101 | pattern = /id=['"][^"']*["']|\s+/ 102 | assert_equal a.strip.gsub(pattern, ""), b.strip.gsub(pattern, "") 103 | end 104 | 105 | def assert_include(str, substr) 106 | assert str.include?(substr), "expected #{str.inspect} to include #{substr.inspect}" 107 | end 108 | 109 | def options(uri, params = {}, env = {}, &block) 110 | request(uri, env.merge(:method => "OPTIONS", :params => params), &block) 111 | end 112 | 113 | def patch(uri, params = {}, env = {}, &block) 114 | request(uri, env.merge(:method => "PATCH", :params => params), &block) 115 | end 116 | 117 | def link(uri, params = {}, env = {}, &block) 118 | request(uri, env.merge(:method => "LINK", :params => params), &block) 119 | end 120 | 121 | def unlink(uri, params = {}, env = {}, &block) 122 | request(uri, env.merge(:method => "UNLINK", :params => params), &block) 123 | end 124 | 125 | # Delegate other missing methods to response. 126 | def method_missing(name, *args, &block) 127 | if response && response.respond_to?(name) 128 | response.send(name, *args, &block) 129 | else 130 | super 131 | end 132 | rescue Rack::Test::Error 133 | super 134 | end 135 | 136 | # Do not output warnings for the duration of the block. 137 | def silence_warnings 138 | $VERBOSE, v = nil, $VERBOSE 139 | yield 140 | ensure 141 | $VERBOSE = v 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /test/sinatra/test_core.rb: -------------------------------------------------------------------------------- 1 | require_relative 'helper.rb' 2 | 3 | begin 4 | class SlimTest < Minitest::Test 5 | it 'renders inline slim strings' do 6 | slim_app { slim "h1 Hiya\n" } 7 | assert ok? 8 | assert_equal "

Hiya

", body 9 | end 10 | 11 | it 'renders .slim files in views path' do 12 | slim_app { slim :hello } 13 | assert ok? 14 | assert_equal "

Hello From Slim

", body 15 | end 16 | 17 | it "renders with inline layouts" do 18 | mock_app do 19 | layout { %(h1\n | THIS. IS. \n == yield.upcase ) } 20 | get('/') { slim 'em Sparta' } 21 | end 22 | get '/' 23 | assert ok? 24 | assert_equal "

THIS. IS. SPARTA

", body 25 | end 26 | 27 | it "renders with file layouts" do 28 | slim_app { slim('| Hello World', :layout => :layout2) } 29 | assert ok? 30 | assert_equal "

Slim Layout!

Hello World

", body 31 | end 32 | 33 | it "raises error if template not found" do 34 | mock_app { get('/') { slim(:no_such_template) } } 35 | assert_raises(Errno::ENOENT) { get('/') } 36 | end 37 | 38 | HTML4_DOCTYPE = "" 39 | 40 | it "passes slim options to the slim engine" do 41 | mock_app { get('/') { slim("x foo='bar'", :attr_quote => "'") }} 42 | get '/' 43 | assert ok? 44 | assert_body "" 45 | end 46 | 47 | it "passes default slim options to the slim engine" do 48 | mock_app do 49 | set :slim, :attr_quote => "'" 50 | get('/') { slim("x foo='bar'") } 51 | end 52 | get '/' 53 | assert ok? 54 | assert_body "" 55 | end 56 | 57 | it "merges the default slim options with the overrides and passes them to the slim engine" do 58 | mock_app do 59 | set :slim, :attr_quote => "'" 60 | get('/') { slim("x foo='bar'") } 61 | get('/other') { slim("x foo='bar'", :attr_quote => '"') } 62 | end 63 | get '/' 64 | assert ok? 65 | assert_body "" 66 | get '/other' 67 | assert ok? 68 | assert_body '' 69 | end 70 | 71 | it "can render truly nested layouts by accepting a layout and a block with the contents" do 72 | mock_app do 73 | template(:main_outer_layout) { "h1 Title\n== yield" } 74 | template(:an_inner_layout) { "h2 Subtitle\n== yield" } 75 | template(:a_page) { "p Contents." } 76 | get('/') do 77 | slim :main_outer_layout, :layout => false do 78 | slim :an_inner_layout do 79 | slim :a_page 80 | end 81 | end 82 | end 83 | end 84 | get '/' 85 | assert ok? 86 | assert_body "

Title

\n

Subtitle

\n

Contents.

\n" 87 | end 88 | end 89 | rescue LoadError 90 | warn "#{$!}: skipping slim tests" 91 | end 92 | -------------------------------------------------------------------------------- /test/sinatra/test_include.rb: -------------------------------------------------------------------------------- 1 | require 'slim/include' 2 | 3 | require_relative 'helper.rb' 4 | 5 | begin 6 | class SlimTest < Minitest::Test 7 | it 'renders .slim files includes with js embed' do 8 | slim_app { slim :embed_include_js } 9 | assert ok? 10 | assert_equal "Slim Examples
Slim
", body 11 | end 12 | 13 | end 14 | rescue LoadError 15 | warn "#{$!}: skipping slim tests" 16 | end 17 | -------------------------------------------------------------------------------- /test/sinatra/views/embed_include_js.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title Slim Examples 5 | javascript: 6 | alert('Slim supports embedded javascript!') 7 | body 8 | include footer.slim 9 | -------------------------------------------------------------------------------- /test/sinatra/views/embed_js.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title Slim Examples 5 | javascript: 6 | alert('Slim supports embedded javascript!') 7 | body 8 | h1 Markup examples 9 | 10 | #content 11 | p This example shows you how a basic Slim file looks. 12 | -------------------------------------------------------------------------------- /test/sinatra/views/footer.slim: -------------------------------------------------------------------------------- 1 | footer Slim 2 | -------------------------------------------------------------------------------- /test/sinatra/views/hello.slim: -------------------------------------------------------------------------------- 1 | h1 Hello From Slim 2 | -------------------------------------------------------------------------------- /test/sinatra/views/layout2.slim: -------------------------------------------------------------------------------- 1 | h1 Slim Layout! 2 | p 3 | == yield 4 | -------------------------------------------------------------------------------- /test/smart/test_smart_text.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | require 'slim/smart' 3 | 4 | class TestSlimSmartText < TestSlim 5 | 6 | def test_explicit_smart_text_recognition 7 | source = %q{ 8 | > 9 | a 10 | > 11 | b 12 | 13 | > 14 | 15 | c 16 | > 17 | 18 | d 19 | 20 | > e 21 | f 22 | > g 23 | h 24 | i 25 | } 26 | 27 | result = %q{a 28 | b 29 | c 30 | d 31 | e 32 | 33 | g 34 | h 35 | } 36 | 37 | assert_html result, source 38 | end 39 | 40 | def test_implicit_smart_text_recognition 41 | source = %q{ 42 | p 43 | A 44 | p 45 | B 46 | 47 | p 48 | 49 | C 50 | p 51 | 52 | D 53 | 54 | p E 55 | F 56 | p G 57 | H 58 | I 59 | } 60 | 61 | result = %q{

A

B

C

D

E

62 | F 63 |

G 64 | H

65 | I} 66 | 67 | assert_html result, source 68 | end 69 | 70 | def test_multi_line_smart_text 71 | source = %q{ 72 | p 73 | First line. 74 | Second line. 75 | Third line 76 | with a continuation 77 | and one more. 78 | Fourth line. 79 | } 80 | 81 | result = %q{

First line. 82 | Second line. 83 | Third line 84 | with a continuation 85 | and one more. 86 | Fourth line.

} 87 | 88 | assert_html result, source 89 | end 90 | 91 | def test_smart_text_escaping 92 | source = %q{ 93 | | Not escaped <&>. 94 | p Escaped <&>. 95 | p 96 | Escaped <&>. 97 | > Escaped <&>. 98 | Protected & < > © Á. 99 | Protected  ÿ. 100 | Escaped &#xx; f; &9; &_; &;. 101 | } 102 | 103 | result = %q{Not escaped <&>.

Escaped <&>.

Escaped <&>. 104 | Escaped <&>. 105 | Protected & < > © Á. 106 | Protected  ÿ. 107 | Escaped &#xx; &#1f; &9; &_; &;.

} 108 | 109 | assert_html result, source 110 | end 111 | 112 | def test_smart_text_disabled_escaping 113 | Slim::Engine.with_options( smart_text_escaping: false ) do 114 | source = %q{ 115 | p Not escaped <&>. 116 | | Not escaped <&>. 117 | p 118 | Not escaped <&>. 119 | > Not escaped <&>. 120 | Not escaped & < > © Á. 121 | Not escaped  ÿ. 122 | Not escaped &#xx; f; &9; &_; &;. 123 | } 124 | 125 | result = %q{

Not escaped <&>.

Not escaped <&>.

Not escaped <&>. 126 | Not escaped <&>. 127 | Not escaped & < > © Á. 128 | Not escaped  ÿ. 129 | Not escaped &#xx; f; &9; &_; &;.

} 130 | 131 | assert_html result, source 132 | end 133 | end 134 | 135 | def test_smart_text_in_tag_escaping 136 | source = %q{ 137 | p Escaped <&>. 138 | Protected & < > © Á. 139 | Protected  ÿ. 140 | Escaped &#xx; f; &9; &_; &;. 141 | } 142 | 143 | result = %q{

Escaped <&>. 144 | Protected & < > © Á. 145 | Protected  ÿ. 146 | Escaped &#xx; &#1f; &9; &_; &;.

} 147 | 148 | assert_html result, source 149 | end 150 | 151 | def test_smart_text_mixed_with_tags 152 | source = %q{ 153 | p 154 | Text 155 | br 156 | >is 157 | strong really 158 | > recognized. 159 | 160 | More 161 | b text 162 | . 163 | 164 | And 165 | i more 166 | ... 167 | 168 | span Really 169 | ?!? 170 | 171 | .bold Really 172 | !!! 173 | 174 | #id 175 | #{'Good'} 176 | ! 177 | } 178 | 179 | result = %q{

Text 180 |
181 | is 182 | really 183 | recognized. 184 | More 185 | text. 186 | And 187 | more... 188 | Really?!? 189 |

Really
!!! 190 |
Good
!

} 191 | 192 | assert_html result, source 193 | end 194 | 195 | def test_smart_text_mixed_with_links 196 | source = %q{ 197 | p 198 | Text with 199 | a href="#1" link 200 | . 201 | 202 | Text with 203 | a href="#2" another 204 | link 205 | > to somewhere else. 206 | 207 | a href="#3" 208 | This link 209 | > goes 210 | elsewhere. 211 | 212 | See ( 213 | a href="#4" link 214 | )? 215 | } 216 | 217 | result = %q{

Text with 218 | link. 219 | Text with 220 | another 221 | link 222 | to somewhere else. 223 | This link 224 | goes 225 | elsewhere. 226 | See (link)?

} 227 | 228 | assert_html result, source 229 | end 230 | 231 | def test_smart_text_mixed_with_code 232 | source = %q{ 233 | p 234 | Try a list 235 | ul 236 | - 2.times do |i| 237 | li 238 | Item: #{i} 239 | > which stops 240 | b here 241 | . Right? 242 | } 243 | 244 | result = %q{

Try a list 245 |

  • Item: 0
  • Item: 1
246 | which stops 247 | here. Right?

} 248 | 249 | assert_html result, source 250 | end 251 | 252 | # Without unicode support, we can't distinguish uppercase and lowercase 253 | # unicode characters reliably. So we only test the basic text, not tag names. 254 | def test_basic_unicode_smart_text 255 | source = %q{ 256 | p 257 | 是 258 | čip 259 | Čip 260 | Žůžo 261 | šíp 262 | } 263 | 264 | result = %q{

是 265 | čip 266 | Čip 267 | Žůžo 268 | šíp

} 269 | 270 | assert_html result, source 271 | end 272 | 273 | def test_unicode_smart_text 274 | source = %q{ 275 | p 276 | 是 277 | čip 278 | Čip 279 | Žůžo 280 | šíp 281 | .řek 282 | . 283 | } 284 | 285 | result = %q{

是 286 | čip 287 | Čip 288 | Žůžo 289 | šíp 290 |

.

} 291 | 292 | assert_html result, source 293 | end 294 | end 295 | -------------------------------------------------------------------------------- /test/translator/test_translator.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | require 'slim/translator' 3 | 4 | class TestSlimTranslator < TestSlim 5 | def setup 6 | super 7 | Slim::Engine.set_options tr: true, tr_fn: 'TestSlimTranslator.tr' 8 | end 9 | 10 | def self.tr(s) 11 | s.upcase 12 | end 13 | 14 | def self.tr_reverse(s) 15 | s.reverse.gsub(/(\d+)%/, '%\1') 16 | end 17 | 18 | def test_no_translation_of_embedded 19 | source = %q{ 20 | markdown: 21 | #Header 22 | Hello from #{"Markdown!"} 23 | 24 | #{1+2} 25 | 26 | * one 27 | * two 28 | } 29 | 30 | case Tilt['md'].name.downcase 31 | when /redcarpet/ 32 | assert_html "

Header

\n\n

Hello from Markdown!

\n\n

3

\n\n
    \n
  • one
  • \n
  • two
  • \n
\n", source, tr_mode: :dynamic 33 | assert_html "

Header

\n\n

Hello from Markdown!

\n\n

3

\n\n
    \n
  • one
  • \n
  • two
  • \n
\n", source, tr_mode: :static 34 | when /rdiscount/ 35 | assert_html "

Header

\n\n

Hello from Markdown!

\n\n

3

\n\n
    \n
  • one
  • \n
  • two
  • \n
\n\n", source, tr_mode: :dynamic 36 | assert_html "

Header

\n\n

Hello from Markdown!

\n\n

3

\n\n
    \n
  • one
  • \n
  • two
  • \n
\n\n", source, tr_mode: :static 37 | when /kramdown/ 38 | assert_html "

Header

\n

Hello from Markdown!

\n\n

3

\n\n
    \n
  • one
  • \n
  • two
  • \n
\n", source, tr_mode: :dynamic 39 | assert_html "

Header

\n

Hello from Markdown!

\n\n

3

\n\n
    \n
  • one
  • \n
  • two
  • \n
\n", source, tr_mode: :static 40 | else 41 | raise "Missing test for #{Tilt['md']}" 42 | end 43 | end 44 | 45 | def test_no_translation_of_attrs 46 | source = %q{ 47 | ' this is 48 | a link to 49 | a href="link" page 50 | } 51 | 52 | assert_html "THIS IS\nA LINK TO PAGE", source, tr_mode: :dynamic 53 | assert_html "THIS IS\nA LINK TO PAGE", source, tr_mode: :static 54 | end 55 | 56 | def test_translation_and_interpolation 57 | source = %q{ 58 | p translate #{hello_world} this 59 | second line 60 | third #{1+2} line 61 | } 62 | 63 | assert_html "

translate Hello World from @env this\nsecond line\nthird 3 line

", source, tr: false 64 | assert_html "

TRANSLATE Hello World from @env THIS\nSECOND LINE\nTHIRD 3 LINE

", source, tr_mode: :dynamic 65 | assert_html "

TRANSLATE Hello World from @env THIS\nSECOND LINE\nTHIRD 3 LINE

", source, tr_mode: :static 66 | end 67 | 68 | def test_translation_reverse 69 | source = %q{ 70 | ' alpha #{1} beta #{2} gamma #{3} 71 | } 72 | 73 | assert_html "3 ammag 2 ateb 1 ahpla ", source, tr_mode: :dynamic, tr_fn: 'TestSlimTranslator.tr_reverse' 74 | assert_html "3 ammag 2 ateb 1 ahpla ", source, tr_mode: :static, tr_fn: 'TestSlimTranslator.tr_reverse' 75 | end 76 | end 77 | --------------------------------------------------------------------------------