jekyll_picture_tag 1.14.0 → 2.0.0pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.envrc +2 -0
- data/.github/workflows/code-checks.yml +2 -12
- data/.rubocop.yml +2 -0
- data/.ruby-version +1 -1
- data/docs/devs/contributing/code.md +11 -3
- data/docs/devs/contributing/testing.md +0 -11
- data/docs/devs/releases.md +20 -0
- data/docs/index.md +32 -17
- data/docs/logo.png +0 -0
- data/docs/logo.svg +880 -0
- data/docs/users/getting_started.md +55 -0
- data/docs/users/installation.md +17 -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 -3
- 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 +70 -55
- data/docs/users/presets/index.md +78 -42
- 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/tutorial.md +97 -0
- data/jekyll_picture_tag.gemspec +33 -23
- data/lib/jekyll_picture_tag.rb +8 -6
- 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 -63
- data/lib/jekyll_picture_tag/images/image_file.rb +90 -0
- data/lib/jekyll_picture_tag/images/img_uri.rb +3 -12
- 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 +41 -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 +5 -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/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 +2 -0
- metadata +124 -106
- data/Dockerfile +0 -9
- data/docs/users/notes/input_checking.md +0 -6
- data/docs/users/presets/strip_metadata.md +0 -13
- data/install_imagemagick.sh +0 -23
- data/jekyll-picture-tag.gemspec +0 -52
- data/lib/jekyll-picture-tag.rb +0 -25
- data/lib/jekyll_picture_tag/cache/base.rb +0 -61
- 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 -13
- data/lib/jekyll_picture_tag/defaults/presets.yml +0 -12
- 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
@@ -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,80 +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
|
-
def
|
85
|
-
|
86
|
-
|
87
|
-
# Post crop, before resizing and reformatting
|
88
|
-
@source_dimensions = { width: image_base.width, height: image_base.height }
|
89
|
-
|
90
|
-
@image = image_base
|
70
|
+
def settings
|
71
|
+
[@source.digest, @source.crop, @source.keep, quality]
|
91
72
|
end
|
92
73
|
|
93
|
-
def
|
94
|
-
@
|
95
|
-
if PictureTag.preset['strip_metadata']
|
96
|
-
i.auto_orient
|
97
|
-
i.strip
|
98
|
-
end
|
99
|
-
|
100
|
-
if @crop
|
101
|
-
i.gravity @gravity
|
102
|
-
i.crop @crop
|
103
|
-
end
|
104
|
-
end
|
74
|
+
def image
|
75
|
+
@image ||= Vips::Image.new_from_file @source.name
|
105
76
|
end
|
106
77
|
|
107
78
|
def generate_image
|
108
|
-
|
109
|
-
|
110
|
-
image.format(@format, 0, { resize: "#{@width}x", quality: quality })
|
111
|
-
FileUtils.mkdir_p(File.dirname(absolute_filename))
|
112
|
-
|
113
|
-
image.write absolute_filename
|
114
|
-
|
115
|
-
FileUtils.chmod(0o644, absolute_filename)
|
116
|
-
end
|
79
|
+
return if @source.missing
|
117
80
|
|
118
|
-
|
119
|
-
PictureTag.quality(@format, @width)
|
81
|
+
ImageFile.new(@source, self)
|
120
82
|
end
|
121
83
|
|
122
84
|
def process_format(format)
|
@@ -0,0 +1,90 @@
|
|
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
|
+
class ImageFile
|
6
|
+
def initialize(source, base)
|
7
|
+
@source = source
|
8
|
+
@base = base
|
9
|
+
|
10
|
+
build
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader :source, :base
|
16
|
+
|
17
|
+
def build
|
18
|
+
notify
|
19
|
+
|
20
|
+
mkdir
|
21
|
+
|
22
|
+
image = load_image
|
23
|
+
|
24
|
+
image = process(image)
|
25
|
+
|
26
|
+
write(image)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Processing pipeline
|
30
|
+
def process(image)
|
31
|
+
image = crop(image) if source.crop?
|
32
|
+
|
33
|
+
image = resize(image)
|
34
|
+
|
35
|
+
image.autorot
|
36
|
+
end
|
37
|
+
|
38
|
+
def write_opts
|
39
|
+
opts = PictureTag.preset['image_options'][@base.format] || {}
|
40
|
+
|
41
|
+
opts[:strip] = PictureTag.preset['strip_metadata']
|
42
|
+
|
43
|
+
# gifs don't accept a quality setting.
|
44
|
+
opts[:Q] = base.quality unless base.format == 'gif'
|
45
|
+
|
46
|
+
opts.transform_keys(&:to_sym)
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_image
|
50
|
+
Vips::Image.new_from_file source.name
|
51
|
+
end
|
52
|
+
|
53
|
+
def write(image)
|
54
|
+
begin
|
55
|
+
image.write_to_file(base.absolute_filename, **write_opts)
|
56
|
+
rescue Vips::Error
|
57
|
+
# If vips can't handle it, fall back to imagemagick.
|
58
|
+
opts = write_opts.transform_keys do |key|
|
59
|
+
key == :Q ? :quality : key
|
60
|
+
end
|
61
|
+
|
62
|
+
image.magicksave(base.absolute_filename, **opts)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Fix permissions. TODO - still necessary?
|
66
|
+
FileUtils.chmod(0o644, base.absolute_filename)
|
67
|
+
end
|
68
|
+
|
69
|
+
def notify
|
70
|
+
puts 'Generating new image file: ' + base.name
|
71
|
+
end
|
72
|
+
|
73
|
+
def resize(image)
|
74
|
+
image.resize(scale_value)
|
75
|
+
end
|
76
|
+
|
77
|
+
def crop(image)
|
78
|
+
image.smartcrop(*source.dimensions,
|
79
|
+
interesting: PictureTag.keep(@source.media_preset))
|
80
|
+
end
|
81
|
+
|
82
|
+
def scale_value
|
83
|
+
base.width.to_f / source.width
|
84
|
+
end
|
85
|
+
|
86
|
+
def mkdir
|
87
|
+
FileUtils.mkdir_p(File.dirname(base.absolute_filename))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
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,23 +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
|
-
return '' if PictureTag.pconfig['ignore_baseurl']
|
45
|
-
|
46
|
-
PictureTag.config[PictureTag.pconfig['baseurl_key']] || ''
|
47
|
-
end
|
48
|
-
|
49
40
|
# https://example.com/my-base-path/assets/generated-images/image.jpg
|
50
41
|
# ^^^^^^^^^^^^^^^^^^^^^^^^
|
51
42
|
# | domain | baseurl | directory | filename
|
@@ -5,7 +5,7 @@ module PictureTag
|
|
5
5
|
class SourceImage
|
6
6
|
attr_reader :shortname, :missing, :media_preset
|
7
7
|
|
8
|
-
include MiniMagick
|
8
|
+
# include MiniMagick
|
9
9
|
|
10
10
|
def initialize(relative_filename, media_preset = nil)
|
11
11
|
# /home/dave/my_blog/assets/images/somefolder/myimage.jpg
|
@@ -21,12 +21,38 @@ module PictureTag
|
|
21
21
|
@digest ||= cache[:digest] || ''
|
22
22
|
end
|
23
23
|
|
24
|
+
def crop
|
25
|
+
PictureTag.crop(media_preset)
|
26
|
+
end
|
27
|
+
|
28
|
+
def crop?
|
29
|
+
!crop.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
def keep
|
33
|
+
PictureTag.keep(media_preset)
|
34
|
+
end
|
35
|
+
|
36
|
+
def dimensions
|
37
|
+
[width, height]
|
38
|
+
end
|
39
|
+
|
24
40
|
def width
|
25
|
-
|
41
|
+
return raw_width unless crop?
|
42
|
+
|
43
|
+
[raw_width, (raw_height * cropped_aspect)].min.round
|
26
44
|
end
|
27
45
|
|
28
46
|
def height
|
29
|
-
|
47
|
+
return raw_height unless crop?
|
48
|
+
|
49
|
+
[raw_height, (raw_width / cropped_aspect)].min.round
|
50
|
+
end
|
51
|
+
|
52
|
+
def cropped_aspect
|
53
|
+
return Utils.aspect_float(raw_width, raw_height) unless crop?
|
54
|
+
|
55
|
+
Utils.aspect_float(*crop.split(':').map(&:to_f))
|
30
56
|
end
|
31
57
|
|
32
58
|
# /home/dave/my_blog/assets/images/somefolder/myimage.jpg
|
@@ -44,13 +70,23 @@ module PictureTag
|
|
44
70
|
# /home/dave/my_blog/assets/images/somefolder/myimage.jpg
|
45
71
|
# ^^^
|
46
72
|
def ext
|
47
|
-
@ext ||= File.extname(name)[1
|
73
|
+
@ext ||= File.extname(name)[1..].downcase
|
48
74
|
end
|
49
75
|
|
50
76
|
private
|
51
77
|
|
78
|
+
# pre-crop
|
79
|
+
def raw_width
|
80
|
+
@raw_width ||= @missing ? 999_999 : image.width
|
81
|
+
end
|
82
|
+
|
83
|
+
# pre-crop
|
84
|
+
def raw_height
|
85
|
+
@raw_height ||= @missing ? 999_999 : image.height
|
86
|
+
end
|
87
|
+
|
52
88
|
def cache
|
53
|
-
@cache ||= Cache
|
89
|
+
@cache ||= Cache.new(@shortname)
|
54
90
|
end
|
55
91
|
|
56
92
|
def missing?
|
@@ -75,14 +111,12 @@ module PictureTag
|
|
75
111
|
|
76
112
|
def update_cache
|
77
113
|
cache[:digest] = source_digest
|
78
|
-
cache[:width] = image.width
|
79
|
-
cache[:height] = image.height
|
80
114
|
|
81
115
|
cache.write
|
82
116
|
end
|
83
117
|
|
84
118
|
def image
|
85
|
-
@image ||= Image.
|
119
|
+
@image ||= Vips::Image.new_from_file(name)
|
86
120
|
end
|
87
121
|
|
88
122
|
def source_digest
|
@@ -90,7 +124,8 @@ module PictureTag
|
|
90
124
|
end
|
91
125
|
|
92
126
|
def missing_image_warning
|
93
|
-
"JPT Could not find #{name}.
|
127
|
+
"JPT Could not find #{name}. " \
|
128
|
+
'Your site will have broken images. Continuing.'
|
94
129
|
end
|
95
130
|
|
96
131
|
def missing_image_error
|
@@ -1,6 +1,70 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
module PictureTag
|
2
|
+
# Instructions obtain, validate, and typecast/coerce input values. These
|
3
|
+
# inputs are either taken directly from jekyll's inputs, or handled by parsers
|
4
|
+
# first.
|
5
|
+
#
|
6
|
+
# Logic which affects only a single setting belongs in Instructions, while
|
7
|
+
# logic which affects multiple settings belongs in Parsers.
|
8
|
+
#
|
9
|
+
# Since instruction classes are so small, we define several per file in the
|
10
|
+
# instructions directory to save on boilerplate. All fall under the
|
11
|
+
# Instructions module namespace.
|
12
|
+
module Instructions
|
13
|
+
# Generic instruction, meant to be inherited. Children of this class must
|
14
|
+
# override the source method, and likely want to override valid?, coerce,
|
15
|
+
# and error_message as applicable.
|
16
|
+
class Instruction
|
17
|
+
# Memoized value of the given instruction. This is the public API.
|
18
|
+
def value
|
19
|
+
return @value if defined?(@value)
|
20
|
+
|
21
|
+
raise ArgumentError, error_message unless valid?
|
22
|
+
|
23
|
+
@value = coerced
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Source(s) of truth - where does this setting come from? Logic does not
|
29
|
+
# belong here. If information comes from muliple places, return an array
|
30
|
+
# or a hash.
|
31
|
+
def source
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
# Determine whether or not the input(s) are valid.
|
36
|
+
def valid?
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
# Convert input(s) to output.
|
41
|
+
def coerce
|
42
|
+
source
|
43
|
+
end
|
44
|
+
|
45
|
+
# Message returned if validation fails. Override this with something more
|
46
|
+
# helpful.
|
47
|
+
def error_message
|
48
|
+
"JPT - #{setting_name} received an invalid argument: #{source}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def coerced
|
52
|
+
return @coerced if defined?(@coerced)
|
53
|
+
|
54
|
+
@coerced = coerce
|
55
|
+
end
|
56
|
+
|
57
|
+
def setting_name
|
58
|
+
Utils.snakeize(self.class.to_s.split('::').last)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Load Parents
|
65
|
+
Dir[File.dirname(__FILE__) + '/instructions/parents/*.rb']
|
66
|
+
.sort.each { |file| require file }
|
67
|
+
|
68
|
+
# Load children:
|
69
|
+
Dir[File.dirname(__FILE__) + '/instructions/children/*.rb']
|
70
|
+
.sort.each { |file| require file }
|