Class: Remind::Reminder

Inherits:
Object
  • Object
show all
Defined in:
lib/remind/reminder.rb

Overview

One REM line, parsed by Remind.

This is the binding that exists because converting a reminder file to anything else is otherwise a re-implementation of Remind. A trigger like REM Mon 13 SKIP OMIT Sat MSG x means "the Monday of the week the 13th falls in, unless that is a Saturday, in which case skip it" -- and every program that has tried to read that with a regular expression has got some of it wrong. Here the parse is ParseRem, the first date is ComputeTrigger, the message is DoSubst, and the dates after the first are ComputeTrigger again from further along. Nothing about the date specification is interpreted on this side.

The fields are Remind's, with two translations and no others: months are 1..12 rather than 0..11, and a field the reminder did not mention is nil rather than Remind's sentinel for it.

Constant Summary collapse

WEEKDAY_BITS =

What Remind numbers the weekday bits from: bit 0 is Monday.

%w[MO TU WE TH FR SA SU].freeze
DISPLAY_TYPES =

A body a calendar can show. RUN reminders execute a shell command, CAL and PS reminders draw on a PostScript calendar; none of them is an event.

%i[msg msf].freeze
TYPE_NAMES =
{
  msg_type:      :msg,
  msf_type:      :msf,
  run_type:      :run,
  cal_type:      :cal,
  sat_type:      :satisfy,
  passthru_type: :passthru,
}.freeze
DEFAULT_LIMIT =

How far a walk will go looking for the next occurrence before deciding there isn't one. Remind answers "cannot compute trigger" on its own for most run-out reminders; this bounds the ones it would keep answering.

100

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(line, session: Session.new) ⇒ Reminder

Returns a new instance of Reminder.



59
60
61
62
63
# File 'lib/remind/reminder.rb', line 59

def initialize(line, session: Session.new)
  @line = line
  @session = session
  @native = session.native
end

Instance Attribute Details

#descriptionObject (readonly)

Returns the value of attribute description.



47
48
49
# File 'lib/remind/reminder.rb', line 47

def description
  @description
end

#firstObject (readonly)

Returns the value of attribute first.



47
48
49
# File 'lib/remind/reminder.rb', line 47

def first
  @first
end

#lineObject (readonly)

Returns the value of attribute line.



47
48
49
# File 'lib/remind/reminder.rb', line 47

def line
  @line
end

#sessionObject (readonly)

Returns the value of attribute session.



47
48
49
# File 'lib/remind/reminder.rb', line 47

def session
  @session
end

#typeObject (readonly)

Returns the value of attribute type.



47
48
49
# File 'lib/remind/reminder.rb', line 47

def type
  @type
end

Class Method Details

.parse(line, session: Session.new) ⇒ Object

nil when the line is not a reminder Remind can parse into an event: a comment, a SET, a REM with no body, a trigger that never fires.



52
53
54
55
56
# File 'lib/remind/reminder.rb', line 52

def parse(line, session: Session.new)
  new(line, session: session).tap(&:parse).presented
rescue EvaluationError
  nil
end

Instance Method Details

#as_of(date) ⇒ Object

The same reminder as it reads on a given day.

A message is not fixed text: %b is "in 27 days' time" or "today" depending on when it is read, and Remind expands it against whatever DSEToday says at the moment it renders. remind therefore prints a different message for each occurrence, and anything showing one occurrence should show the message that occurrence would have had.

Today is process-wide, so it is pinned and put back.



194
195
196
197
198
199
200
201
# File 'lib/remind/reminder.rb', line 194

def as_of(date)
  was = session.today

  session.today = date
  self.class.parse(line, session: session)
ensure
  session.today = was
end

#atObject

Minutes since midnight, or nil for an all-day reminder.



137
138
139
140
141
142
143
144
145
# File 'lib/remind/reminder.rb', line 137

def at
  time = timetrig_field(:time)

  if time == native.no_time
    nil
  else
    time
  end
end

#backObject

The -n in REM 1 -1: how many days back from the date the trigger names. REM 1 -1 is the last day of the month before.



116
117
118
# File 'lib/remind/reminder.rb', line 116

def back
  positive(trigger_field(:back))
end

#dayObject

The day of the month, or nil.



68
69
70
# File 'lib/remind/reminder.rb', line 68

def day
  field(:d, native.no_day)
end

#display?Boolean

Returns:

  • (Boolean)


181
182
183
# File 'lib/remind/reminder.rb', line 181

def display?
  DISPLAY_TYPES.include?(type)
end

#durationObject

DURATION, in minutes, or nil. Remind fills this with the same "no time given" sentinel as the AT clause, not with zero.



149
150
151
152
153
154
155
156
157
# File 'lib/remind/reminder.rb', line 149

def duration
  given = timetrig_field(:duration)

  if given == native.no_time
    nil
  else
    positive(given)
  end
