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.
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Joys
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
5
5
  end
data/lib/joys.rb CHANGED
@@ -4,4 +4,5 @@ require_relative "joys/version"
4
4
  require_relative "joys/core"
5
5
  require_relative "joys/helpers"
6
6
  require_relative "joys/styles"
7
- require_relative "joys/tags"
7
+ require_relative "joys/tags"
8
+ require_relative "joys/config"
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.2
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