joys 0.1.2 → 0.1.3
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 +8 -0
- data/README.md +321 -150
- data/joys-0.1.2.gem +0 -0
- data/lib/.DS_Store +0 -0
- data/lib/joys/cli.rb +1554 -0
- data/lib/joys/config.rb +133 -0
- data/lib/joys/core.rb +70 -42
- data/lib/joys/data.rb +477 -0
- data/lib/joys/helpers.rb +36 -9
- data/lib/joys/ssg.rb +597 -0
- data/lib/joys/tags.rb +5 -1
- data/lib/joys/toys.rb +328 -0
- data/lib/joys/version.rb +1 -1
- data/lib/joys.rb +2 -1
- metadata +7 -2
- data/.DS_Store +0 -0
data/lib/joys/toys.rb
ADDED
@@ -0,0 +1,328 @@
|
|
1
|
+
# Define a cycle helper that keeps its state per-key
|
2
|
+
# frozen_string_literal: true
|
3
|
+
# lib/joys/toys.rb - Intelligent Core Extensions
|
4
|
+
module Joys
|
5
|
+
module Toys
|
6
|
+
module CoreExtensions
|
7
|
+
module Numeric
|
8
|
+
|
9
|
+
# 3127613.filesize(:kb) => "3.0 GB", 1536.filesize => "1.5 KB"
|
10
|
+
def filesize(unit = :bytes)
|
11
|
+
# Convert input to bytes first
|
12
|
+
bytes = case unit
|
13
|
+
when :bytes, :b then self
|
14
|
+
when :kb then self * 1024
|
15
|
+
when :mb then self * 1024 * 1024
|
16
|
+
when :gb then self * 1024 * 1024 * 1024
|
17
|
+
when :tb then self * 1024 * 1024 * 1024 * 1024
|
18
|
+
else self
|
19
|
+
end
|
20
|
+
|
21
|
+
# Then convert bytes to human readable
|
22
|
+
return "#{bytes} B" if bytes < 1024
|
23
|
+
|
24
|
+
units = [
|
25
|
+
[1024**4, 'TB'],
|
26
|
+
[1024**3, 'GB'],
|
27
|
+
[1024**2, 'MB'],
|
28
|
+
[1024, 'KB']
|
29
|
+
]
|
30
|
+
units.each do |threshold, suffix|
|
31
|
+
if bytes >= threshold
|
32
|
+
value = bytes.to_f / threshold
|
33
|
+
formatted = value % 1 == 0 ? value.to_i.to_s : "%.1f" % value
|
34
|
+
return "#{formatted} #{suffix}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
"#{bytes} B"
|
39
|
+
end
|
40
|
+
# 21 => "21st", 102 => "102nd"
|
41
|
+
def ordinal
|
42
|
+
return self.to_s if self < 0
|
43
|
+
|
44
|
+
suffix = case self.to_i % 100
|
45
|
+
when 11, 12, 13 then 'th'
|
46
|
+
else
|
47
|
+
case self.to_i % 10
|
48
|
+
when 1 then 'st'
|
49
|
+
when 2 then 'nd'
|
50
|
+
when 3 then 'rd'
|
51
|
+
else 'th'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
"#{self}#{suffix}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# 1200 => "1.2K", 1000000 => "1M", 1500000000 => "1.5B"
|
58
|
+
def compact
|
59
|
+
return self.to_s if self.abs < 1000
|
60
|
+
|
61
|
+
units = [
|
62
|
+
[1_000_000_000_000, 'T'],
|
63
|
+
[1_000_000_000, 'B'],
|
64
|
+
[1_000_000, 'M'],
|
65
|
+
[1_000, 'K']
|
66
|
+
]
|
67
|
+
|
68
|
+
units.each do |threshold, suffix|
|
69
|
+
if self.abs >= threshold
|
70
|
+
value = self.to_f / threshold
|
71
|
+
formatted = value % 1 == 0 ? value.to_i.to_s : "%.1f" % value
|
72
|
+
return "#{formatted}#{suffix}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
self.to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
# 42 => "forty-two", 123 => "one hundred twenty-three"
|
80
|
+
def spell
|
81
|
+
return 'zero' if self == 0
|
82
|
+
return self.to_s if self < 0 || self >= 1_000_000
|
83
|
+
|
84
|
+
ones = %w[zero one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen]
|
85
|
+
tens = %w[zero ten twenty thirty forty fifty sixty seventy eighty ninety]
|
86
|
+
|
87
|
+
num = self.to_i
|
88
|
+
result = []
|
89
|
+
|
90
|
+
# Thousands
|
91
|
+
if num >= 1000
|
92
|
+
result << ones[num / 1000] + ' thousand'
|
93
|
+
num %= 1000
|
94
|
+
end
|
95
|
+
|
96
|
+
# Hundreds
|
97
|
+
if num >= 100
|
98
|
+
result << ones[num / 100] + ' hundred'
|
99
|
+
num %= 100
|
100
|
+
end
|
101
|
+
|
102
|
+
# Tens and ones
|
103
|
+
if num >= 20
|
104
|
+
ten_part = tens[num / 10]
|
105
|
+
one_part = num % 10 > 0 ? ones[num % 10] : nil
|
106
|
+
result << [ten_part, one_part].compact.join('-')
|
107
|
+
elsif num > 0
|
108
|
+
result << ones[num]
|
109
|
+
end
|
110
|
+
|
111
|
+
result.join(' ')
|
112
|
+
end
|
113
|
+
|
114
|
+
# 4 => "IV", 1994 => "MCMXCIV"
|
115
|
+
def roman
|
116
|
+
return '' if self <= 0 || self >= 4000
|
117
|
+
|
118
|
+
values = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
|
119
|
+
numerals = %w[M CM D CD C XC L XL X IX V IV I]
|
120
|
+
|
121
|
+
result = ''
|
122
|
+
num = self.to_i
|
123
|
+
|
124
|
+
values.each_with_index do |value, index|
|
125
|
+
count = num / value
|
126
|
+
result += numerals[index] * count
|
127
|
+
num -= value * count
|
128
|
+
end
|
129
|
+
|
130
|
+
result
|
131
|
+
end
|
132
|
+
|
133
|
+
# 10.20.cash('€') => "10.20€", 10.20.cash('$') => "$10.20"
|
134
|
+
def cash(currency = '$')
|
135
|
+
formatted = "%.2f" % self
|
136
|
+
|
137
|
+
# Currency positioning logic
|
138
|
+
case currency.to_s
|
139
|
+
when '$', '¥', '₩', '₹', '₪' # Prefix currencies
|
140
|
+
"#{currency}#{formatted}"
|
141
|
+
when '€', '£', '₽', 'kr', 'zł' # Suffix currencies
|
142
|
+
"#{formatted}#{currency}"
|
143
|
+
else # Unknown - default to prefix
|
144
|
+
"#{currency}#{formatted}"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
module String
|
150
|
+
# "hello world-foo" => "Hello World Foo" (smart about separators)
|
151
|
+
def title
|
152
|
+
self.split(/[\s\-_]+/).map(&:capitalize).join(' ')
|
153
|
+
end
|
154
|
+
|
155
|
+
# "John Wesley Doe" => "JWD"
|
156
|
+
def initials
|
157
|
+
self.split(/\s+/).map { |word| word[0]&.upcase }.compact.join
|
158
|
+
end
|
159
|
+
|
160
|
+
# "hello_world" => "HelloWorld"
|
161
|
+
def camel
|
162
|
+
self.split(/[\s\-_]+/).map.with_index { |word, i|
|
163
|
+
i == 0 ? word.downcase : word.capitalize
|
164
|
+
}.join
|
165
|
+
end
|
166
|
+
|
167
|
+
# "HelloWorld" => "hello_world"
|
168
|
+
def snake
|
169
|
+
self.gsub(/([A-Z])/, '_\1').downcase.sub(/^_/, '')
|
170
|
+
end
|
171
|
+
|
172
|
+
# "Hello World!" => "hello-world"
|
173
|
+
def slug
|
174
|
+
self.downcase
|
175
|
+
.gsub(/[^\w\s\-]/, '') # Remove special chars except spaces/hyphens
|
176
|
+
.strip
|
177
|
+
.gsub(/[\s\-_]+/, '-') # Convert spaces/underscores to hyphens
|
178
|
+
.gsub(/\-+/, '-') # Collapse multiple hyphens
|
179
|
+
.gsub(/^\-|\-$/, '') # Remove leading/trailing hyphens
|
180
|
+
end
|
181
|
+
|
182
|
+
# "1234567890" => "(123) 456-7890"
|
183
|
+
def phone
|
184
|
+
digits = self.gsub(/\D/, '')
|
185
|
+
return self if digits.length != 10
|
186
|
+
|
187
|
+
"(#{digits[0..2]}) #{digits[3..5]}-#{digits[6..9]}"
|
188
|
+
end
|
189
|
+
|
190
|
+
# "Long text here".snip(10) => "Long te..." (smart ellipsis)
|
191
|
+
def snip(length, ellipsis = '...')
|
192
|
+
return self if self.length <= length
|
193
|
+
return self if length < ellipsis.length + 1 # Don't make it longer!
|
194
|
+
|
195
|
+
# Smart truncation - try to break on word boundary near the limit
|
196
|
+
if length > 10
|
197
|
+
word_break = self[0..length-ellipsis.length].rindex(/\s/)
|
198
|
+
if word_break && word_break > length * 0.7 # Only if reasonably close
|
199
|
+
return self[0..word_break-1] + ellipsis
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
self[0..length-ellipsis.length-1] + ellipsis
|
204
|
+
end
|
205
|
+
|
206
|
+
# "hello world".hilite("world") => "hello <mark>world</mark>"
|
207
|
+
def hilite(term)
|
208
|
+
return self if term.nil? || term.empty?
|
209
|
+
self.gsub(/(#{Regexp.escape(term)})/i, '<mark>\1</mark>')
|
210
|
+
end
|
211
|
+
|
212
|
+
# "Secret info here".redact(/\w{6,}/) => "****** info ****"
|
213
|
+
def redact(pattern = /\w{4,}/)
|
214
|
+
self.gsub(pattern) { |match| '*' * match.length }
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
module Time
|
219
|
+
# 2.hours.ago.ago => "2h ago" (smart recent time)
|
220
|
+
def ago(format = :short)
|
221
|
+
diff = ::Time.now - self
|
222
|
+
return 'just now' if diff < 60
|
223
|
+
|
224
|
+
case format
|
225
|
+
when :short
|
226
|
+
case diff
|
227
|
+
when 0...3600 then "#{(diff/60).to_i}m ago"
|
228
|
+
when 3600...86400 then "#{(diff/3600).to_i}h ago"
|
229
|
+
when 86400...2592000 then "#{(diff/86400).to_i}d ago"
|
230
|
+
else strftime('%b %d')
|
231
|
+
end
|
232
|
+
when :long
|
233
|
+
case diff
|
234
|
+
when 0...3600 then "#{(diff/60).to_i} minutes ago"
|
235
|
+
when 3600...86400 then "#{(diff/3600).to_i} hours ago"
|
236
|
+
when 86400...2592000 then "#{(diff/86400).to_i} days ago"
|
237
|
+
else strftime('%B %d, %Y')
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
module Date
|
244
|
+
# Date.today.weekday => "Mon", Date.today.weekday(:long) => "Monday"
|
245
|
+
def weekday(format = :short)
|
246
|
+
format == :long ? strftime('%A') : strftime('%a')
|
247
|
+
end
|
248
|
+
|
249
|
+
# Date.new(2024,6,15).season => "Summer"
|
250
|
+
def season
|
251
|
+
month = self.month
|
252
|
+
case month
|
253
|
+
when 12, 1, 2 then 'Winter'
|
254
|
+
when 3, 4, 5 then 'Spring'
|
255
|
+
when 6, 7, 8 then 'Summer'
|
256
|
+
when 9, 10, 11 then 'Fall'
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# Date.today.smart => "Today", Date.yesterday.smart => "Yesterday"
|
261
|
+
def smart
|
262
|
+
today = Date.today
|
263
|
+
return 'Today' if self == today
|
264
|
+
return 'Yesterday' if self == today - 1
|
265
|
+
return 'Tomorrow' if self == today + 1
|
266
|
+
|
267
|
+
# This year - show month/day
|
268
|
+
if self.year == today.year
|
269
|
+
strftime('%b %d')
|
270
|
+
else
|
271
|
+
strftime('%b %d, %Y')
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# Date.today.schedule("+2d") => 2 days from now, Date.today.schedule("-1w") => 1 week ago
|
276
|
+
def schedule(offset_string)
|
277
|
+
match = offset_string.match(/([+-]?)(\d+)([dwmyh])/)
|
278
|
+
return self unless match
|
279
|
+
|
280
|
+
sign, amount, unit = match.captures
|
281
|
+
multiplier = sign == '-' ? -1 : 1
|
282
|
+
amount = amount.to_i * multiplier
|
283
|
+
|
284
|
+
case unit
|
285
|
+
when 'd' # days
|
286
|
+
self + amount
|
287
|
+
when 'w' # weeks
|
288
|
+
self + (amount * 7)
|
289
|
+
when 'm' # months
|
290
|
+
self >> amount # Use Ruby's month arithmetic
|
291
|
+
when 'y' # years
|
292
|
+
self >> (amount * 12)
|
293
|
+
when 'h' # hours (convert to datetime)
|
294
|
+
(self.to_time + (amount * 3600)).to_date
|
295
|
+
else
|
296
|
+
self
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
module Integer
|
302
|
+
# 7265.duration => "2h 1m", 90.duration => "1m 30s"
|
303
|
+
def duration(format = :short)
|
304
|
+
seconds = self
|
305
|
+
return '0s' if seconds == 0
|
306
|
+
|
307
|
+
hours = seconds / 3600
|
308
|
+
minutes = (seconds % 3600) / 60
|
309
|
+
secs = seconds % 60
|
310
|
+
|
311
|
+
parts = []
|
312
|
+
parts << "#{hours}h" if hours > 0
|
313
|
+
parts << "#{minutes}m" if minutes > 0
|
314
|
+
parts << "#{secs}s" if secs > 0 && hours == 0 # Only show seconds if under 1 hour
|
315
|
+
|
316
|
+
parts.join(' ')
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# Apply the extensions
|
324
|
+
Numeric.include Joys::Toys::CoreExtensions::Numeric
|
325
|
+
String.include Joys::Toys::CoreExtensions::String
|
326
|
+
Time.include Joys::Toys::CoreExtensions::Time
|
327
|
+
Date.include Joys::Toys::CoreExtensions::Date
|
328
|
+
Integer.include Joys::Toys::CoreExtensions::Integer
|
data/lib/joys/version.rb
CHANGED
data/lib/joys.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: joys
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Steven Garcia
|
@@ -18,7 +18,6 @@ executables: []
|
|
18
18
|
extensions: []
|
19
19
|
extra_rdoc_files: []
|
20
20
|
files:
|
21
|
-
- ".DS_Store"
|
22
21
|
- CHANGELOG.md
|
23
22
|
- CODE_OF_CONDUCT.md
|
24
23
|
- LICENSE
|
@@ -27,12 +26,18 @@ files:
|
|
27
26
|
- Rakefile
|
28
27
|
- joys-0.1.0.gem
|
29
28
|
- joys-0.1.1.gem
|
29
|
+
- joys-0.1.2.gem
|
30
30
|
- lib/.DS_Store
|
31
31
|
- lib/joys.rb
|
32
|
+
- lib/joys/cli.rb
|
33
|
+
- lib/joys/config.rb
|
32
34
|
- lib/joys/core.rb
|
35
|
+
- lib/joys/data.rb
|
33
36
|
- lib/joys/helpers.rb
|
37
|
+
- lib/joys/ssg.rb
|
34
38
|
- lib/joys/styles.rb
|
35
39
|
- lib/joys/tags.rb
|
40
|
+
- lib/joys/toys.rb
|
36
41
|
- lib/joys/version.rb
|
37
42
|
- sig/joys.rbs
|
38
43
|
homepage: https://github.com/activestylus/joys.git
|
data/.DS_Store
DELETED
Binary file
|