truenorth 0.2.3 → 0.2.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5f3438794269dffcf86a880d84a026215705c4f209224788e6ea23455b14ea1
4
- data.tar.gz: 01de8411d76be106e62a870885011c27cd10ec2df715af9300be9c1c6c545b75
3
+ metadata.gz: 63c9a467b51f61d550f8c48a37109dbc647d5b599044beffc55333f0bade9e48
4
+ data.tar.gz: 12211afbaf407921821f41c2cddcc9c9581ad9e6f8e1ffef20ef83a8ec6df91e
5
5
  SHA512:
6
- metadata.gz: 5a9f8bb510b0d8e697dba433e05ff634dbb433a449afffbfef66eebb1cd0c371bc8533d3e858f397642ee5e722987d71693395a2b2133ef21f4281f93e5c3820
7
- data.tar.gz: d383619f1d8df6b96f1628a7e6b642a6739b1f3ba7c74c4d0d8fa8b944a8b2f92f75e73d9be482a84654f8b15ec8de22d05153b7f7c29d3adbbc4949b415bd7f
6
+ metadata.gz: 2c0538cf12be2ff1a20f5950206b381537683bf3e5cfe87ee9895e7fe677154bebbd636df6837ed3ad0a136018060f982f697d875d456e1402cc4ef4d57e161a
7
+ data.tar.gz: 7874967a9ce9b6a7342e82ca8a5346dbd1fc0ccfe87a099dbeda0ac0201e9765ea9a8fbe3a36f8e9eff1eece1fb4ac12719f8ff5eb9777da2bef98560dfd7be4
data/lib/truenorth/cli.rb CHANGED
@@ -59,23 +59,28 @@ module Truenorth
59
59
  exit 1
60
60
  end
61
61
 
62
- desc 'book TIME', 'Book a slot at the specified time'
62
+ desc 'book TIME_OR_DESCRIPTION', 'Book a slot (e.g., "10am", "feb 15 10am", "squash feb 15 10am")'
63
63
  option :date, aliases: '-d', desc: 'Date (YYYY-MM-DD, or +N for days from today)'
64
64
  option :court, aliases: '-c', desc: 'Preferred court (e.g., "Court 1", "Squash Court 2")'
65
65
  option :activity, aliases: '-a', default: 'squash', desc: 'Activity type'
66
66
  option :dry_run, type: :boolean, aliases: '-n', desc: 'Test without actually booking'
67
- def book(time)
68
- date = parse_date(options[:date])
67
+ def book(time_or_description)
68
+ # Parse natural language input
69
+ parsed = parse_booking_request(time_or_description, options[:date], options[:activity])
70
+
71
+ date = parsed[:date]
72
+ time = parsed[:time]
73
+ activity = parsed[:activity]
69
74
  client = Client.new
70
75
 
71
76
  mode = options[:dry_run] ? ' (DRY RUN)' : ''
72
- say "Booking #{options[:activity]} at #{time} on #{date}#{mode}...", :cyan
77
+ say "Booking #{activity} at #{time} on #{date}#{mode}...", :cyan
73
78
 
74
79
  result = client.book(
75
80
  time,
76
81
  date: date,
77
82
  court: options[:court],
78
- activity: options[:activity],
83
+ activity: activity,
79
84
  dry_run: options[:dry_run]
80
85
  )
81
86
 
@@ -139,15 +144,27 @@ module Truenorth
139
144
 
140
145
  desc 'cancel INDEX', 'Cancel a reservation by index (from reservations list)'
141
146
  option :dry_run, type: :boolean, aliases: '-n', desc: 'Test without actually canceling'
147
+ option :all, type: :boolean, aliases: '-a', desc: 'Cancel from full list (use with --all view)'
142
148
  def cancel(index)
143
149
  client = Client.new
144
150
 
145
151
  say 'Fetching reservations...', :cyan
146
152
  results = client.reservations
147
153
 
154
+ # Apply same filter as reservations command
155
+ unless options[:all]
156
+ results = results.select { |r| r[:member].nil? }
157
+ if results.empty?
158
+ say 'You have no reservations to cancel.', :yellow
159
+ say "Use 'truenorth cancel INDEX --all' to cancel family members' reservations", :cyan
160
+ exit 1
161
+ end
162
+ end
163
+
148
164
  idx = index.to_i - 1
