Class: Rbnotes::Utils

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/rbnotes/utils.rb

Overview

Defines several utility methods those are intended to be used in Rbnotes classes.

Constant Summary collapse

TIMESTAMP_DELIMITERS =

Acceptable delimiters to separate a timestamp string for human being to read and input easily.

Here is some examples:

- "2021-04-15 15:34:56" -> "20210415153456" (a timestamp string)
- "2020-04-15_15:34:56" -> (same as above)
- "2020-04-15-15-34-56" -> (same as above)
- "2020 04 15 15 34 56" -> (same as above)
- "2020-04-15" -> "20200415" (a timestamp pattern)
/[-:_\s]/

Instance Method Summary collapse

Instance Method Details

#expand_keyword_in_args(args) ⇒ Object

Parses the given arguments and expand keywords if found. Each of the arguments is assumed to represent a timestamp pattern (or a keyword to be expand into several timestamp pattern). Returns an Array of timestamp partterns (each pattern is a String object).

A timestamp pattern looks like:

(a) full qualified timestamp (with suffix): "20201030160200"
(b) year and date part: "20201030"
(c) year and month part: "202010"
(d) year part only: "2020"
(e) date part only: "1030"

KEYWORD:

- "today"      (or "to")
- "yeasterday" (or "ye")
- "this_week"  (or "tw")
- "last_week"  (or "lw")
- "this_month" (or "tm")
- "last_month" (or "lm")
- "all"

:call-seq:

expand_keyword_in_args(Array of Strings) -> Array of Strings


261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/rbnotes/utils.rb', line 261

def expand_keyword_in_args(args)
  args = args.dup
  patterns = []
  while args.size > 0
    arg = args.shift
    if arg == "all" or arg == "recent" or arg == "re"
      return [nil]
    elsif valid_keyword?(arg)
      patterns.concat(expand_keyword(arg))
    else
      patterns << arg
    end
  end
  patterns.uniq.sort
end

#find_editor(preferred_editor) ⇒ Object

Finds a external editor program which is specified with the argument, then returns the absolute path of the editor. If the specified editor was not found, then search default editors in the command search paths (i.e. ‘ENV). See also the document for `find_program`.

The default editors to search in the search paths are:

  1. ENV

  2. “nano”

  3. “vi”

When all the default editors were not found, returns ‘nil`.



46
47
48
# File 'lib/rbnotes/utils.rb', line 46

def find_editor(preferred_editor)
  find_program([preferred_editor, ENV["EDITOR"], "nano", "vi"].compact)
end

#find_notes(timestamp_patterns, repo, num_of_notes = 0) ⇒ Object

Finds all notes those timestamps match to given patterns in the given repository. Returns an Array contains Timestamp objects. The returned Array is sorted by Timestamp.

When a positive number was specified as the 3rd argument, the number was used as the limitation count of enumerated notes.

:call-seq:

find_notes(Array of timestamp patterns, Textrepo::Repository, Integer)


319
320
321
322
323
324
325
326
327
328
329
# File 'lib/rbnotes/utils.rb', line 319

def find_notes(timestamp_patterns, repo, num_of_notes = 0)
  notes = timestamp_patterns.map { |pat|
    repo.entries(pat)
  }.flatten.sort{ |a, b| b <=> a }.uniq

  if num_of_notes > 0
    notes[0,num_of_notes]
  else
    notes
  end
end

#find_program(names) ⇒ Object

Finds a executable program in given names. When the executable was found, it stops searching then returns an absolute path of the executable.

The actual searching is done in 2 cases. That is, a given name is:

  1. an absolute path: returns the path itself if it exists and is executable.

  2. just a program name: searchs the name in the search paths (ENV); if it is found in a path, construct an absolute path from the name and the path, then returns the path.

:call-seq:

["nano", "vi"]                 -> "/usr/bin/nano"
["vi", "/usr/local/bin/emacs"] -> "/usr/bin/vi"
["/usr/local/bin/emacs", "vi"] -> "/usr/bin/vi" (if emacs doesn't exist)
["/usr/local/bin/emacs", "vi"] -> "/usr/local/bin/emacs" (if exists)


70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/rbnotes/utils.rb', line 70

def find_program(names)
  names.each { |name|
    pathname = Pathname.new(name)
    if pathname.absolute?
      return pathname.to_path if pathname.exist? && pathname.executable?
    else
      abs = search_in_path(name)
      return abs unless abs.nil?
    end
  }
  nil
end

#make_headline(timestamp, text, pad = nil) ⇒ Object

Makes a headline with the timestamp and subject of the notes, it looks like as follows:

|<--------------- console column size -------------------->|
|   |+-- timestamp ---+  +-subject (the 1st line of note) -+
|                     |  |                                 |
|   |20101010001000_123: I love Macintosh.                 [EOL]
|   |20100909090909_999: This is very very long looong subj[EOL]
|<->|                 |  |
  ^--- pad             ++
                       ^--- delimiter (2 characters)

The subject part will truncate when it is long.



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/rbnotes/utils.rb', line 292

def make_headline(timestamp, text, pad = nil)
  _, column = IO.console_size
  delimiter = ": "
  timestamp_width = timestamp.to_s.size
  subject_width = column - timestamp_width - delimiter.size - 1
  subject_width -= pad.size unless pad.nil?

  subject = remove_heading_markup(text[0])

  ts_part = "#{timestamp.to_s}    "[0..(timestamp_width - 1)]
  ts_part.prepend(pad) unless pad.nil?
  sj_part = truncate_str(subject, subject_width)

  ts_part + delimiter + sj_part
end

#read_multiple_timestamps(args) ⇒ Object

Generates multiple Textrepo::Timestamp objects from the command line arguments. When no argument is given, try to read from STDIN.

When multiple strings those point the identical time are included the arguments (passed or read form STDIN), the redundant strings will be removed.

The order of the arguments will be preserved into the return value, even if the redundant strings were removed.

:call-seq:

read_multiple_timestamps(args) -> [String]

Raises:



152
153
154
155
156
157
158
159
# File 'lib/rbnotes/utils.rb', line 152

def read_multiple_timestamps(args)
  strings = args.size < 1 ? read_multiple_args($stdin) : args
  raise NoArgumentError if (strings.nil? || strings.empty?)
  strings.uniq.map { |str|
    str = remove_delimiters_from_timestamp_string(str)
    Textrepo::Timestamp.parse_s(str)
  }
end

#read_timestamp(args) ⇒ Object

Generates a Textrepo::Timestamp object from a String which comes from the command line arguments. When no argument is given, then reads from STDIN.

:call-seq:

read_timestamp(args) -> String

Raises:



127
128
129
130
131
132
133
134
135
# File 'lib/rbnotes/utils.rb', line 127

def read_timestamp(args)
  args = args.dup

  str = args.shift || read_arg($stdin)
  raise NoArgumentError if str.nil?

  str = remove_delimiters_from_timestamp_string(str)
  Textrepo::Timestamp.parse_s(str)
end

#read_timestamp_patterns(args, enum_week: false) ⇒ Object

Reads timestamp patterns in an array of arguments. It supports keywords expansion and enumeration of week. The function is intended to be used from Commands::List#execute and Commands::Pick#execute.



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/rbnotes/utils.rb', line 167

def read_timestamp_patterns(args, enum_week: false)
  args = args.dup

  validate_arguments(args)
  patterns = nil
  if enum_week
    args.unshift(Time.now.strftime("%Y%m%d")) if args.size == 0
    patterns = []
    while args.size > 0
      arg = args.shift
      begin
        patterns.concat(timestamp_patterns_in_week(arg.dup))
      rescue InvalidTimestampPatternAsDateError => _e
        raise InvalidTimestampPatternAsDateError, args.unshift(arg)
      end
    end
  else
    patterns = expand_keyword_in_args(args)
  end
  patterns
end

#run_with_tmpfile(prog, filename, initial_content = nil) ⇒ Object

Executes the program with passing the given filename as argument. The file will be created into ‘Dir.tmpdir`.

