├── .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}#{tag}>"
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 '', 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 '', 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 '', 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 '', 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\nHello 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\nHello from Markdown!
\n\n3
\n\n1
\n\n\n", source
22 | elsif ::Tilt['md'].name =~ /RDiscount/
23 | # rdiscount
24 | assert_html "Header \n\nHello from Markdown!
\n\n3
\n\n1
\n\n\n\n", source
25 | else
26 | # kramdown, :auto_ids by default
27 | assert_html "\nHello from Markdown!
\n\n3
\n\n1
\n\n\n", source
28 |
29 | Slim::Embedded.with_options(markdown: {auto_ids: false}) do
30 | assert_html "Header \nHello from Markdown!
\n\n3
\n\n1
\n\n\n", source
31 | end
32 |
33 | assert_html "\nHello from Markdown!
\n\n3
\n\n1
\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 'Text Text2 ', 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 'Text Text2 ', 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 'Text Text2 ', source
83 | end
84 |
85 | def test_boolean_attribute_string2
86 | source = %{
87 | option selected="selected" Text
88 | }
89 |
90 | assert_html 'Text ', 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 'Text Text ', 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 == " "
129 | }
130 |
131 | result = %q{
132 |
133 |
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 |
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 'slim1recslim2 1+2=3 slim1recslim2 subdir ', 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 | ""
40 | end
41 | }
42 |
43 | assert_html '
Joe
Jack
', 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 'Person 1 Person 2
', 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 ' Joe Jack
', 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'Older Newer ', 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{Name }, @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 \nSubtitle \nContents.
\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 ", 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 x; f; &9; &_; &;.
101 | }
102 |
103 | result = %q{Not escaped <&>.Escaped <&>.
Escaped <&>.
104 | Escaped <&>.
105 | Protected & < > © Á.
106 | Protected ÿ.
107 | Escaped &#xx; f; &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 x; 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 x; 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 x; f; &9; &_; &;.
141 | }
142 |
143 | result = %q{Escaped <&>.
144 | Protected & < > © Á.
145 | Protected ÿ.
146 | Escaped &#xx; f; &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 |
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\nHello from Markdown!
\n\n3
\n\n\n", source, tr_mode: :dynamic
33 | assert_html "Header \n\nHello from Markdown!
\n\n3
\n\n\n", source, tr_mode: :static
34 | when /rdiscount/
35 | assert_html "Header \n\nHello from Markdown!
\n\n3
\n\n\n\n", source, tr_mode: :dynamic
36 | assert_html "Header \n\nHello from Markdown!
\n\n3
\n\n\n\n", source, tr_mode: :static
37 | when /kramdown/
38 | assert_html "\nHello from Markdown!
\n\n3
\n\n\n", source, tr_mode: :dynamic
39 | assert_html "\nHello from Markdown!
\n\n3
\n\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 |
--------------------------------------------------------------------------------