distorted 0.5.6 → 0.5.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +661 -0
- data/README.md +4 -139
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/Less_Perfect_DOS_VGA.png +0 -0
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/More_Perfect_DOS_VGA.png +0 -0
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/img/Perfect_DOS_VGA.png +0 -0
- data/font/1252/LICENSE/MoreLessPerfectDOSVGA437/less_more_perfect_dos_vga_437.html +52 -0
- data/font/1252/LICENSE/PerfectDOSVGA437/font-comment.php@file=perfect_dos_vga_437.html +5 -0
- data/font/1252/LessPerfectDOSVGA.ttf +0 -0
- data/font/1252/MorePerfectDOSVGA.ttf +0 -0
- data/font/1252/Perfect DOS VGA 437 Win.ttf +0 -0
- data/font/437/Perfect DOS VGA 437.ttf +0 -0
- data/font/437/dos437.txt +72 -0
- data/font/65001/Anonymous Pro B.ttf +0 -0
- data/font/65001/Anonymous Pro BI.ttf +0 -0
- data/font/65001/Anonymous Pro I.ttf +0 -0
- data/font/65001/Anonymous Pro.ttf +0 -0
- data/font/65001/LICENSE/AnonymousPro/FONTLOG.txt +45 -0
- data/font/65001/LICENSE/AnonymousPro/OFL-FAQ.txt +235 -0
- data/font/65001/LICENSE/AnonymousPro/OFL.txt +94 -0
- data/font/65001/LICENSE/AnonymousPro/README.txt +55 -0
- data/font/850/ProFont-Bold-01/LICENSE +22 -0
- data/font/850/ProFont-Bold-01/readme.txt +28 -0
- data/font/850/ProFontWindows-Bold.ttf +0 -0
- data/font/850/ProFontWindows.ttf +0 -0
- data/font/850/Profont/LICENSE +22 -0
- data/font/850/Profont/readme.txt +31 -0
- data/font/932/LICENSE/README-ttf.txt +213 -0
- data/font/932/mona.ttf +0 -0
- data/lib/distorted/error_code.rb +8 -0
- data/lib/distorted/font.rb +192 -0
- data/lib/distorted/image.rb +121 -0
- data/lib/distorted/modular_technology/pango.rb +75 -0
- data/lib/distorted/monkey_business/hash.rb +33 -0
- data/lib/distorted/monkey_business/mnemoniq.rb +8 -0
- data/lib/distorted/monkey_business/set.rb +15 -0
- data/lib/distorted/monkey_business/string.rb +6 -0
- data/lib/distorted/pdf.rb +110 -0
- data/lib/distorted/svg.rb +21 -0
- data/lib/distorted/text.rb +241 -0
- data/lib/distorted/version.rb +20 -0
- data/lib/distorted/video.rb +193 -0
- data/test/distorted_test.rb +11 -0
- data/test/test_helper.rb +4 -0
- metadata +49 -5
data/font/932/mona.ttf
ADDED
Binary file
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# https://ruby-doc.org/core/Exception.html sez:
|
2
|
+
# "It is recommended that a library should have one subclass of StandardError
|
3
|
+
# or RuntimeError and have specific exception types inherit from it.
|
4
|
+
# This allows the user to rescue a generic exception type to catch
|
5
|
+
# all exceptions the library may raise even if future versions of
|
6
|
+
# the library add new exception subclasses."
|
7
|
+
class StandardDistorteDError < StandardError
|
8
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
# Font metadata extraction
|
4
|
+
require 'ttfunk'
|
5
|
+
|
6
|
+
require 'mime/types'
|
7
|
+
|
8
|
+
# No need to do all the fancy library versioning in a subclass.
|
9
|
+
require 'vips'
|
10
|
+
|
11
|
+
require 'distorted/text'
|
12
|
+
|
13
|
+
|
14
|
+
module Cooltrainer
|
15
|
+
module DistorteD
|
16
|
+
class Font < Text
|
17
|
+
|
18
|
+
MEDIA_TYPE = 'font'.freeze
|
19
|
+
|
20
|
+
# TODO: Test OTF, OTB, and others.
|
21
|
+
# NOTE: Traditional bitmap fonts won't be supported due to Pango 1.44
|
22
|
+
# and later switching to Harfbuzz from Freetype:
|
23
|
+
# https://gitlab.gnome.org/GNOME/pango/-/issues/386
|
24
|
+
# https://blogs.gnome.org/mclasen/2019/05/25/pango-future-directions/
|
25
|
+
MIME_TYPES = MIME::Types[/^#{self::MEDIA_TYPE}\/ttf/].to_set
|
26
|
+
|
27
|
+
ATTRS = Set[
|
28
|
+
:alt,
|
29
|
+
]
|
30
|
+
ATTRS_VALUES = {
|
31
|
+
}
|
32
|
+
ATTRS_DEFAULT = {
|
33
|
+
}
|
34
|
+
|
35
|
+
|
36
|
+
# irb(main):089:0> chars.take(5)
|
37
|
+
# => [[1, 255], [2, 1], [3, 2], [4, 3], [5, 4]]
|
38
|
+
# irb(main):090:0> chars.values.take(5)
|
39
|
+
# => [255, 1, 2, 3, 4]
|
40
|
+
# irb(main):091:0> chars.values.map(&:chr).take(5)
|
41
|
+
# => ["\xFF", "\x01", "\x02", "\x03", "\x04"]
|
42
|
+
def to_pango
|
43
|
+
output = '' << cr << '<span>' << cr
|
44
|
+
|
45
|
+
output << "<span size='35387'> #{font_name}</span>" << cr << cr
|
46
|
+
|
47
|
+
output << "<span size='24576'> #{font_description}</span>" << cr
|
48
|
+
output << "<span size='24576'> #{font_copyright}</span>" << cr
|
49
|
+
output << "<span size='24576'> #{font_version}</span>" << cr << cr
|
50
|
+
|
51
|
+
# Print a preview String in using the loaded font. Or don't.
|
52
|
+
if @demo
|
53
|
+
output << cr << cr << "<span size='24576' foreground='grey'> #{g_markup_escape_text(@demo)}</span>" << cr << cr << cr
|
54
|
+
end
|
55
|
+
|
56
|
+
# /!\ MANDATORY READING /!\
|
57
|
+
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html
|
58
|
+
#
|
59
|
+
# "The 'cmap' table maps character codes to glyph indices.
|
60
|
+
# The choice of encoding for a particular font is dependent upon the conventions
|
61
|
+
# used by the intended platform. A font intended to run on multiple platforms
|
62
|
+
# with different encoding conventions will require multiple encoding tables.
|
63
|
+
# As a result, the 'cmap' table may contain multiple subtables,
|
64
|
+
# one for each supported encoding scheme."
|
65
|
+
#
|
66
|
+
# Cmap#unicode is a convenient shortcut to sorting the subtables
|
67
|
+
# and removing any unusable ones:
|
68
|
+
# https://github.com/prawnpdf/ttfunk/blob/master/lib/ttfunk/table/cmap.rb
|
69
|
+
#
|
70
|
+
# irb(main):174:0> font_meta.cmap.tables.count
|
71
|
+
# => 3
|
72
|
+
# irb(main):175:0> font_meta.cmap.unicode.count
|
73
|
+
# => 2
|
74
|
+
@font_meta.cmap.tables.each do |table|
|
75
|
+
next if !table.unicode?
|
76
|
+
# Each subtable's `code_map` is a Hash map of character codes (the Hash keys)
|
77
|
+
# to the glyph IDs from the original font (the Hash's values).
|
78
|
+
#
|
79
|
+
# Subtable::encode takes:
|
80
|
+
# - a Hash mapping character codes to original font glyph IDs.
|
81
|
+
# - the desired output encoding — Set[:mac_roman, :unicode, :unicode_ucs4]
|
82
|
+
# https://github.com/prawnpdf/ttfunk/blob/master/lib/ttfunk/table/cmap/subtable.rb
|
83
|
+
# …and returns a Hash with keys:
|
84
|
+
# - :charmap — Hash mapping the characters in the input charmap
|
85
|
+
# to a another hash containing both the `:old`
|
86
|
+
# and `:new` glyph ids for each character code.
|
87
|
+
# - :subtable — String encoded subtable for the given encoding.
|
88
|
+
encoded = TTFunk::Table::Cmap::Subtable::encode(table&.code_map, :unicode).dig(:charmap)
|
89
|
+
|
90
|
+
output << "<span size='49152'>"
|
91
|
+
|
92
|
+
i = 0
|
93
|
+
encoded.each_pair { |c, (old, new)|
|
94
|
+
|
95
|
+
begin
|
96
|
+
if glyph = @font_meta.glyph_outlines.for(c)
|
97
|
+
# Add a space on either side of the character so they aren't
|
98
|
+
# all smooshed up against each other and unreadable.
|
99
|
+
output << ' ' << g_markup_escape_char(c) << ' '
|
100
|
+
if i >= 15
|
101
|
+
output << cr
|
102
|
+
i = 0
|
103
|
+
else
|
104
|
+
i = i + 1
|
105
|
+
end
|
106
|
+
else
|
107
|
+
end
|
108
|
+
rescue NoMethodError => nme
|
109
|
+
# TTFunk's `glyph_outlines.for()` will raise this if we call it
|
110
|
+
# for a codepoint that does not exist in the font, which we will
|
111
|
+
# not do because we are enumerating the codepoints in the font,
|
112
|
+
# but we should still handle the possibility.
|
113
|
+
# irb(main):060:0> font.glyph_outlines.for(555555)
|
114
|
+
#
|
115
|
+
# Traceback (most recent call last):
|
116
|
+
# 6: from /usr/bin/irb:23:in `<main>'
|
117
|
+
# 5: from /usr/bin/irb:23:in `load'
|
118
|
+
# 4: from /home/okeeblow/.gems/gems/irb-1.2.4/exe/irb:11:in `<top (required)>'
|
119
|
+
# 3: from (irb):60
|
120
|
+
# 2: from /home/okeeblow/.gems/gems/ttfunk-1.6.2.1/lib/ttfunk/table/glyf.rb:35:in `for'
|
121
|
+
# 1: from /home/okeeblow/.gems/gems/ttfunk-1.6.2.1/lib/ttfunk/table/loca.rb:35:in `size_of'
|
122
|
+
# NoMethodError (undefined method `-' for nil:NilClass)
|
123
|
+
end
|
124
|
+
}
|
125
|
+
|
126
|
+
output << '</span>' << cr
|
127
|
+
end
|
128
|
+
|
129
|
+
output << '</span>'
|
130
|
+
output
|
131
|
+
end
|
132
|
+
|
133
|
+
# Return the `src` as the font_path since we aren't using
|
134
|
+
# any of the built-in fonts.
|
135
|
+
def font_path
|
136
|
+
@src
|
137
|
+
end
|
138
|
+
|
139
|
+
def initialize(src, demo: nil)
|
140
|
+
@src = src
|
141
|
+
@demo = demo
|
142
|
+
|
143
|
+
# TODO: Check that src exists, because TTFunk won't and will just
|
144
|
+
# give us an unusable object instead.
|
145
|
+
@font_meta = TTFunk::File.open(src)
|
146
|
+
|
147
|
+
# https://libvips.github.io/libvips/API/current/libvips-create.html#vips-text
|
148
|
+
@image = Vips::Image.text(
|
149
|
+
# This string must be well-escaped Pango Markup:
|
150
|
+
# https://developer.gnome.org/pango/stable/pango-Markup.html
|
151
|
+
# However the official function for escaping text is
|
152
|
+
# not implemented in Ruby GLib, so we have to do it ourselves.
|
153
|
+
to_pango,
|
154
|
+
**{
|
155
|
+
# String absolute path to TTF
|
156
|
+
:fontfile => font_path,
|
157
|
+
# It's not enough to just specify the TTF path;
|
158
|
+
# we must also specify a font family, subfamily, and size.
|
159
|
+
:font => "#{font_name}",
|
160
|
+
# Space between lines (in Points).
|
161
|
+
:spacing => @font_meta.line_gap,
|
162
|
+
# Requires libvips 8.8
|
163
|
+
:justify => false,
|
164
|
+
:dpi => 144,
|
165
|
+
},
|
166
|
+
)
|
167
|
+
end
|
168
|
+
|
169
|
+
end # Font
|
170
|
+
end # DistorteD
|
171
|
+
end # Cooltrainer
|
172
|
+
|
173
|
+
|
174
|
+
# Notes on file-format specifics and software-library-specifics
|
175
|
+
#
|
176
|
+
# # TTF (via TTFunk)
|
177
|
+
#
|
178
|
+
# ## Cmap
|
179
|
+
#
|
180
|
+
# Each TTFunk::Table::Cmap::Format<whatever> class responds to `:supported?`
|
181
|
+
# with its own internal boolean telling us if that Format is usable in TTFunk.
|
182
|
+
# This has nothing to do with any font file itself, just the library code.
|
183
|
+
# irb(main)> font.cmap.tables.map{|t| t.supported?}
|
184
|
+
# => [true, true, true]
|
185
|
+
#
|
186
|
+
# Any subclass of TTFunk::Table::Cmap::Subtable responds to `:unicode?`
|
187
|
+
# with a boolean calculated from the instance `@platform_id` and `@encoding_id`,
|
188
|
+
# and those numeric IDs are assigned to the symbolic (e.g. `:macroman`) names in:
|
189
|
+
# https://github.com/prawnpdf/ttfunk/blob/master/lib/ttfunk/table/cmap/subtable.rb
|
190
|
+
# irb(main)> font.cmap.tables.map{|t| t.unicode?}
|
191
|
+
# => [true, false, true]
|
192
|
+
#
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# Requiring libvips 8.8 for HEIC/HEIF (moo) support, `justify` support in the
|
2
|
+
# Vips::Image text operator, animated WebP support, and more:
|
3
|
+
# https://libvips.github.io/libvips/2019/04/22/What's-new-in-8.8.html
|
4
|
+
VIPS_MINIMUM_VER = [8, 8, 0]
|
5
|
+
|
6
|
+
# Tell the user to install the shared library if it's missing.
|
7
|
+
begin
|
8
|
+
require 'vips'
|
9
|
+
|
10
|
+
we_good = false
|
11
|
+
if Vips::version(0) >= VIPS_MINIMUM_VER[0]
|
12
|
+
if Vips::version(1) >= VIPS_MINIMUM_VER[1]
|
13
|
+
if Vips::version(2) >= VIPS_MINIMUM_VER[2]
|
14
|
+
we_good = true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
unless we_good
|
19
|
+
raise LoadError.new("libvips is older than DistorteD's minimum requirement: needed #{VIPS_MINIMUM_VER.join('.'.freeze)} vs available '#{Vips::version_string}'")
|
20
|
+
end
|
21
|
+
|
22
|
+
rescue LoadError => le
|
23
|
+
# Only match libvips.so load failure
|
24
|
+
raise unless le.message =~ /libvips.so/
|
25
|
+
|
26
|
+
# Multiple OS help
|
27
|
+
help = <<~INSTALL
|
28
|
+
|
29
|
+
Please install the libvips image processing library.
|
30
|
+
|
31
|
+
FreeBSD:
|
32
|
+
pkg install graphics/vips
|
33
|
+
|
34
|
+
macOS:
|
35
|
+
brew install vips
|
36
|
+
|
37
|
+
Debian/Ubuntu/Mint:
|
38
|
+
apt install libvips libvips-dev
|
39
|
+
INSTALL
|
40
|
+
|
41
|
+
# Re-raise with install message
|
42
|
+
raise $!, "#{help}\n#{$!}", $!.backtrace
|
43
|
+
end
|
44
|
+
|
45
|
+
require 'set'
|
46
|
+
|
47
|
+
require 'mime/types'
|
48
|
+
|
49
|
+
module Cooltrainer
|
50
|
+
module DistorteD
|
51
|
+
class Image
|
52
|
+
|
53
|
+
MEDIA_TYPE = 'image'.freeze
|
54
|
+
|
55
|
+
# SVG support is a sub-class and not directly supported here:
|
56
|
+
# `write_to_file': No known saver for '/home/okeeblow/Works/cooltrainer/_site/IIDX-turntable.svg'. (Vips::Error)
|
57
|
+
MIME_TYPES = MIME::Types[/^#{MEDIA_TYPE}\/(?!svg)/, :complete => true].to_set
|
58
|
+
|
59
|
+
# Attributes for our <picture>/<img>.
|
60
|
+
# Automatically enabled as attrs for DD Liquid Tag.
|
61
|
+
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture#Attributes
|
62
|
+
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#Attributes
|
63
|
+
# https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading
|
64
|
+
# :crop is a Vips-only attr
|
65
|
+
ATTRS = Set[:alt, :caption, :href, :crop, :loading]
|
66
|
+
|
67
|
+
# Defaults for HTML Element attributes.
|
68
|
+
# Not every attr has to be listed here.
|
69
|
+
# Many need no default and just won't render.
|
70
|
+
ATTRS_DEFAULT = {
|
71
|
+
:crop => :attention,
|
72
|
+
:loading => :eager,
|
73
|
+
}
|
74
|
+
ATTRS_VALUES = {
|
75
|
+
# https://www.rubydoc.info/gems/ruby-vips/Vips/Interesting
|
76
|
+
:crop => Set[:none, :centre, :entropy, :attention],
|
77
|
+
:loading => Set[:eager, :lazy],
|
78
|
+
}
|
79
|
+
|
80
|
+
|
81
|
+
def initialize(src)
|
82
|
+
@image = Vips::Image.new_from_file(src)
|
83
|
+
@src = src
|
84
|
+
end
|
85
|
+
|
86
|
+
def rotate(angle: nil)
|
87
|
+
if angle == :auto
|
88
|
+
@image = @image&.autorot
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def clean
|
93
|
+
# Nuke the entire site from orbit. It's the only way to be sure.
|
94
|
+
@image.get_fields.grep(/exif-ifd/).each {|field| @image.remove field}
|
95
|
+
end
|
96
|
+
|
97
|
+
def save(dest, width: nil, crop: nil)
|
98
|
+
begin
|
99
|
+
if width.nil? or width == :full
|
100
|
+
return @image.write_to_file(dest)
|
101
|
+
elsif width.respond_to?(:to_i)
|
102
|
+
ver = @image.thumbnail_image(
|
103
|
+
width.to_i,
|
104
|
+
# Use `self` namespace for constants so subclasses can redefine
|
105
|
+
**{:crop => crop || self.singleton_class.const_get(:ATTRS_DEFAULT)[:crop]},
|
106
|
+
)
|
107
|
+
return ver.write_to_file(dest)
|
108
|
+
end
|
109
|
+
rescue Vips::Error => v
|
110
|
+
if v.message.include?('No known saver')
|
111
|
+
# TODO: Handle missing output formats. Replacements? Skip it? Die?
|
112
|
+
return nil
|
113
|
+
else
|
114
|
+
raise
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end # save
|
118
|
+
|
119
|
+
end # Image
|
120
|
+
end # DistorteD
|
121
|
+
end # Cooltrainer
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Cooltrainer
|
2
|
+
module DistorteD
|
3
|
+
module Tech
|
4
|
+
module Pango
|
5
|
+
|
6
|
+
# Escape text as necessary for Pango Markup, which is what Vips::Image.text()
|
7
|
+
# expects for its argv. This code should be in GLib but is unimplemented in Ruby's:
|
8
|
+
#
|
9
|
+
# https://ruby-gnome2.osdn.jp/hiki.cgi?Gtk%3A%3ALabel#Markup+%28styled+text%29
|
10
|
+
# "The markup passed to Gtk::Label#set_markup() must be valid; for example,
|
11
|
+
# literal </>/& characters must be escaped as <, >, and &.
|
12
|
+
# If you pass text obtained from the user, file, or a network to
|
13
|
+
# Gtk::Label#set_markup(), you'll want to escape it
|
14
|
+
# with GLib::Markup.escape_text?(not implemented yet)."
|
15
|
+
#
|
16
|
+
# Base my own implementation on the original C version found in gmarkup:
|
17
|
+
# https://gitlab.gnome.org/GNOME/glib/-/blob/master/glib/gmarkup.c
|
18
|
+
def g_markup_escape_text(text)
|
19
|
+
text.map{ |c| g_markup_escape_char(c) }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns a Pango-escaped Carriage Return.
|
23
|
+
# Use this for linebreaking Pango Markup output.
|
24
|
+
def cr
|
25
|
+
g_markup_escape_char(0x0D)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a Pango-escapped Line Feed.
|
29
|
+
# This isn't used/needed for anything with Pango
|
30
|
+
# but it felt weird to include CR and not LF lmao
|
31
|
+
def lf
|
32
|
+
g_markup_escape_char(0x0A)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a Pango'escaped CRLF pair.
|
36
|
+
# Also not needed for anything.
|
37
|
+
def crlf
|
38
|
+
cr << lf
|
39
|
+
end
|
40
|
+
|
41
|
+
# The char-by-char actual function used by g_markup_escape_text
|
42
|
+
def g_markup_escape_char(c)
|
43
|
+
# I think a fully-working version of this function would
|
44
|
+
# be as simple `sprintf('&#x%x;', c.ord)`, but I want to copy
|
45
|
+
# the C implementation as closely as possible, which means using
|
46
|
+
# the named escape sequences for common characters and separating
|
47
|
+
# the Unicode control characters (> 0x7f) even though three's no
|
48
|
+
# need to in Ruby.
|
49
|
+
case c.ord
|
50
|
+
when '&'.ord
|
51
|
+
'&'
|
52
|
+
when '<'.ord
|
53
|
+
'<'
|
54
|
+
when '>'.ord
|
55
|
+
'>'
|
56
|
+
when '\''.ord
|
57
|
+
'''
|
58
|
+
when '"'.ord
|
59
|
+
'"'
|
60
|
+
when 0x1..0x8, 0xb..0xc, 0xe..0x1f, 0x7f
|
61
|
+
sprintf('&#x%x;', c.ord)
|
62
|
+
when 0x7f..0x84, 0x86..0x9f
|
63
|
+
sprintf('&#x%x;', c.ord)
|
64
|
+
when 0x0 # what's this…?
|
65
|
+
# Avoid a `ArgumentError: string contains null byte`
|
66
|
+
# by not printing one :)
|
67
|
+
else
|
68
|
+
c
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end # Pango
|
73
|
+
end # Tech
|
74
|
+
end # DistorteD
|
75
|
+
end # Cooltrainer
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
|
5
|
+
# Complement Ruby::YAML behavior, where usage of Set syntax
|
6
|
+
# returns a Hash with all-nil values.
|
7
|
+
# Calling :to_set on a Hash with all-nil values should return
|
8
|
+
# a Set of the Hash's keys.
|
9
|
+
this_old_set = instance_method(:to_set)
|
10
|
+
define_method(:to_set) do
|
11
|
+
if self.values.all?{ |v| v.nil? }
|
12
|
+
self.keys.to_set
|
13
|
+
else
|
14
|
+
this_old_set.bind(self).()
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# https://github.com/dam13n/ruby-bury/blob/master/hash.rb
|
19
|
+
# This is not packaged as a Gem or I'd be using it instead of including my own.
|
20
|
+
def bury(*args)
|
21
|
+
if args.count < 2
|
22
|
+
raise ArgumentError.new('2 or more arguments required')
|
23
|
+
elsif args.count == 2
|
24
|
+
self[args[0]] = args[1]
|
25
|
+
else
|
26
|
+
arg = args.shift
|
27
|
+
self[arg] = {} unless self[arg]
|
28
|
+
self[arg].bury(*args) unless args.empty?
|
29
|
+
end
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'mime/types'
|
2
|
+
|
3
|
+
# MIME::Types#preferred_extension returns @extensions.first unless
|
4
|
+
# otherwise set. I don't like some of the defaults, so this file
|
5
|
+
# changes them.
|
6
|
+
# Normally I don't like to monkey patch just on import without calling
|
7
|
+
# some method, but this is one time I explicitly want to do that.
|
8
|
+
MIME::Types['image/jpeg'].last.preferred_extension = 'jpg'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
# Override Set.to_hash to complement Ruby::YAML's Set implementation,
|
4
|
+
# where the YAML Set syntax returns a Hash with all-nil values,
|
5
|
+
# at least without some decorator sugar in the YAML itself:
|
6
|
+
# https://rhnh.net/2011/01/31/yaml-tutorial/
|
7
|
+
#
|
8
|
+
# Since Set is implemented using a Hash internally I think it makes
|
9
|
+
# more sense for Set.to_hash to return a Hash with all-nil values
|
10
|
+
# with keys matching the contents of the original Set.
|
11
|
+
class Set
|
12
|
+
def to_hash
|
13
|
+
Hash[self.map { |s| [s, nil] }]
|
14
|
+
end
|
15
|
+
end
|