If initial_content is not nil, it must be an array of strings then it provides the initial content of a temporary file.

:call-seq:

"/usr/bin/nano", "20201021131300.md", nil -> "/somewhere/tmpdir/20201021131300.md"
"/usr/bin/vi", "20201021131301.md", ["apple\n", "orange\n"] -> "/somewhere/tmpdir/20201021131301.md"

Raises:



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/rbnotes/utils.rb', line 94

def run_with_tmpfile(prog, filename, initial_content = nil)
  tmpfile = File.expand_path(add_extension(filename), Dir.tmpdir)

  unless initial_content.nil?
    File.open(tmpfile, "w") {|f| f.print(initial_content.join("\n"))}
  end

  rc = system(prog, tmpfile)
  raise ProgramAbortError, [prog, tmpfile] unless rc
  tmpfile
end

#specified_recent?(args) ⇒ Boolean

Returns:

  • (Boolean)


331
332
333
334
# File 'lib/rbnotes/utils.rb', line 331

def specified_recent?(args)
  validate_arguments(args)
  args.include?("recent") or args.include?("re")
end

#timestamp_patterns_in_week(arg) ⇒ Object

Enumerates all timestamp patterns in a week which contains a given timestamp as a day of the week.

The argument must be one of the followings:

- "yyyymodd" (eg. "20201220")
- "yymoddhhmiss" (eg. "20201220120048")
- "yymoddhhmiss_sfx" (eg. "20201220120048_012")
- "modd" (eg. "1220") (assums in the current year)
- nil (assumes today)

:call-seq:

timestamp_patterns_in_week(String) -> [Array of Strings]


203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/rbnotes/utils.rb', line 203

def timestamp_patterns_in_week(arg)
  date_str = nil

  if arg
    date_str = remove_delimiters_from_timestamp_string(arg)
  else
    date_str = Textrepo::Timestamp.now[0, 8]
  end

  case date_str.size
  when "yyyymodd".size
    # nothing to do
  when "yyyymoddhhmiss".size, "yyyymoddhhmiss_sfx".size
    date_str = date_str[0, 8]
  when "modd".size
    this_year = Time.now.year.to_s
    date_str = "#{this_year}#{date_str}"
  else
    raise InvalidTimestampPatternAsDateError, arg
  end

  begin
    date = Date.parse(date_str)
  rescue Date::Error => _e
    raise InvalidTimestampPatternAsDateError, arg
  end

  dates_in_week(date).map { |date| timestamp_pattern(date) }
end