safari_bookmarks_parser 0.1.1 → 0.2.0

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: 707f428c27b200d30b70df6ec9a5f33b681b91144460ae9c973041e3d5fb1af2
4
- data.tar.gz: eb151045988fc6226db28cb3ffc562a3a18b4f1a28c52eb0526ecb0ffc978d87
3
+ metadata.gz: 64c367d9f7044efc558a5aeec069249c6152edfd35081e8cb1bb718bd0531c7c
4
+ data.tar.gz: 0c65ce14b8b0fb6705dec8eb61d0ac61ede0c039c0873af830fac68becb5cbdd
5
5
  SHA512:
6
- metadata.gz: bcaebcb7129e64301411f88e6a2275f50c64be6d998406531246c34bf81a436b4ef3b524c5c1edca13a43dfac8c4579a12e68feffe94cd5b01068ba20eaa7086
7
- data.tar.gz: 6db98a62641040b2ff3f0da81bd25f19eddf288650ebf9e5ccb1b729c36c211b9c4901b7cfc5dd32a709710bb8c2e28538a34202d3f55b1050fa4621f6330a69
6
+ metadata.gz: 39daa035d8e210d82ff706e229fee99f4ca705112275c5cc1b5517c1f30e07c67a39da0288cdc3a6bc48cec5ef9cd5f7395b96ee14a7c703dc603493fd1666d1
7
+ data.tar.gz: 0e81f0eb742de04011d38a42cc25d273d05775478a2a065ea0a190efad67d084724b148f684dab4c47e805feea10740849cacb1af2446270487138deb5616cb3
data/README.md CHANGED
@@ -22,6 +22,12 @@ Dump `Bookmarks.plist`:
22
22
  $ safari_bookmarks_parser dump
23
23
  ```
24
24
 
25
+ Dump `Bookmarks.plist` to `Bookmarks.json':
26
+
27
+ ```
28
+ $ safari_bookmarks_parser dump -o Bookmarks.json
29
+ ```
30
+
25
31
  Dump `Bookmarks.plist` as list:
26
32
 
27
33
  ```
@@ -52,6 +58,28 @@ Dump other `Bookmarks.plist`:
52
58
  $ safari_bookmarks_parser dump /path/to/Bookmarks.plist
53
59
  ```
54
60
 
61
+ ### Dups
62
+
63
+ Find duplicated bookmarks:
64
+
65
+ ```
66
+ $ safari_bookmarks_parser dups
67
+ ```
68
+
69
+ Find duplicated bookmarks excluding reading list:
70
+
71
+ ```
72
+ $ safari_bookmarks_parser dups -R
73
+ ```
74
+
75
+ ### Empty
76
+
77
+ Find empty folders:
78
+
79
+ ```
80
+ $ safari_bookmarks_parser empty
81
+ ```
82
+
55
83
  ## Development
56
84
 
57
85
  - Run `bin/rubocop` to check syntax
@@ -5,9 +5,15 @@ require 'safari_bookmarks_parser/bookmark_folder'
5
5
 
6
6
  require 'safari_bookmarks_parser/parser'
7
7
 
8
+ require 'safari_bookmarks_parser/services/find_duplicated_bookmarks'
9
+ require 'safari_bookmarks_parser/services/find_empty_folders'
10
+
8
11
  require 'safari_bookmarks_parser/runner'
9
12
 
13
+ require 'safari_bookmarks_parser/commands/base_command'
10
14
  require 'safari_bookmarks_parser/commands/dump_command'
15
+ require 'safari_bookmarks_parser/commands/dups_command'
16
+ require 'safari_bookmarks_parser/commands/empty_command'
11
17
 
12
18
  require 'safari_bookmarks_parser/version'
13
19
 
@@ -2,10 +2,11 @@
2
2
 
3
3
  module SafariBookmarksParser
4
4
  class BookmarkFolder
5
- attr_reader :title, :children
5
+ attr_reader :title, :folder_names, :children
6
6
 
7
- def initialize(title:, children: [])
7
+ def initialize(title:, folder_names:, children: [])
8
8
  @title = title
9
+ @folder_names = folder_names
9
10
  @children = children
10
11
  end
11
12
 
@@ -20,7 +21,7 @@ module SafariBookmarksParser
20
21
  end
21
22
 
22
23
  def to_h
