jekyll_picture_tag 1.14.0 → 2.0.0pre1
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/.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 }
|