149
165
  if idx < 0 || idx >= results.length
150
- say "Invalid index. Must be between 1 and #{results.length}", :red
166
+ filter_note = options[:all] ? '' : ' (your reservations only)'
167
+ say "Invalid index. Must be between 1 and #{results.length}#{filter_note}", :red
151
168
  exit 1
152
169
  end
153
170
 
@@ -305,6 +322,65 @@ module Truenorth
305
322
  "#{text[0...max_length - 1]}…"
306
323
  end
307
324
 
325
+ def parse_booking_request(input, date_option, activity_option)
326
+ # Extract activity if mentioned (squash, golf, music, room)
327
+ activity = activity_option
328
+ if input =~ /\b(squash|golf|music|room)\b/i
329
+ activity = ::Regexp.last_match(1).downcase
330
+ input = input.gsub(/\b(squash|golf|music|room)\b/i, '').strip
331
+ end
332
+
333
+ # Extract date if present
334
+ date = nil
335
+ input_lower = input.downcase
336
+
337
+ # Try patterns like "feb 15", "february 15th", "2/15"
338
+ if input_lower =~ /\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\s+(\d{1,2})(st|nd|rd|th)?\b/i
339
+ month_str = ::Regexp.last_match(1)
340
+ day = ::Regexp.last_match(2).to_i
341
+
342
+ month_map = {
343
+ 'jan' => 1, 'feb' => 2, 'mar' => 3, 'apr' => 4,
344
+ 'may' => 5, 'jun' => 6, 'jul' => 7, 'aug' => 8,
345
+ 'sep' => 9, 'oct' => 10, 'nov' => 11, 'dec' => 12
346
+ }
347
+
348
+ month = month_map[month_str[0..2].downcase]
349
+ year = Date.today.year
350
+ year += 1 if month && month < Date.today.month # Next year if month has passed
351
+
352
+ date = Date.new(year, month, day) if month
353
+ input = input.gsub(/\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\s+(\d{1,2})(st|nd|rd|th)?\b/i, '').strip
354
+ elsif input =~ %r{\b(\d{1,2})/(\d{1,2})\b}
355
+ month = ::Regexp.last_match(1).to_i
356
+ day = ::Regexp.last_match(2).to_i
357
+ year = Date.today.year
358
+ date = Date.new(year, month, day)
359
+ input = input.gsub(%r{\b\d{1,2}/\d{1,2}\b}, '').strip
360
+ end
361
+
362
+ # Remove day names (monday, tuesday, etc.)
363
+ input = input.gsub(/\b(monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b/i, '').strip
364
+
365
+ # Extract time - look for patterns like "10am", "10:00am", "10:00 AM", "10"
366
+ time = nil
367
+ if input =~ /\b(\d{1,2})(:(\d{2}))?\s*(am|pm)?\b/i
368
+ hour = ::Regexp.last_match(1).to_i
369
+ minute = ::Regexp.last_match(3) || '00'
370
+ period = ::Regexp.last_match(4) || (hour < 8 ? 'PM' : 'AM')
371
+
372
+ time = "#{hour}:#{minute} #{period.upcase}"
373
+ end
374
+
375
+ # Use provided date option if date wasn't extracted
376
+ date ||= parse_date(date_option)
377
+
378
+ # If no time found, use the remaining input as-is
379
+ time ||= input.strip
380
+
381
+ { date: date, time: time, activity: activity }
382
+ end
383
+
308
384
  def parse_date(date_str)
309
385
  return Date.today if date_str.nil? || date_str.empty?
310
386
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Truenorth
4
- VERSION = '0.2.3'
4
+ VERSION = '0.2.5'
5
5
  end
data/truenorth.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'truenorth'
5
- spec.version = '0.2.3'
5
+ spec.version = '0.2.5'
6
6
  spec.authors = ['usiegj00']
7
7
  spec.email = ['112138+usiegj00@users.noreply.github.com']
8
8
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: truenorth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - usiegj00