23
- { 'title' => title, 'children' => children.map(&:to_h) }
24
+ { 'title' => title, 'folder_names' => folder_names, 'children' => children.map(&:to_h) }
24
25
  end
25
26
 
26
27
  def to_json(options)
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'optparse'
5
+ require 'yaml'
6
+
7
+ module SafariBookmarksParser
8
+ module Commands
9
+ class BaseCommand
10
+ attr_reader :plist_path, :output_path, :output_format
11
+
12
+ def initialize(argv)
13
+ @plist_path = File.expand_path('~/Library/Safari/Bookmarks.plist')
14
+ @output_path = nil
15
+ @output_format = :json
16
+
17
+ parse_options(argv)
18
+ handle_argv(argv)
19
+ end
20
+
21
+ private
22
+
23
+ def format_to_text(obj)
24
+ case @output_format
25
+ when :json
26
+ JSON.pretty_generate(obj)
27
+ when :yaml
28
+ YAML.dump(obj)
29
+ end
30
+ end
31
+
32
+ def output_text(text)
33
+ if @output_path
34
+ File.write(@output_path, text)
35
+ else
36
+ puts text
37
+ end
38
+ end
39
+
40
+ def on_output_path(parser)
41
+ parser.on('-o', '--output-path=PATH', 'Output path (default: output to $stdout)') do |value|
42
+ @output_path = value
43
+ end
44
+ end
45
+
46
+ def on_output_format(parser)
47
+ desc = %(Output format (default: "#{@output_format}"; one of "json" or "yaml"))
48
+ parser.on('-f', '--output-format=FORMAT', %w[json yaml], desc) do |value|
49
+ @output_format = value.to_sym
50
+ end
51
+ end
52
+
53
+ def do_parse(parser, argv)
54
+ parser.parse!(argv)
55
+ rescue OptionParser::ParseError => e
56
+ raise Error, e.message
57
+ end
58
+
59
+ def handle_argv(argv)
60
+ @plist_path = argv.first if argv.any?
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,41 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
- require 'optparse'
5
- require 'yaml'
6
-
7
3
  module SafariBookmarksParser
8
4
  module Commands
9
- class DumpCommand
10
- attr_reader :plist_path, :output_path, :output_format, :output_style, :output_parts
5
+ class DumpCommand < BaseCommand
6
+ attr_reader :output_style, :output_parts
11
7
 
12
8
  def initialize(argv)
13
- @plist_path = File.expand_path('~/Library/Safari/Bookmarks.plist')
14
- @output_path = nil
15
- @output_format = :json
16
9
  @output_style = :tree
17
10
  @output_parts = :all
18
11
 
19
- @option_parser = nil
20
-
21
- parse_options(argv)
22
- handle_argv(argv)
12
+ super
23
13
  end
24
14
 
25
15
  def run
26
16
  plist_parser = Parser.parse(@plist_path)
27
17
 
28
- result = result_for_output_parts(plist_parser)
29
- result = result_for_output_style(result)
18
+ result = select_output_parts(plist_parser)
19
+ result = select_output_style(result)
30
20
 
31
- text = format_result(result)
21
+ text = format_to_text(result)
32
22
 
33
23
  output_text(text)
34
24
  end
35
25
 
36
26
  private
37
27
 
38
- def result_for_output_parts(plist_parser)
28
+ def select_output_parts(plist_parser)
39
29
  case @output_parts
40
30
  when :all
41
31
  plist_parser.root_folder
@@ -46,7 +36,7 @@ module SafariBookmarksParser
46
36
  end
47
37
  end
48
38
 
49
- def result_for_output_style(result)
39
+ def select_output_style(result)
50
40
  case @output_style
51
41
  when :tree
52
42
  result.to_h
@@ -55,23 +45,6 @@ module SafariBookmarksParser
55
45
  end
56
46
  end
57
47
 
58
- def format_result(result)
59
- case @output_format
60
- when :json
61
- JSON.pretty_generate(result)
62
- when :yaml
63
- YAML.dump(result)
64
- end
65
- end
66
-
67
- def output_text(text)
68
- if @output_path
69
- File.write(@output_path, text)
70
- else
71
- puts text
72
- end
73
- end
74
-
75
48
  def parse_options(argv)
76
49
  parser = OptionParser.new
77
50
 
