jekyll-minibundle 2.1.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +96 -42
- data/LICENSE.txt +1 -1
- data/README.md +232 -52
- data/Rakefile +16 -0
- data/jekyll-minibundle.gemspec +7 -6
- data/lib/jekyll/minibundle/asset_bundle.rb +3 -3
- data/lib/jekyll/minibundle/asset_file_drop.rb +45 -0
- data/lib/jekyll/minibundle/asset_file_properties.rb +12 -8
- data/lib/jekyll/minibundle/asset_file_registry.rb +4 -4
- data/lib/jekyll/minibundle/asset_tag_markup.rb +21 -20
- data/lib/jekyll/minibundle/bundle_file.rb +6 -1
- data/lib/jekyll/minibundle/development_file.rb +2 -1
- data/lib/jekyll/minibundle/development_file_collection.rb +2 -2
- data/lib/jekyll/minibundle/environment.rb +13 -15
- data/lib/jekyll/minibundle/files.rb +16 -18
- data/lib/jekyll/minibundle/hashes.rb +10 -12
- data/lib/jekyll/minibundle/log.rb +5 -7
- data/lib/jekyll/minibundle/mini_bundle_block.rb +35 -12
- data/lib/jekyll/minibundle/mini_stamp_tag.rb +108 -16
- data/lib/jekyll/minibundle/stamp_file.rb +1 -0
- data/lib/jekyll/minibundle/variable_template.rb +145 -0
- data/lib/jekyll/minibundle/variable_template_registry.rb +19 -0
- data/lib/jekyll/minibundle/version.rb +1 -1
- data/test/fixture/site/_bin/with_count +1 -1
- data/test/integration/minibundle_development_mode_test.rb +146 -61
- data/test/integration/minibundle_production_mode_test.rb +271 -143
- data/test/integration/ministamp_development_mode_test.rb +66 -25
- data/test/integration/ministamp_production_mode_test.rb +129 -37
- data/test/integration/static_files_as_asset_sources_test.rb +10 -10
- data/test/support/assertions.rb +1 -1
- data/test/support/{static_file_api_config.rb → static_file_config.rb} +6 -3
- data/test/support/test_case.rb +7 -4
- data/test/unit/asset_bundle_test.rb +6 -6
- data/test/unit/asset_file_drop_test.rb +65 -0
- data/test/unit/asset_file_registry_test.rb +136 -98
- data/test/unit/asset_tag_markup_test.rb +11 -5
- data/test/unit/bundle_file_properties_test.rb +44 -23
- data/test/unit/bundle_file_writing_test.rb +50 -32
- data/test/unit/development_file_properties_test.rb +95 -0
- data/test/unit/development_file_writing_test.rb +15 -6
- data/test/unit/environment_test.rb +3 -3
- data/test/unit/files_test.rb +7 -7
- data/test/unit/hashes_test.rb +12 -12
- data/test/unit/jekyll_static_file_api_test.rb +91 -20
- data/test/unit/mini_bundle_block_test.rb +59 -9
- data/test/unit/mini_stamp_tag_test.rb +37 -6
- data/test/unit/stamp_file_properties_test.rb +47 -24
- data/test/unit/stamp_file_writing_test.rb +33 -24
- data/test/unit/variable_template_test.rb +121 -0
- metadata +27 -6
- data/test/unit/development_file_collection_properties_test.rb +0 -106
@@ -3,36 +3,42 @@ require 'jekyll/minibundle/asset_tag_markup'
|
|
3
3
|
|
4
4
|
module Jekyll::Minibundle::Test
|
5
5
|
class AssetTagMarkupTest < TestCase
|
6
|
+
def test_escape_url
|
7
|
+
actual = AssetTagMarkup.make_markup(:css, 'https://example.com/path?a=this+"thing"&b=<br>', {})
|
8
|
+
expected = %{<link rel="stylesheet" href="https://example.com/path?a=this+"thing"&b=<br>">}
|
9
|
+
assert_equal(expected, actual)
|
10
|
+
end
|
11
|
+
|
6
12
|
def test_escape_attribute_value
|
7
13
|
attributes = {media: 'screen, projection', extra: '">attack<br'}
|
8
14
|
actual = AssetTagMarkup.make_markup(:css, '/asset.css', attributes)
|
9
15
|
expected = %{<link rel="stylesheet" href="/asset.css" media="screen, projection" extra="">attack<br">}
|
10
|
-
assert_equal
|
16
|
+
assert_equal(expected, actual)
|
11
17
|
end
|
12
18
|
|
13
19
|
def test_output_just_attribute_name_for_nil_value
|
14
20
|
actual = AssetTagMarkup.make_markup(:css, '/asset.css', async: nil)
|
15
21
|
expected = %{<link rel="stylesheet" href="/asset.css" async>}
|
16
|
-
assert_equal
|
22
|
+
assert_equal(expected, actual)
|
17
23
|
end
|
18
24
|
|
19
25
|
def test_convert_attribute_value_to_string
|
20
26
|
actual = AssetTagMarkup.make_markup(:css, '/asset.css', boolean: false)
|
21
27
|
expected = %{<link rel="stylesheet" href="/asset.css" boolean="false">}
|
22
|
-
assert_equal
|
28
|
+
assert_equal(expected, actual)
|
23
29
|
end
|
24
30
|
|
25
31
|
def test_output_empty_attribute_value
|
26
32
|
actual = AssetTagMarkup.make_markup(:css, '/asset.css', empty: '')
|
27
33
|
expected = %{<link rel="stylesheet" href="/asset.css" empty="">}
|
28
|
-
assert_equal
|
34
|
+
assert_equal(expected, actual)
|
29
35
|
end
|
30
36
|
|
31
37
|
def test_raise_exception_if_unknown_type
|
32
38
|
err = assert_raises(ArgumentError) do
|
33
39
|
AssetTagMarkup.make_markup(:unknown, '/asset', {})
|
34
40
|
end
|
35
|
-
assert_equal
|
41
|
+
assert_equal('Unknown type for generating bundle markup: unknown, /asset', err.to_s)
|
36
42
|
end
|
37
43
|
end
|
38
44
|
end
|
@@ -1,48 +1,54 @@
|
|
1
1
|
require 'support/test_case'
|
2
2
|
require 'support/fixture_config'
|
3
|
-
require 'support/
|
3
|
+
require 'support/static_file_config'
|
4
4
|
require 'jekyll/minibundle/bundle_file'
|
5
5
|
|
6
6
|
module Jekyll::Minibundle::Test
|
7
7
|
class BundleFilePropertiesTest < TestCase
|
8
8
|
include FixtureConfig
|
9
|
-
include
|
9
|
+
include StaticFileConfig
|
10
10
|
|
11
11
|
def setup
|
12
|
-
@@results ||=
|
13
|
-
file
|
14
|
-
capture_io { file.destination_path_for_markup }
|
15
|
-
get_send_results(file, STATIC_FILE_API_PROPERTIES)
|
12
|
+
@@results ||= with_bundle_file do |file|
|
13
|
+
get_send_results(file, STATIC_FILE_PROPERTIES)
|
16
14
|
end
|
17
15
|
end
|
18
16
|
|
17
|
+
def test_basename
|
18
|
+
assert_equal("site-#{JS_BUNDLE_FINGERPRINT}", @@results.fetch(:basename))
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_data
|
22
|
+
assert_equal({}, @@results.fetch(:data))
|
23
|
+
end
|
24
|
+
|
19
25
|
def test_defaults
|
20
26
|
assert_equal({}, @@results.fetch(:defaults))
|
21
27
|
end
|
22
28
|
|
23
29
|
def test_destination_rel_dir
|
24
|
-
assert_equal
|
25
|
-
end
|
26
|
-
|
27
|
-
def test_name
|
28
|
-
assert_equal "site-#{JS_BUNDLE_FINGERPRINT}.js", @@results.fetch(:name)
|
30
|
+
assert_equal('/assets', @@results.fetch(:destination_rel_dir))
|
29
31
|
end
|
30
32
|
|
31
33
|
def test_extname
|
32
|
-
assert_equal
|
34
|
+
assert_equal('.js', @@results.fetch(:extname))
|
33
35
|
end
|
34
36
|
|
35
37
|
def test_modified_time
|
36
|
-
assert_instance_of
|
38
|
+
assert_instance_of(Time, @@results.fetch(:modified_time))
|
37
39
|
end
|
38
40
|
|
39
41
|
def test_mtime
|
40
42
|
mtime = @@results.fetch(:modified_time)
|
41
|
-
assert_equal
|
43
|
+
assert_equal(mtime.to_i, @@results.fetch(:mtime))
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_name
|
47
|
+
assert_equal("site-#{JS_BUNDLE_FINGERPRINT}.js", @@results.fetch(:name))
|
42
48
|
end
|
43
49
|
|
44
50
|
def test_path
|
45
|
-
assert_match(%r{
|
51
|
+
assert_match(%r{\A/.+/jekyll-minibundle-.+\.js\z}, @@results.fetch(:path))
|
46
52
|
end
|
47
53
|
|
48
54
|
def test_placeholders
|
@@ -54,24 +60,39 @@ module Jekyll::Minibundle::Test
|
|
54
60
|
end
|
55
61
|
|
56
62
|
def test_to_liquid
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
+
with_bundle_file do |file|
|
64
|
+
drop = file.to_liquid
|
65
|
+
assert_equal("site-#{JS_BUNDLE_FINGERPRINT}.js", drop.name)
|
66
|
+
assert_equal('.js', drop.extname)
|
67
|
+
assert_equal("site-#{JS_BUNDLE_FINGERPRINT}", drop.basename)
|
68
|
+
assert_instance_of(Time, drop.modified_time)
|
69
|
+
assert_match(%r{/jekyll-minibundle-.+\.js\z}, drop.path)
|
70
|
+
assert_nil(drop.collection)
|
71
|
+
end
|
63
72
|
end
|
64
73
|
|
65
74
|
def test_type
|
66
|
-
assert_nil
|
75
|
+
assert_nil(@@results.fetch(:type))
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_url
|
79
|
+
assert_equal(JS_BUNDLE_DESTINATION_FINGERPRINT_PATH, @@results.fetch(:url))
|
67
80
|
end
|
68
81
|
|
69
82
|
def test_write?
|
70
|
-
assert
|
83
|
+
assert(@@results.fetch(:write?))
|
71
84
|
end
|
72
85
|
|
73
86
|
private
|
74
87
|
|
88
|
+
def with_bundle_file(&block)
|
89
|
+
with_fake_site do |site|
|
90
|
+
file = BundleFile.new(site, bundle_config(minifier_cmd_to_remove_comments))
|
91
|
+
capture_io { file.destination_path_for_markup }
|
92
|
+
block.call(file)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
75
96
|
def bundle_config(minifier_cmd)
|
76
97
|
{
|
77
98
|
'type' => :js,
|
@@ -6,6 +6,24 @@ module Jekyll::Minibundle::Test
|
|
6
6
|
class BundleFileWritingTest < TestCase
|
7
7
|
include FixtureConfig
|
8
8
|
|
9
|
+
def test_raise_error_if_source_directory_does_not_exist
|
10
|
+
err = assert_raises(ArgumentError) do
|
11
|
+
with_fake_site do |site|
|
12
|
+
make_bundle_file(site, 'source_dir' => STAMP_SOURCE_PATH)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
assert_match(%r{\ABundle source directory does not exist: .+/#{Regexp.escape(STAMP_SOURCE_PATH)}\z}, err.to_s)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_raise_error_if_asset_source_file_does_not_exist
|
19
|
+
err = assert_raises(ArgumentError) do
|
20
|
+
with_fake_site do |site|
|
21
|
+
make_bundle_file(site, 'assets' => %w{no-such})
|
22
|
+
end
|
23
|
+
end
|
24
|
+
assert_match(%r{\ABundle asset source file does not exist: .+/#{Regexp.escape(JS_BUNDLE_SOURCE_DIR)}/no-such.js\z}, err.to_s)
|
25
|
+
end
|
26
|
+
|
9
27
|
def test_calling_destination_path_for_markup_determines_fingerprint_and_destination_write
|
10
28
|
with_fake_site do |site|
|
11
29
|
bundle_file = make_bundle_file(site)
|
@@ -18,16 +36,16 @@ module Jekyll::Minibundle::Test
|
|
18
36
|
|
19
37
|
capture_io { org_markup_path = bundle_file.destination_path_for_markup }
|
20
38
|
|
21
|
-
assert
|
39
|
+
assert(write_file(bundle_file))
|
22
40
|
|
23
41
|
org_mtime = file_mtime_of(old_destination)
|
24
42
|
|
25
|
-
assert_equal
|
43
|
+
assert_equal(1, get_minifier_cmd_count)
|
26
44
|
|
27
45
|
last_markup_path = bundle_file.destination_path_for_markup
|
28
46
|
|
29
|
-
assert_equal
|
30
|
-
assert_equal
|
47
|
+
assert_equal(org_markup_path, last_markup_path)
|
48
|
+
assert_equal(1, get_minifier_cmd_count)
|
31
49
|
|
32
50
|
# change content, but don't call destination_path_for_markup yet
|
33
51
|
|
@@ -35,10 +53,10 @@ module Jekyll::Minibundle::Test
|
|
35
53
|
|
36
54
|
# preserve content's fingerprint
|
37
55
|
|
38
|
-
refute
|
56
|
+
refute(write_file(bundle_file))
|
39
57
|
|
40
|
-
assert_equal
|
41
|
-
assert_equal
|
58
|
+
assert_equal(org_mtime, file_mtime_of(old_destination))
|
59
|
+
assert_equal(1, get_minifier_cmd_count)
|
42
60
|
|
43
61
|
# see content's fingerprint to update after calling
|
44
62
|
# destination_path_for_markup
|
@@ -47,12 +65,12 @@ module Jekyll::Minibundle::Test
|
|
47
65
|
|
48
66
|
refute_equal org_markup_path, last_markup_path
|
49
67
|
|
50
|
-
assert
|
68
|
+
assert(write_file(bundle_file))
|
51
69
|
|
52
70
|
new_destination = destination_path('assets/site-375a0b430b0c5555d0edd2205d26c04d.js')
|
53
71
|
|
54
|
-
assert_operator
|
55
|
-
assert_equal
|
72
|
+
assert_operator(file_mtime_of(new_destination), :>, org_mtime)
|
73
|
+
assert_equal(2, get_minifier_cmd_count)
|
56
74
|
end
|
57
75
|
end
|
58
76
|
|
@@ -65,20 +83,20 @@ module Jekyll::Minibundle::Test
|
|
65
83
|
capture_io { org_markup_path = bundle_file.destination_path_for_markup }
|
66
84
|
bundle_file.destination_path_for_markup
|
67
85
|
|
68
|
-
assert
|
86
|
+
assert(write_file(bundle_file))
|
69
87
|
|
70
88
|
org_mtime = file_mtime_of(destination)
|
71
89
|
|
72
|
-
assert_equal
|
90
|
+
assert_equal(1, get_minifier_cmd_count)
|
73
91
|
|
74
92
|
ensure_file_mtime_changes { FileUtils.touch(source) }
|
75
93
|
capture_io { last_markup_path = bundle_file.destination_path_for_markup }
|
76
94
|
bundle_file.destination_path_for_markup
|
77
95
|
|
78
|
-
assert
|
79
|
-
assert_equal
|
80
|
-
assert_operator
|
81
|
-
assert_equal
|
96
|
+
assert(write_file(bundle_file))
|
97
|
+
assert_equal(org_markup_path, last_markup_path)
|
98
|
+
assert_operator(file_mtime_of(destination), :>, org_mtime)
|
99
|
+
assert_equal(2, get_minifier_cmd_count)
|
82
100
|
end
|
83
101
|
end
|
84
102
|
|
@@ -86,15 +104,15 @@ module Jekyll::Minibundle::Test
|
|
86
104
|
with_fake_site do |site|
|
87
105
|
bundle_file = make_bundle_file(site)
|
88
106
|
|
89
|
-
refute
|
90
|
-
assert_empty
|
91
|
-
assert_equal
|
107
|
+
refute(write_file(bundle_file))
|
108
|
+
assert_empty(Dir[destination_path('assets/*.js')])
|
109
|
+
assert_equal(0, get_minifier_cmd_count)
|
92
110
|
|
93
111
|
capture_io { bundle_file.destination_path_for_markup }
|
94
112
|
|
95
|
-
assert
|
96
|
-
assert
|
97
|
-
assert_equal
|
113
|
+
assert(write_file(bundle_file))
|
114
|
+
assert(File.file?(destination_path(JS_BUNDLE_DESTINATION_FINGERPRINT_PATH)))
|
115
|
+
assert_equal(1, get_minifier_cmd_count)
|
98
116
|
end
|
99
117
|
end
|
100
118
|
|
@@ -102,30 +120,30 @@ module Jekyll::Minibundle::Test
|
|
102
120
|
with_fake_site do |site|
|
103
121
|
bundle_file = make_bundle_file(site)
|
104
122
|
|
105
|
-
refute
|
106
|
-
refute
|
123
|
+
refute(bundle_file.modified?)
|
124
|
+
refute(write_file(bundle_file))
|
107
125
|
|
108
126
|
capture_io { bundle_file.destination_path_for_markup }
|
109
127
|
|
110
|
-
assert
|
111
|
-
assert
|
128
|
+
assert(bundle_file.modified?)
|
129
|
+
assert(write_file(bundle_file))
|
112
130
|
|
113
|
-
refute
|
114
|
-
refute
|
131
|
+
refute(bundle_file.modified?)
|
132
|
+
refute(write_file(bundle_file))
|
115
133
|
end
|
116
134
|
end
|
117
135
|
|
118
136
|
private
|
119
137
|
|
120
|
-
def make_bundle_file(site)
|
121
|
-
|
122
|
-
site,
|
138
|
+
def make_bundle_file(site, config = {})
|
139
|
+
bundle_config = {
|
123
140
|
'type' => :js,
|
124
141
|
'source_dir' => JS_BUNDLE_SOURCE_DIR,
|
125
142
|
'assets' => %w{dependency app},
|
126
143
|
'destination_path' => JS_BUNDLE_DESTINATION_PATH,
|
127
144
|
'minifier_cmd' => minifier_cmd_to_remove_comments_and_count
|
128
|
-
|
145
|
+
}
|
146
|
+
BundleFile.new(site, bundle_config.merge(config))
|
129
147
|
end
|
130
148
|
|
131
149
|
def write_file(file)
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'support/test_case'
|
2
|
+
require 'support/fixture_config'
|
3
|
+
require 'support/static_file_config'
|
4
|
+
require 'jekyll/minibundle/development_file'
|
5
|
+
|
6
|
+
module Jekyll::Minibundle::Test
|
7
|
+
class DevelopmentFilePropertiesTest < TestCase
|
8
|
+
include FixtureConfig
|
9
|
+
include StaticFileConfig
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@@results ||= with_development_file do |file|
|
13
|
+
get_send_results(file, STATIC_FILE_PROPERTIES)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_basename
|
18
|
+
assert_equal('screen', @@results.fetch(:basename))
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_data
|
22
|
+
assert_equal({}, @@results.fetch(:data))
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_defaults
|
26
|
+
assert_equal({}, @@results.fetch(:defaults))
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_destination_rel_dir
|
30
|
+
assert_equal('/assets', @@results.fetch(:destination_rel_dir))
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_extname
|
34
|
+
assert_equal('.css', @@results.fetch(:extname))
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_modified_time
|
38
|
+
assert_instance_of(Time, @@results.fetch(:modified_time))
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_mtime
|
42
|
+
mtime = @@results.fetch(:modified_time)
|
43
|
+
assert_equal(mtime.to_i, @@results.fetch(:mtime))
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_name
|
47
|
+
assert_equal('screen.css', @@results.fetch(:name))
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_path
|
51
|
+
assert_match(%r{\A/.+/#{STAMP_SOURCE_PATH}\z}, @@results.fetch(:path))
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_placeholders
|
55
|
+
assert_equal({}, @@results.fetch(:placeholders))
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_relative_path
|
59
|
+
assert_equal("/#{STAMP_SOURCE_PATH}", @@results.fetch(:relative_path))
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_to_liquid
|
63
|
+
with_development_file do |file|
|
64
|
+
drop = file.to_liquid
|
65
|
+
assert_equal('screen.css', drop.name)
|
66
|
+
assert_equal('.css', drop.extname)
|
67
|
+
assert_equal('screen', drop.basename)
|
68
|
+
assert_instance_of(Time, drop.modified_time)
|
69
|
+
assert_equal("/#{STAMP_SOURCE_PATH}", drop.path)
|
70
|
+
assert_nil(drop.collection)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_type
|
75
|
+
assert_nil(@@results.fetch(:type))
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_url
|
79
|
+
assert_equal(STAMP_DESTINATION_PATH, @@results.fetch(:url))
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_write?
|
83
|
+
assert(@@results.fetch(:write?))
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def with_development_file(&block)
|
89
|
+
with_fake_site do |site|
|
90
|
+
file = DevelopmentFile.new(site, STAMP_SOURCE_PATH, STAMP_DESTINATION_PATH)
|
91
|
+
block.call(file)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -6,23 +6,32 @@ module Jekyll::Minibundle::Test
|
|
6
6
|
class DevelopmentFileWritingTest < TestCase
|
7
7
|
include FixtureConfig
|
8
8
|
|
9
|
+
def test_raise_error_if_source_file_does_not_exist
|
10
|
+
err = assert_raises(ArgumentError) do
|
11
|
+
with_fake_site do |site|
|
12
|
+
DevelopmentFile.new(site, '_tmp', STAMP_DESTINATION_PATH)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
assert_match(%r{\ADevelopment source file does not exist: .+/_tmp\z}, err.to_s)
|
16
|
+
end
|
17
|
+
|
9
18
|
def test_modify_property_determines_if_write_would_succeed
|
10
19
|
with_fake_site do |site|
|
11
20
|
dev_file = make_development_file(site)
|
12
21
|
source = source_path(STAMP_SOURCE_PATH)
|
13
22
|
|
14
|
-
assert
|
15
|
-
assert
|
23
|
+
assert(dev_file.modified?)
|
24
|
+
assert(write_file(dev_file))
|
16
25
|
|
17
26
|
ensure_file_mtime_changes do
|
18
27
|
FileUtils.touch(source)
|
19
28
|
end
|
20
29
|
|
21
|
-
assert
|
22
|
-
assert
|
30
|
+
assert(dev_file.modified?)
|
31
|
+
assert(write_file(dev_file))
|
23
32
|
|
24
|
-
refute
|
25
|
-
refute
|
33
|
+
refute(dev_file.modified?)
|
34
|
+
refute(write_file(dev_file))
|
26
35
|
end
|
27
36
|
end
|
28
37
|
|