jekyll_picture_tag 1.13.0 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.envrc +4 -0
- data/.github/workflows/code-checks.yml +33 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +29 -76
- data/.ruby-version +1 -1
- data/docs/.envrc +2 -0
- data/docs/devs/contributing/code.md +14 -4
- data/docs/devs/contributing/docs.md +24 -6
- data/docs/devs/contributing/setup.md +21 -1
- data/docs/devs/contributing/testing.md +19 -37
- data/docs/devs/releases.md +45 -4
- data/docs/index.md +43 -18
- data/docs/logo.png +0 -0
- data/docs/logo.svg +880 -0
- data/docs/users/configuration/disable.md +1 -1
- data/docs/users/configuration/ignore_missing.md +1 -1
- data/docs/users/configuration/kramdown_fix.md +1 -1
- data/docs/users/configuration/suppress_warnings.md +1 -1
- data/docs/users/configuration/urls.md +69 -0
- data/docs/users/deployment.md +49 -0
- data/docs/users/getting_started.md +55 -0
- data/docs/users/installation.md +18 -38
- data/docs/users/liquid_tag/argument_reference/crop.md +21 -36
- data/docs/users/liquid_tag/examples.md +13 -25
- data/docs/users/liquid_tag/index.md +1 -1
- data/docs/users/notes/{migration.md → migration_1.md} +1 -1
- data/docs/users/notes/migration_2.md +99 -0
- data/docs/users/presets/cropping.md +21 -22
- data/docs/users/presets/default.md +11 -2
- data/docs/users/presets/examples.md +77 -45
- data/docs/users/presets/fallback_image.md +1 -1
- data/docs/users/presets/html_attributes.md +1 -1
- data/docs/users/presets/image_formats.md +3 -3
- data/docs/users/presets/image_quality.md +71 -56
- data/docs/users/presets/index.md +19 -45
- data/docs/users/presets/link_source.md +1 -1
- data/docs/users/presets/media_queries.md +1 -1
- data/docs/users/presets/nomarkdown_override.md +1 -1
- data/docs/users/presets/pixel_ratio_srcsets.md +1 -1
- data/docs/users/presets/width_height_attributes.md +1 -1
- data/docs/users/presets/width_srcsets.md +61 -23
- data/docs/users/presets/writing_presets.md +65 -0
- data/docs/users/tutorial.md +97 -0
- data/jekyll_picture_tag.gemspec +38 -23
- data/lib/jekyll_picture_tag.rb +8 -5
- data/lib/jekyll_picture_tag/cache.rb +64 -3
- data/lib/jekyll_picture_tag/defaults/global.rb +18 -0
- data/lib/jekyll_picture_tag/defaults/presets.rb +57 -0
- data/lib/jekyll_picture_tag/images.rb +1 -0
- data/lib/jekyll_picture_tag/images/generated_image.rb +25 -60
- data/lib/jekyll_picture_tag/images/image_file.rb +105 -0
- data/lib/jekyll_picture_tag/images/img_uri.rb +3 -10
- data/lib/jekyll_picture_tag/images/source_image.rb +44 -9
- data/lib/jekyll_picture_tag/instructions.rb +70 -6
- data/lib/jekyll_picture_tag/instructions/children/config.rb +128 -0
- data/lib/jekyll_picture_tag/instructions/children/context.rb +24 -0
- data/lib/jekyll_picture_tag/instructions/children/params.rb +90 -0
- data/lib/jekyll_picture_tag/instructions/children/parsers.rb +48 -0
- data/lib/jekyll_picture_tag/instructions/children/preset.rb +182 -0
- data/lib/jekyll_picture_tag/instructions/parents/conditional_instruction.rb +69 -0
- data/lib/jekyll_picture_tag/instructions/parents/env_instruction.rb +29 -0
- data/lib/jekyll_picture_tag/output_formats/basic.rb +5 -17
- data/lib/jekyll_picture_tag/parsers.rb +6 -0
- data/lib/jekyll_picture_tag/{instructions → parsers}/arg_splitter.rb +1 -1
- data/lib/jekyll_picture_tag/parsers/configuration.rb +28 -0
- data/lib/jekyll_picture_tag/{instructions → parsers}/html_attributes.rb +1 -1
- data/lib/jekyll_picture_tag/parsers/image_backend.rb +33 -0
- data/lib/jekyll_picture_tag/parsers/preset.rb +43 -0
- data/lib/jekyll_picture_tag/{instructions → parsers}/tag_parser.rb +15 -12
- data/lib/jekyll_picture_tag/router.rb +35 -93
- data/lib/jekyll_picture_tag/srcsets/basic.rb +4 -10
- data/lib/jekyll_picture_tag/utils.rb +10 -20
- data/lib/jekyll_picture_tag/version.rb +1 -1
- data/readme.md +48 -9
- metadata +161 -80
- data/.travis.yml +0 -8
- data/Dockerfile +0 -9
- data/docs/users/configuration/cdn.md +0 -35
- data/docs/users/configuration/relative_urls.md +0 -15
- data/docs/users/notes/input_checking.md +0 -6
- data/jekyll-picture-tag.gemspec +0 -52
- data/lib/jekyll-picture-tag.rb +0 -25
- data/lib/jekyll_picture_tag/cache/base.rb +0 -59
- data/lib/jekyll_picture_tag/cache/generated.rb +0 -20
- data/lib/jekyll_picture_tag/cache/source.rb +0 -19
- data/lib/jekyll_picture_tag/defaults/global.yml +0 -11
- data/lib/jekyll_picture_tag/defaults/presets.yml +0 -11
- data/lib/jekyll_picture_tag/instructions/configuration.rb +0 -121
- data/lib/jekyll_picture_tag/instructions/preset.rb +0 -122
- data/lib/jekyll_picture_tag/instructions/set.rb +0 -75
data/lib/jekyll_picture_tag.rb
CHANGED
@@ -5,9 +5,12 @@ require_relative 'jekyll_picture_tag/cache'
|
|
5
5
|
require_relative 'jekyll_picture_tag/images'
|
6
6
|
require_relative 'jekyll_picture_tag/instructions'
|
7
7
|
require_relative 'jekyll_picture_tag/output_formats'
|
8
|
+
require_relative 'jekyll_picture_tag/parsers'
|
8
9
|
require_relative 'jekyll_picture_tag/router'
|
9
10
|
require_relative 'jekyll_picture_tag/srcsets'
|
10
11
|
require_relative 'jekyll_picture_tag/utils'
|
12
|
+
require_relative 'jekyll_picture_tag/defaults/presets'
|
13
|
+
require_relative 'jekyll_picture_tag/defaults/global'
|
11
14
|
|
12
15
|
# Title: Jekyll Picture Tag
|
13
16
|
# Authors: Rob Wierzbowski : @robwierzbowski
|
@@ -60,7 +63,9 @@ module PictureTag
|
|
60
63
|
def render(context)
|
61
64
|
setup(context)
|
62
65
|
|
63
|
-
if PictureTag.disabled?
|
66
|
+
if PictureTag.disabled? || PictureTag.raw_params.empty?
|
67
|
+
Utils.warning 'You have called JPT without any arguments.'
|
68
|
+
|
64
69
|
''
|
65
70
|
else
|
66
71
|
PictureTag.output_class.new.to_s
|
@@ -70,11 +75,9 @@ module PictureTag
|
|
70
75
|
private
|
71
76
|
|
72
77
|
def setup(context)
|
78
|
+
PictureTag.clear_instructions
|
73
79
|
PictureTag.context = context
|
74
|
-
|
75
|
-
# Now that we have both the tag parameters and the context object, we can
|
76
|
-
# build our instruction set.
|
77
|
-
PictureTag.instructions = Instructions::Set.new(@raw_params)
|
80
|
+
PictureTag.raw_params = @raw_params
|
78
81
|
|
79
82
|
# We need to explicitly prevent jekyll from overwriting our generated
|
80
83
|
# image files:
|
@@ -1,3 +1,64 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module PictureTag
|
4
|
+
# Store expensive bits of information between text files. Originally cached
|
5
|
+
# width & heights of images in addition to digests, now just image digests.
|
6
|
+
class Cache
|
7
|
+
def initialize(base_name)
|
8
|
+
@base_name = base_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
data[key]
|
13
|
+
end
|
14
|
+
|
15
|
+
def []=(key, value)
|
16
|
+
raise ArgumentError unless template.keys.include? key
|
17
|
+
|
18
|
+
data[key] = value
|
19
|
+
end
|
20
|
+
|
21
|
+
# Call after updating data.
|
22
|
+
def write
|
23
|
+
return if PictureTag.config['disable_disk_cache']
|
24
|
+
|
25
|
+
FileUtils.mkdir_p(File.join(base_directory, sub_directory))
|
26
|
+
|
27
|
+
File.open(filename, 'w+') do |f|
|
28
|
+
f.write JSON.generate(data)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def data
|
35
|
+
@data ||= if File.exist?(filename)
|
36
|
+
JSON.parse(File.read(filename)).transform_keys(&:to_sym)
|
37
|
+
else
|
38
|
+
template
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# /home/dave/my_blog/.jekyll-cache/jpt/(cache_dir)/assets/myimage.jpg.json
|
43
|
+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
44
|
+
def base_directory
|
45
|
+
File.join(PictureTag.site.cache_dir, 'jpt')
|
46
|
+
end
|
47
|
+
|
48
|
+
# /home/dave/my_blog/.jekyll-cache/jpt/(cache_dir)/assets/myimage.jpg.json
|
49
|
+
# ^^^^^^^^
|
50
|
+
def sub_directory
|
51
|
+
File.dirname(@base_name)
|
52
|
+
end
|
53
|
+
|
54
|
+
# /home/dave/my_blog/.jekyll-cache/jpt/somefolder/myimage.jpg.json
|
55
|
+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
56
|
+
def filename
|
57
|
+
File.join(base_directory, @base_name + '.json')
|
58
|
+
end
|
59
|
+
|
60
|
+
def template
|
61
|
+
{ digest: nil }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module PictureTag
|
2
|
+
# Default settings for _config.yml
|
3
|
+
DEFAULT_CONFIG = {
|
4
|
+
'picture' => {
|
5
|
+
'source' => '',
|
6
|
+
'output' => 'generated',
|
7
|
+
'suppress_warnings' => false,
|
8
|
+
'relative_url' => true,
|
9
|
+
'cdn_environments' => ['production'],
|
10
|
+
'nomarkdown' => true,
|
11
|
+
'ignore_missing_images' => false,
|
12
|
+
'disabled' => false,
|
13
|
+
'fast_build' => false,
|
14
|
+
'ignore_baseurl' => false,
|
15
|
+
'baseurl_key' => 'baseurl'
|
16
|
+
}
|
17
|
+
}.freeze
|
18
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module PictureTag
|
2
|
+
DEFAULT_PRESET = { 'markup' => 'auto',
|
3
|
+
'formats' => ['original'],
|
4
|
+
'widths' => [400, 600, 800, 1000],
|
5
|
+
'fallback_width' => 800,
|
6
|
+
'fallback_format' => 'original',
|
7
|
+
'noscript' => false,
|
8
|
+
'link_source' => false,
|
9
|
+
'quality' => 75,
|
10
|
+
'format_quality' => { 'webp' => 50,
|
11
|
+
'avif' => 30,
|
12
|
+
'jp2' => 30 },
|
13
|
+
'data_sizes' => true,
|
14
|
+
'keep' => 'attention',
|
15
|
+
'dimension_attributes' => false,
|
16
|
+
'strip_metadata' => true,
|
17
|
+
'image_options' => {
|
18
|
+
'avif' => { 'compression' => 'av1', 'speed' => 8 }
|
19
|
+
} }.freeze
|
20
|
+
|
21
|
+
STOCK_PRESETS = {
|
22
|
+
'jpt-webp' => { 'formats' => %w[webp original] },
|
23
|
+
|
24
|
+
'jpt-avif' => { 'formats' => %w[avif webp original] },
|
25
|
+
|
26
|
+
'jpt-lazy' => { 'markup' => 'data_auto',
|
27
|
+
'noscript' => true,
|
28
|
+
'formats' => %w[webp original],
|
29
|
+
'attributes' => { 'parent' => 'class="lazy"' } },
|
30
|
+
|
31
|
+
'jpt-loaded' => { 'formats' => %w[avif jp2 webp original],
|
32
|
+
'dimension_attributes' => true },
|
33
|
+
|
34
|
+
'jpt-direct' => { 'markup' => 'direct_url',
|
35
|
+
'fallback_format' => 'webp',
|
36
|
+
'fallback_width' => 600 },
|
37
|
+
|
38
|
+
'jpt-thumbnail' => { 'base_width' => 250,
|
39
|
+
'pixel_ratios' => [1, 1.5, 2],
|
40
|
+
'formats' => %w[webp original],
|
41
|
+
'fallback_width' => 250,
|
42
|
+
'attributes' => { 'picture' => 'class="icon"' } },
|
43
|
+
|
44
|
+
'jpt-avatar' => { 'base_width' => 100,
|
45
|
+
'pixel_ratios' => [1, 1.5, 2],
|
46
|
+
'fallback_width' => 100,
|
47
|
+
'crop' => '1:1' }
|
48
|
+
}.freeze
|
49
|
+
|
50
|
+
STOCK_MEDIA_QUERIES = {
|
51
|
+
'jpt-mobile' => 'max-width: 480px',
|
52
|
+
'jpt-tablet' => 'max-width: 768',
|
53
|
+
'jpt-laptop' => 'max-width: 1024px',
|
54
|
+
'jpt-desktop' => 'max-width: 1200',
|
55
|
+
'jpt-wide' => 'min-width: 1201'
|
56
|
+
}.freeze
|
57
|
+
end
|
@@ -1,18 +1,20 @@
|
|
1
|
-
require '
|
1
|
+
require 'ruby-vips'
|
2
2
|
|
3
3
|
module PictureTag
|
4
|
-
# Represents a generated image file.
|
4
|
+
# Represents a generated image, but not the file itself. Its purpose is to
|
5
|
+
# make its properties available for query, and hand them off to the ImageFile
|
6
|
+
# class for generation.
|
5
7
|
class GeneratedImage
|
6
|
-
attr_reader :width
|
8
|
+
attr_reader :width
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
def initialize(source_file:, width:, format:, crop: nil, gravity: '')
|
10
|
+
def initialize(source_file:, width:, format:)
|
11
11
|
@source = source_file
|
12
12
|
@width = width
|
13
|
-
@
|
14
|
-
|
15
|
-
|
13
|
+
@raw_format = format
|
14
|
+
end
|
15
|
+
|
16
|
+
def format
|
17
|
+
@format ||= process_format(@raw_format)
|
16
18
|
end
|
17
19
|
|
18
20
|
def exists?
|
@@ -43,77 +45,40 @@ module PictureTag
|
|
43
45
|
|
44
46
|
# Post crop
|
45
47
|
def source_width
|
46
|
-
|
47
|
-
|
48
|
-
cache[:width]
|
48
|
+
image.width
|
49
49
|
end
|
50
50
|
|
51
51
|
# Post crop
|
52
52
|
def source_height
|
53
|
-
|
54
|
-
|
55
|
-
cache[:height]
|
53
|
+
image.height
|
56
54
|
end
|
57
55
|
|
58
|
-
|
59
|
-
|
60
|
-
# We exclude width and format from the cache name, since it isn't specific to them.
|
61
|
-
def cache
|
62
|
-
@cache ||= Cache::Generated.new("#{@source.base_name}-#{id}")
|
56
|
+
def quality
|
57
|
+
PictureTag.quality(format, width)
|
63
58
|
end
|
64
59
|
|
65
|
-
|
66
|
-
return if @source.missing
|
67
|
-
|
68
|
-
# Ensure it's generated:
|
69
|
-
image
|
70
|
-
|
71
|
-
cache[:width] = @source_dimensions[:width]
|
72
|
-
cache[:height] = @source_dimensions[:height]
|
73
|
-
|
74
|
-
cache.write
|
75
|
-
end
|
60
|
+
private
|
76
61
|
|
77
|
-
# Hash all inputs and truncate, so we know when they change without getting
|
62
|
+
# Hash all inputs and truncate, so we know when they change without getting
|
63
|
+
# too long.
|
78
64
|
# /home/dave/my_blog/_site/generated/somefolder/myimage-100-1234abcde.jpg
|
79
65
|
# ^^^^^^^^^
|
80
66
|
def id
|
81
|
-
@id ||= Digest::MD5.hexdigest(
|
67
|
+
@id ||= Digest::MD5.hexdigest(settings.join)[0..8]
|
82
68
|
end
|
83
69
|
|
84
|
-
|
85
|
-
|
86
|
-
@image ||= open_image
|
70
|
+
def settings
|
71
|
+
[@source.digest, @source.crop, @source.keep, quality]
|
87
72
|
end
|
88
73
|
|
89
|
-
def
|
90
|
-
|
91
|
-
image_base.combine_options do |i|
|
92
|
-
i.auto_orient
|
93
|
-
if @crop
|
94
|
-
i.gravity @gravity
|
95
|
-
i.crop @crop
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
@source_dimensions = { width: image_base.width, height: image_base.height }
|
100
|
-
|
101
|
-
image_base
|
74
|
+
def image
|
75
|
+
@image ||= Vips::Image.new_from_file @source.name
|
102
76
|
end
|
103
77
|
|
104
78
|
def generate_image
|
105
|
-
|
106
|
-
|
107
|
-
image.format(@format, 0, { resize: "#{@width}x", quality: quality })
|
108
|
-
FileUtils.mkdir_p(File.dirname(absolute_filename))
|
109
|
-
|
110
|
-
image.write absolute_filename
|
111
|
-
|
112
|
-
FileUtils.chmod(0o644, absolute_filename)
|
113
|
-
end
|
79
|
+
return if @source.missing
|
114
80
|
|
115
|
-
|
116
|
-
PictureTag.quality(@format, @width)
|
81
|
+
ImageFile.new(@source, self)
|
117
82
|
end
|
118
83
|
|
119
84
|
def process_format(format)
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module PictureTag
|
2
|
+
# Basically a wrapper class for vips. Handles image operations.
|
3
|
+
# Vips returns new images for Crop, resize, and autorotate operations.
|
4
|
+
# Quality, metadata stripping, and format are applied on write.
|
5
|
+
#
|
6
|
+
# This deserves to be two classes and a factory, one for normal vips save and
|
7
|
+
# one for magicksave. This is illustrated by the fact that stubbing backend
|
8
|
+
# determination logic for its unit tests would basically require
|
9
|
+
# re-implementing it completely.
|
10
|
+
#
|
11
|
+
# I'm planning to implement standalone imagemagick as an alternative to vips,
|
12
|
+
# so when I add that I'll also do that refactoring. For now it works fine and
|
13
|
+
# it's not too bloated.
|
14
|
+
class ImageFile
|
15
|
+
def initialize(source, base)
|
16
|
+
@source = source
|
17
|
+
@base = base
|
18
|
+
|
19
|
+
build
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :source, :base
|
25
|
+
|
26
|
+
def build
|
27
|
+
notify
|
28
|
+
|
29
|
+
mkdir
|
30
|
+
|
31
|
+
image = load_image
|
32
|
+
|
33
|
+
image = process(image)
|
34
|
+
|
35
|
+
write(image)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Processing pipeline
|
39
|
+
def process(image)
|
40
|
+
image = crop(image) if source.crop?
|
41
|
+
|
42
|
+
image = resize(image)
|
43
|
+
|
44
|
+
image.autorot
|
45
|
+
end
|
46
|
+
|
47
|
+
def handler
|
48
|
+
PictureTag.backend.handler_for(@base.format)
|
49
|
+
end
|
50
|
+
|
51
|
+
def quality_key
|
52
|
+
handler == :vips ? :Q : :quality
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_opts
|
56
|
+
opts = PictureTag.preset['image_options'][@base.format] || {}
|
57
|
+
|
58
|
+
opts[:strip] = PictureTag.preset['strip_metadata']
|
59
|
+
|
60
|
+
# gifs don't accept a quality setting, and PNGs don't on older versions of
|
61
|
+
# vips. Since it's not remarkably useful anyway, we'll ignore them.
|
62
|
+
opts[quality_key] = base.quality unless %w[gif png].include? base.format
|
63
|
+
|
64
|
+
opts.transform_keys(&:to_sym)
|
65
|
+
end
|
66
|
+
|
67
|
+
def load_image
|
68
|
+
Vips::Image.new_from_file source.name
|
69
|
+
end
|
70
|
+
|
71
|
+
def write(image)
|
72
|
+
case handler
|
73
|
+
when :vips
|
74
|
+
image.write_to_file(base.absolute_filename, **write_opts)
|
75
|
+
# If vips can't handle it, fall back to imagemagick.
|
76
|
+
when :magick
|
77
|
+
image.magicksave(base.absolute_filename, **write_opts)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Fix permissions. TODO - still necessary?
|
81
|
+
FileUtils.chmod(0o644, base.absolute_filename)
|
82
|
+
end
|
83
|
+
|
84
|
+
def notify
|
85
|
+
puts 'Generating new image file: ' + base.name
|
86
|
+
end
|
87
|
+
|
88
|
+
def resize(image)
|
89
|
+
image.resize(scale_value)
|
90
|
+
end
|
91
|
+
|
92
|
+
def crop(image)
|
93
|
+
image.smartcrop(*source.dimensions,
|
94
|
+
interesting: PictureTag.keep(@source.media_preset))
|
95
|
+
end
|
96
|
+
|
97
|
+
def scale_value
|
98
|
+
base.width.to_f / source.width
|
99
|
+
end
|
100
|
+
|
101
|
+
def mkdir
|
102
|
+
FileUtils.mkdir_p(File.dirname(base.absolute_filename))
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -18,7 +18,7 @@ module PictureTag
|
|
18
18
|
# | domain | baseurl | directory | filename
|
19
19
|
def to_s
|
20
20
|
Addressable::URI.escape(
|
21
|
-
File.join(domain, baseurl, directory, @filename)
|
21
|
+
File.join(domain, PictureTag.baseurl, directory, @filename)
|
22
22
|
)
|
23
23
|
end
|
24
24
|
|
@@ -29,21 +29,14 @@ module PictureTag
|
|
29
29
|
# | domain | baseurl | j-p-t output dir | filename
|
30
30
|
def domain
|
31
31
|
if PictureTag.cdn?
|
32
|
-
PictureTag.
|
33
|
-
elsif PictureTag.
|
32
|
+
PictureTag.cdn_url
|
33
|
+
elsif PictureTag.relative_url
|
34
34
|
''
|
35
35
|
else
|
36
36
|
PictureTag.config['url'] || ''
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
# https://example.com/my-base-path/assets/generated-images/image.jpg
|
41
|
-
# ^^^^^^^^^^^^^
|
42
|
-
# | domain | baseurl | directory | filename
|
43
|
-
def baseurl
|
44
|
-
PictureTag.config['baseurl'] || ''
|
45
|
-
end
|
46
|
-
|
47
40
|
# https://example.com/my-base-path/assets/generated-images/image.jpg
|
48
41
|
# ^^^^^^^^^^^^^^^^^^^^^^^^
|
49
42
|
# | domain | baseurl | directory | filename
|