end

#errorObject



240
241
242
243
244
# File 'lib/remind/reminder.rb', line 240

def error
  if @code&.positive?
    session.error_message(@code)
  end
end

#monthObject

1..12, or nil. Remind counts months from zero; this does not.



73
74
75
76
77
78
79
# File 'lib/remind/reminder.rb', line 73

def month
  zero_based = field(:m, native.no_month)

  if zero_based
    zero_based + 1
  end
end

#occurrences(limit: DEFAULT_LIMIT) ⇒ Object

Every date this reminder triggers on, starting at the first, asked of Remind one at a time. Lazy, because a daily reminder has no last one.



168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/remind/reminder.rb', line 168

def occurrences(limit: DEFAULT_LIMIT)
  Enumerator.new do |dates|
    cursor = first_dse
    taken = 0

    while cursor >= 0 && taken < limit
      dates << session.date(cursor)
      taken += 1
      cursor = next_dse(cursor + 1)
    end
  end
end

#omits_weekdays?Boolean

Returns:

  • (Boolean)


126
127
128
# File 'lib/remind/reminder.rb', line 126

def omits_weekdays?
  trigger_field(:localomit) != 0
end

#once?Boolean

Returns:

  • (Boolean)


130
131
132
# File 'lib/remind/reminder.rb', line 130

def once?
  trigger_field(:once) != 0
end

#parseObject

--- parsing ----------------------------------------------------------



205
206
207
208
209
210
211
212
# File 'lib/remind/reminder.rb', line 205

def parse
  @trigger = Fiddle::Pointer.malloc(native.sizeof_trigger, Fiddle::RUBY_FREE)
  @timetrig = Fiddle::Pointer.malloc(native.sizeof_timetrig, Fiddle::RUBY_FREE)

  @code, @first_dse, @description = expand(native.normal_mode, @trigger, @timetrig)
  @type = TYPE_NAMES[type_name]
  @first = read_first
end

#parsed?Boolean

Returns:

  • (Boolean)


236
237
238
# File 'lib/remind/reminder.rb', line 236

def parsed?
  @code&.zero? && @first_dse >= 0
end

#presentedObject

nil rather than an unusable object, for the caller that is walking a file and does not want to ask about every line twice.



230
231
232
233
234
# File 'lib/remind/reminder.rb', line 230

def presented
  if parsed?
    self
  end
end

#repeatObject

REM 1 *7 repeats every seven days. nil when the reminder does not.



95
96
97
# File 'lib/remind/reminder.rb', line 95

def repeat
  positive(trigger_field(:rep))
end

#skipObject

Whether the reminder skips or moves over OMITted days, which is a thing no RRULE can say.



122
123
124
# File 'lib/remind/reminder.rb', line 122

def skip
  trigger_field(:skip)
end

#summaryObject

The message as a calendar wants it: %"…%" marks the part of a reminder that is the entry's title, and CAL_MODE is the mode that honours it. A reminder that marks nothing has the whole message for a title.

It costs a second parse, because DoSubst consumes the parser as it renders and there is only one message in a line either way.



220
221
222
223
224
225
226
# File 'lib/remind/reminder.rb', line 220

def summary
  @summary ||= expand(
    native.cal_mode,
    Fiddle::Pointer.malloc(native.sizeof_trigger, Fiddle::RUBY_FREE),
    Fiddle::Pointer.malloc(native.sizeof_timetrig, Fiddle::RUBY_FREE),
  ).last
end

#until_dateObject

The last date the reminder may trigger on -- UNTIL or THROUGH -- as a Date, or nil.



101
102
103
104
105
106
107
# File 'lib/remind/reminder.rb', line 101

def until_date
  last = field(:until, native.no_until)

  if last
    session.date(last)
  end
end

#warning_daysObject

Advance warning, in days: the +n in REM 15 +3.



110
111
112
# File 'lib/remind/reminder.rb', line 110

def warning_days
  positive(trigger_field(:delta))
end

#warning_minutesObject

The alarm on an AT clause: AT 9:30 +15, in minutes.



160
161
162
# File 'lib/remind/reminder.rb', line 160

def warning_minutes
  positive(timetrig_field(:delta))
end

#weekdaysObject

The weekdays named in the trigger, as RFC 5545 spells them, in Remind's bit order: REM Mon Wed Fri is ["MO", "WE", "FR"].



87
88
89
90
91
92
# File 'lib/remind/reminder.rb', line 87

def weekdays
  bits = trigger_field(:wd)

  WEEKDAY_BITS.each_index.select { |index| bits.anybits?(1 << index) }
              .map { |index| WEEKDAY_BITS[index] }
end

#yearObject



81
82
83
# File 'lib/remind/reminder.rb', line 81

def year
  field(:y, native.no_year)
end