Class: Fatty::History

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/fatty/history.rb,
lib/fatty/history/entry.rb

Defined Under Namespace

Classes: Entry

Constant Summary collapse

DEFAULT_HISTORY_FILE =
File.expand_path("~/.fatty_history")
DEFAULT_HISTORY_MAX =
10_000

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path: nil, max: nil) ⇒ History

Returns a new instance of History.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/fatty/history.rb', line 12

def initialize(path: nil, max: nil)
  @path =
    case path
    when :default
      Config.config.dig(:history, :file) || DEFAULT_HISTORY_FILE
    when nil, false
      nil
    else
      path
    end
  @path = File.expand_path(@path) if @path
  @max = max&.to_i || Config.config.dig(:history, :max)&.to_i || DEFAULT_HISTORY_MAX
  @entries = []
  @cursors = {}

  if @path
    Fatty.info("History loaded from #{@path}", tag: :config)
    load
  else
    Fatty.info("In-memory History only: no path", tag: :config)
  end
end

Instance Attribute Details

#entriesObject (readonly)

Returns the value of attribute entries.



10
11
12
# File 'lib/fatty/history.rb', line 10

def entries
  @entries
end

Class Method Details

.defaultObject

A global default History object available to all sessions. Sets a class instance variable.



37
38
39
# File 'lib/fatty/history.rb', line 37

def self.default
  for_path(:default)
end

.for_path(path = :default) ⇒ Object



41
42
43
44
# File 'lib/fatty/history.rb', line 41

def self.for_path(path = :default)
  @instances ||= {}
  @instances[path] ||= new(path: path)
end

.normalize_ctx(ctx) ⇒ Object



183
184
185
186
187
188
189
# File 'lib/fatty/history.rb', line 183

def self.normalize_ctx(ctx)
  return {} unless ctx.is_a?(Hash)

  ctx.each_with_object({}) { |(key, value), memo|
    memo[key.to_s] = value
  }.sort.to_h
end

.reset_instances!Object



46
47
48
# File 'lib/fatty/history.rb', line 46

def self.reset_instances!
  @instances = {}
end

Instance Method Details

#add(text, kind: :command, ctx: nil, stamp: nil, persist: true) ⇒ Object

Manipulating History



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/fatty/history.rb', line 83

def add(text, kind: :command, ctx: nil, stamp: nil, persist: true)
  text = text.to_s
  return if text.strip.empty?

  kind = kind.to_sym
  ctx = normalize_ctx(ctx)
  entry = Entry.new(text: text, kind: kind, ctx: ctx, stamp: stamp)

  @entries.reject! do |old_entry|
    old_entry.text == text &&
      old_entry.kind == kind &&
      old_entry.ctx == ctx
  end

  @entries << entry
  truncate!
  append_to_file(entry) if persist
  reset_cursor_for(kind, ctx: ctx)
  text
end

#each(&block) ⇒ Object



56
57
58
59
60
# File 'lib/fatty/history.rb', line 56

def each(&block)
  return enum_for(:each) unless block

  @entries.each(&block)
end

#for(kind: nil, ctx: nil) ⇒ Object



62
63
64
65
66
67
68
69
70
71
# File 'lib/fatty/history.rb', line 62

def for(kind: nil, ctx: nil)
  return enum_for(:for, kind: kind, ctx: ctx) unless block_given?

  each do |entry|
    next if kind && entry.kind != kind.to_sym
    next if ctx && !ctx_match?(entry, ctx)

    yield entry
  end
end

#nextObject



108
109
110
# File 'lib/fatty/history.rb', line 108

def next
  next_for(:command)
end

#next_for(*kinds, ctx: nil) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/fatty/history.rb', line 139

def next_for(*kinds, ctx: nil)
  ctx = normalize_ctx(ctx)
  cursor = cursor_for(*kinds, ctx: ctx)
  the_entries = entries_for(*kinds, ctx: ctx, prefix: cursor[:prefix])
  return "" if the_entries.empty? || cursor[:index].nil?

  if cursor[:index] < the_entries.length - 1
    cursor[:index] += 1
    the_entries[cursor[:index]].text
  else
    scratch = cursor[:scratch]
    reset_cursor_for(*kinds, ctx: ctx)
    scratch || ""
  end
end

#previous(current) ⇒ Object



104
105
106
# File 'lib/fatty/history.rb', line 104

def previous(current)
  previous_for(:command, current: current)
end

#previous_for(*kinds, current:, ctx: nil) ⇒ Object



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/fatty/history.rb', line 116

def previous_for(*kinds, current:, ctx: nil)
  ctx = normalize_ctx(ctx)
  cursor = cursor_for(*kinds, ctx: ctx)
  prefix = cursor[:prefix]

  if cursor[:index].nil?
    cursor[:scratch] = current.to_s
    cursor[:prefix] = current.to_s
    prefix = cursor[:prefix]
  end

  the_entries = entries_for(*kinds, ctx: ctx, prefix: prefix)
  return current.to_s if the_entries.empty?

  if cursor[:index].nil?
    cursor[:index] = the_entries.length - 1
    return the_entries[cursor[:index]].text
  end

  cursor[:index] -= 1 if cursor[:index].positive?
  the_entries[cursor[:index]].text
end

#recent(kind: nil, ctx: nil, limit: nil) ⇒ Object



73
74
75
76
77
# File 'lib/fatty/history.rb', line 73

def recent(kind: nil, ctx: nil, limit: nil)
  rows = self.for(kind: kind, ctx: ctx).to_a
  rows = rows.last(limit) if limit
  rows.reverse
end

#reset_cursorObject



112
113
114
# File 'lib/fatty/history.rb', line 112

def reset_cursor
  reset_cursor_for(:command)
end

#reset_cursor_for(*kinds, ctx: nil) ⇒ Object



155
156
157
158
159
160
161
# File 'lib/fatty/history.rb', line 155

def reset_cursor_for(*kinds, ctx: nil)
  ctx = normalize_ctx(ctx)
  cursor = cursor_for(*kinds, ctx: ctx)
  cursor[:index] = nil
  cursor[:prefix] = nil
  cursor[:scratch] = nil
end

#suggestions_for(*kinds, prefix:, ctx: nil, limit: nil) ⇒ Object



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/fatty/history.rb', line 163

def suggestions_for(*kinds, prefix:, ctx: nil, limit: nil)
  text = prefix.to_s
  return [] if text.empty?

  wanted_ctx = normalize_ctx(ctx)

  rows =
    if wanted_ctx.empty?
      entries_for(*kinds, prefix: text)
    else
      local = entries_for(*kinds, ctx: wanted_ctx, prefix: text)
      global = entries_for(*kinds, prefix: text)
      local + global
    end

  rows = rows.reverse.map(&:text).uniq
  rows = rows.first(limit) if limit
  rows
end