@@ -84,24 +57,11 @@ module SafariBookmarksParser
84
57
  on_list(parser)
85
58
 
86
59
  on_reading_list_only(parser)
87
- on_omit_reading_list(parser)
60
+ on_exclude_reading_list(parser)
88
61
 
89
62
  do_parse(parser, argv)
90
63
  end
91
64
 
92
- def on_output_path(parser)
93
- parser.on('-o', '--output-path=PATH', 'Output path (default: output to $stdout)') do |value|
94
- @output_path = value
95
- end
96
- end
97
-
98
- def on_output_format(parser)
99
- desc = "Output format (default: #{@output_format}; one of json or yaml)"
100
- parser.on('-f', '--output-format=FORMAT', %w[json yaml], desc) do |value|
101
- @output_format = value.to_sym
102
- end
103
- end
104
-
105
65
  def on_tree(parser)
106
66
  parser.on('--tree', 'Output as tree (default)') do
107
67
  @output_style = :tree
@@ -120,22 +80,12 @@ module SafariBookmarksParser
120
80
  end
121
81
  end
122
82
 
123
- def on_omit_reading_list(parser)
124
- parser.on('-R', 'Omit reading list') do
83
+ def on_exclude_reading_list(parser)
84
+ parser.on('-R', 'Exclude reading list') do
125
85
  @output_parts = :bookmarks
126
86
  end
127
87
  end
128
88
 
129
- def do_parse(parser, argv)
130
- parser.parse!(argv)
131
- rescue OptionParser::ParseError => e
132
- raise Error, e.message
133
- end
134
-
135
- def handle_argv(argv)
136
- @plist_path = argv.first if argv.any?
137
- end
138
-
139
89
  Runner.register_command(:dump, self)
140
90
  end
141
91
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SafariBookmarksParser
4
+ module Commands
5
+ class DupsCommand < BaseCommand
6
+ attr_reader :exclude_reading_list
7
+
8
+ def initialize(argv)
9
+ @exclude_reading_list = false
10
+
11
+ super
12
+ end
13
+
14
+ def run
15
+ plist_parser = Parser.parse(@plist_path)
16
+
17
+ bookmarks = bookmarks_to_process(plist_parser).to_a
18
+
19
+ duplicated_bookmark_groups = Services::FindDuplicatedBookmarks.call(bookmarks: bookmarks)
20
+
21
+ return if duplicated_bookmark_groups.empty?
22
+
23
+ duplicated_bookmark_groups = duplicated_bookmark_groups.map do |group|
24
+ group.map(&:to_h)
25
+ end
26
+
27
+ text = format_to_text(duplicated_bookmark_groups)
28
+
29
+ output_text(text)
30
+ end
31
+
32
+ private
33
+
34
+ def bookmarks_to_process(plist_parser)
35
+ if @exclude_reading_list
36
+ plist_parser.root_folder_without_reading_list
37
+ else
38
+ plist_parser.root_folder
39
+ end
40
+ end
41
+
42
+ def parse_options(argv)
43
+ parser = OptionParser.new
44
+
45
+ parser.banner = "Usage: #{parser.program_name} dups [options] [~/Library/Safari/Bookmarks.plist]"
46
+
47
+ on_output_path(parser)
48
+ on_output_format(parser)
49
+
50
+ on_exclude_reading_list(parser)
51
+
52
+ do_parse(parser, argv)
53
+ end
54
+
55
+ def on_exclude_reading_list(parser)
56
+ parser.on('-R', "Exclude reading list (default: #{@exclude_reading_list})") do
57
+ @exclude_reading_list = true
58
+ end
59
+ end
60
+
61
+ Runner.register_command(:dups, self)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SafariBookmarksParser
4
+ module Commands
5
+ class EmptyCommand < BaseCommand
6
+ def run
7
+ plist_parser = Parser.parse(@plist_path)
8
+
9
+ empty_folders = Services::FindEmptyFolders.call(root_folder: plist_parser.root_folder)
10
+
11
+ return if empty_folders.empty?
12
+
13
+ text = format_to_text(empty_folders.map(&:to_h))
14
+
15
+ output_text(text)
16
+ end
17
+
18
+ private
19
+
20
+ def parse_options(argv)
21
+ parser = OptionParser.new
22
+
23
+ parser.banner = "Usage: #{parser.program_name} empty [options] [~/Library/Safari/Bookmarks.plist]"
24
+
25
+ on_output_path(parser)
26
+ on_output_format(parser)
27
+
28
+ do_parse(parser, argv)
29
+ end
30
+
31
+ Runner.register_command(:empty, self)
32
+ end
33
+ end
34
+ end
@@ -65,7 +65,7 @@ module SafariBookmarksParser
65
65
  traverse(child, folder_names + [title])
