-
Notifications
You must be signed in to change notification settings - Fork 3
/
parse-ff-bookmarks.js
executable file
·169 lines (160 loc) · 5.4 KB
/
parse-ff-bookmarks.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#!/usr/bin/env node
/**
* A simple script to read in a JSON file generated by Firefox's bookmarks
* manager and output the bookmarks into a nicely formatted HTML file.
*/
// Dependencies:
var fs = require('fs'),
path = require('path'),
Q = require('q'),
Handlebars = require('handlebars');
// "Constants":
var FOLDER_TEMPLATE_FILE = __dirname + '/templates/folder.handlebars',
BOOKMARK_TEMPLATE_FILE = __dirname + '/templates/bookmark.handlebars',
LAYOUT_TEMPLATE_FILE = __dirname + '/templates/layout.handlebars',
CSS_FILE = __dirname + '/styles.css',
JS_FILE = __dirname + '/collapse.js';
// Data variables:
var bookmarksJsonFile = process.argv[2],
outputFile = process.argv[3],
startFolder = process.argv[4],
bookmarks,
outputStream,
folderTemplate,
bookmarkTemplate,
layoutTemplate;
// Handlebars helper for rendering folder children.
Handlebars.registerHelper('renderChild', function(child) {
if (child.type == 'text/x-moz-place') {
return new Handlebars.SafeString('<li>' + bookmarkTemplate(child) + '</li>');
}
if (child.type == 'text/x-moz-place-container') {
return new Handlebars.SafeString('<li>' + folderTemplate(child) + '</li>');
}
return '';
});
// Check that required CLI arguments are present.
// If they aren't, or there are extra, display usage and exit.
if (!bookmarksJsonFile || !outputFile || (process.argv.length > 5)) {
console.log('Usage: parse-ff-bookmarks BOOKMARKS_JSON_FILE OUTPUT_FILE [START_FOLDER]');
process.exit(1);
}
// If bookmarks JSON file is a relative path, resolve it to an absolute one.
if (bookmarksJsonFile.charAt(0) != '/') {
bookmarksJsonFile = path.resolve(__dirname, bookmarksJsonFile);
}
// Check if the bookmarks JSON file exists.
(function() {
var deferred = Q.defer();
fs.exists(bookmarksJsonFile, deferred.resolve);
return deferred.promise;
})()
.then(function(exists) {
if (!exists) {
throw new Error('Bookmarks JSON file not found.');
}
// Read JSON data from file.
return Q.nfcall(fs.readFile, bookmarksJsonFile);
})
.then(function(data) {
bookmarks = JSON.parse(data);
// If user specified a start folder, reduce bookmarks to contents of that folder.
if (startFolder) {
bookmarks = getFolder(startFolder, bookmarks);
if (!bookmarks) {
throw new Error('The specified start folder was not found.');
}
}
// Create nested links for all bookmark folders.
createNestedLinks(bookmarks);
// Read in folder and bookmark templates, as well as CSS and JS.
return [
Q.nfcall(fs.readFile, FOLDER_TEMPLATE_FILE),
Q.nfcall(fs.readFile, BOOKMARK_TEMPLATE_FILE),
Q.nfcall(fs.readFile, LAYOUT_TEMPLATE_FILE),
Q.nfcall(fs.readFile, CSS_FILE),
Q.nfcall(fs.readFile, JS_FILE)
];
}, function(error) {
throw new Error('Unable to read Bookmarks JSON file. Please ensure it exists and is readable.');
})
.spread(function(folderTemplateContents, bookmarkTemplateContents, layoutTemplateContents, css, js) {
// Compile templates.
folderTemplate = Handlebars.compile(folderTemplateContents.toString());
bookmarkTemplate = Handlebars.compile(bookmarkTemplateContents.toString());
layoutTemplate = Handlebars.compile(layoutTemplateContents.toString());
Handlebars.registerPartial('folder', folderTemplate);
// Recursively render bookmarks.
var renderedBookmarks = layoutTemplate({content: bookmarks, css: css, js: js});
return Q.nfcall(fs.writeFile, outputFile, renderedBookmarks);
})
// On failure, print the error message.
.fail(function(error) {
console.error('Error: ' + error.message);
});
/**
* Recursively searches for a folder.
*
* @param string|array folder
* An array, or string with the structure "parent-folder/child-folder",
* representing the path to the desired folder.
* @param object contents
* The object with children to search through.
*
* @return object|bool
* Folder object if it was found, or false if not.
*/
function getFolder(folder, contents) {
if (typeof folder === 'string') {
folder = folder.split('/');
}
var currentFolder = folder[0];
var foundFolder = false;
var children = contents.children;
folder = folder.slice(1);
for (var i = 0, length = children.length; i < length; i++) {
if (children[i].type == 'text/x-moz-place-container' && children[i].title == currentFolder) {
foundFolder = children[i];
break;
}
}
if (folder.length > 0 && foundFolder) {
return getFolder(folder, foundFolder);
}
return foundFolder;
}
/**
* Recursively adds link paths to all folders.
*
* @param object bookmarks
* Bookmarks object with children.
* @param string parentPath
* Path part(s) to prefix new ones with.
*/
function createNestedLinks(bookmarks, parentPath) {
parentPath = parentPath || '';
var children = bookmarks.children;
for (var i = 0, length = children.length; i < length; i++) {
if (children[i].type == 'text/x-moz-place-container') {
var folderId = convertToId(children[i].title);
children[i].folderId = folderId;
children[i].path = parentPath + '/' + folderId;
if (children[i].children && children[i].children.length > 0) {
createNestedLinks(children[i], children[i].path);
}
}
}
}
/**
* Normalizes a title to one that's URL compatible.
*
* @param string name
* The title to normalize.
*
* @return string
* The normalized title.
*/
function convertToId(name) {
name = name.toLowerCase();
return name.replace(/[^a-z0-9-_]/g, '-');
}