Class: Booker::Bookmarks

Inherits:
Object
  • Object
show all
Defined in:
lib/booker/bookmarks.rb

Overview

Main Bookmarks facade class

Constant Summary collapse

BROWSERS =

one row per browser: everything booker knows about a bookmark source lives here, so a fourth browser is an entry rather than a hunt through the parser lookup, the installer's labels and its colors. chrome's file has no extension, so it is the fallback rather than a row to match on

{
  firefox: {parser: Parsers::Firefox, ext: ".sqlite", label: "[Firefox]", color: :blu},
  safari: {parser: Parsers::Safari, ext: ".plist", label: "[Safari]", color: :cyan},
  chrome: {parser: Parsers::Chrome, ext: nil, label: "[Chrome]", color: :yel}
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(search_term = "") ⇒ Bookmarks

Returns a new instance of Bookmarks.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/booker/bookmarks.rb', line 33

def initialize(search_term = "")
  @conf = Config.default
  @allurls = []
  seen = Set.new

  # stat each configured path once, not once to count them and again to
  # read them. the index is kept: it is the source half of every id
  sources = @conf.bookmarks.each_with_index.select { |p, _| p && File.exist?(p) }
  @multi_source = sources.length > 1

  sources.each do |file_path, source_index|
    source = self.class.source_for(file_path)
    parser = BROWSERS.fetch(source)[:parser].new(file_path, search_term)
    parser.parse

    parser.results.each do |bookmark|
      # add? is nil when the key was already there, so the dedupe check and
      # the record of having seen it are the same call
      next unless seen.add?([bookmark.title, bookmark.url])

      @allurls << Bookmark.new(
        folder: bookmark.folder,
        title: bookmark.title,
        url: bookmark.url,
        id: "#{source_index}_#{bookmark.id}",
        source: source
      )
    end
  end
end

Instance Attribute Details

#allurlsObject (readonly)

Returns the value of attribute allurls.



31
32
33
# File 'lib/booker/bookmarks.rb', line 31

def allurls
  @allurls
end

Class Method Details

.source_for(path) ⇒ Object

which browser wrote this file, by name. the installer labels sources with it and the parse loop below picks a parser with it, so the two can never disagree about what a path is



27
28
29
# File 'lib/booker/bookmarks.rb', line 27

def self.source_for(path)
  BROWSERS.find { |_, b| b[:ext] && path&.end_with?(b[:ext]) }&.first || :chrome
end

Instance Method Details

#autocompleteObject

output for zsh autocompetion, print out id, title and cleaned url



65
66
67
68
69
70
71
# File 'lib/booker/bookmarks.rb', line 65

def autocomplete
  @allurls.each do |url|
    name = clean_name(url)
    link = clean_link(url)
    puts url.id + ":" + name + ":" + link
  end
end

#autocomplete_rawObject

machine readable feed for the shell completion scripts



86
87
88
# File 'lib/booker/bookmarks.rb', line 86

def autocomplete_raw
  rows.each { |row| puts row }
end

#bookmark_url(id) ⇒ Object

get link (from id number). #each returned @allurls itself when nothing matched, so a bad id reached open_bookmark as an Array and blew up with a TypeError instead of the "bookmark not found" message



122
123
124
# File 'lib/booker/bookmarks.rb', line 122

def bookmark_url(id)
  @allurls.find { |url| id == url.id }&.url
end

clean link for completion, remove strange things from any linkurls



110
111
112
113
114
115
116
117
# File 'lib/booker/bookmarks.rb', line 110

def clean_link(url)
  link = url.url.gsub(/[,'"&?].*/, "")
  link.gsub!(/.*:\/+/, "")
  link.delete!(" ")
  # Use half terminal width for URL
  max_width = (Term.width / 2 - CODEWIDTH).clamp(..50)
  link[0..max_width]
end

#clean_name(url) ⇒ Object

clean title for completion, delete anything not allowed in linktitle



91
92
93
94
# File 'lib/booker/bookmarks.rb', line 91

def clean_name(url)
  # Use half terminal width for name to leave room for URL
  display_name(url).window((Term.width / 2).clamp(..50))
end

#display_name(url) ⇒ Object

"[source] folder | title", with no width applied



97
98
99
100
101
102
103
104
105
106
107
# File 'lib/booker/bookmarks.rb', line 97

def display_name(url)
  # Format: [source] folder | title (source only when multi-source)
  prefix = (@multi_source && url.source) ? "[#{url.source}] " : ""
  name = prefix + url.display_folder + " | " + url.title.gsub(/[^a-z0-9\-\/_ ]/i, "")
  name.squeeze!("-")
  name.squeeze!(" ")
  # a top level bookmark has no folder text, which would otherwise leave a
  # leading space - invisible in the padded #clean_name output, but not in
  # the raw feed the completion scripts read
  name.strip
end

#rowsObject

one tab separated id/title/url triple per bookmark, shared by the shell completion feed and the picker. never truncated or padded, unlike #autocomplete: tput cols is unreliable in a completion subshell and a truncated url opens a broken link. tabs and newlines go from every field, not just the url - one surviving in a folder name splits that bookmark into two candidates the picker offers separately, neither openable



79
80
81
82
83
# File 'lib/booker/bookmarks.rb', line 79

def rows
  @allurls.map do |url|
    [url.id, display_name(url), url.url].map { |f| f.to_s.delete("\t\n") }.join("\t")
  end
end