66
66
  end.compact
67
67
 
68
- BookmarkFolder.new(title: title, children: children)
68
+ BookmarkFolder.new(title: title, folder_names: folder_names, children: children)
69
69
  end
70
70
 
71
71
  def accept_leaf(node, folder_names)
@@ -80,6 +80,8 @@ module SafariBookmarksParser
80
80
  end
81
81
 
82
82
  def binary_plist_to_xml_plist(binary_plist_path)
83
+ binary_plist_readable?(binary_plist_path)
84
+
83
85
  Tempfile.open(['Bookmarks', '.xml']) do |tempfile|
84
86
  command = ['plutil', '-convert', 'xml1', '-o', tempfile.path, binary_plist_path]
85
87
 
@@ -88,5 +90,16 @@ module SafariBookmarksParser
88
90
  tempfile.read
89
91
  end
90
92
  end
93
+
94
+ def binary_plist_readable?(binary_plist_path)
95
+ return if File.readable?(binary_plist_path)
96
+
97
+ message = <<~MESSAGE
98
+ Couldn't read #{binary_plist_path}
99
+ In "System Preferences" -> "Security & Privacy" -> "Privacy" -> "Full Disk Access", check "Terminal".
100
+ MESSAGE
101
+
102
+ raise Error, message
103
+ end
91
104
  end
92
105
  end
@@ -45,6 +45,8 @@ module SafariBookmarksParser
45
45
  Available commands are:
46
46
 
47
47
  - dump
48
+ - dups
49
+ - empty
48
50
  MESSAGE
49
51
  end
50
52
 
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SafariBookmarksParser
4
+ module Services
5
+ class FindDuplicatedBookmarks
6
+ def self.call(bookmarks:)
7
+ cache = Hash.new {|hash, key| hash[key] = [] }
8
+
9
+ bookmarks.to_a.each do |bookmark|
10
+ key = bookmark.url.sub(%r{\Ahttps?://}, '')
11
+ cache[key] << bookmark
12
+ end
13
+
14
+ cache.select {|_, value| value.size > 1 }.values
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SafariBookmarksParser
4
+ module Services
5
+ class FindEmptyFolders
6
+ class << self
7
+ def call(root_folder:)
8
+ results = []
9
+
10
+ traverse(root_folder, results)
11
+
12
+ results
13
+ end
14
+
15
+ def traverse(node, results)
16
+ case node
17
+ when BookmarkFolder
18
+ if node.children.empty?
19
+ results << node
20
+ else
21
+ node.children.each do |child|
22
+ traverse(child, results)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SafariBookmarksParser
4
- VERSION = '0.1.1'
4
+ VERSION = '0.2.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: safari_bookmarks_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - healthypackrat
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-22 00:00:00.000000000 Z
11
+ date: 2021-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: plist
@@ -38,9 +38,14 @@ files:
38
38
  - lib/safari_bookmarks_parser.rb
39
39
  - lib/safari_bookmarks_parser/bookmark.rb
40
40
  - lib/safari_bookmarks_parser/bookmark_folder.rb
41
+ - lib/safari_bookmarks_parser/commands/base_command.rb
41
42
  - lib/safari_bookmarks_parser/commands/dump_command.rb
43
+ - lib/safari_bookmarks_parser/commands/dups_command.rb
44
+ - lib/safari_bookmarks_parser/commands/empty_command.rb
42
45
  - lib/safari_bookmarks_parser/parser.rb
43
46
  - lib/safari_bookmarks_parser/runner.rb
47
+ - lib/safari_bookmarks_parser/services/find_duplicated_bookmarks.rb
48
+ - lib/safari_bookmarks_parser/services/find_empty_folders.rb
44
49
  - lib/safari_bookmarks_parser/version.rb
45
50
  homepage: https://github.com/healthypackrat/safari_bookmarks_parser
46
51
  licenses: