Skip to content

Commit ee23ce4

Browse files
Translate feature
* Bump version * Update deps * Add JSDocs to all functions * Improve validation * Add "Translate" feature
1 parent 5aa65eb commit ee23ce4

File tree

6 files changed

+1186
-534
lines changed

6 files changed

+1186
-534
lines changed

README.md

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ if (process.platform === 'win32') {
9292
## Documentation
9393

9494

95-
### getWindowsShortcutProperties.sync
95+
### getWindowsShortcutProperties.sync API
9696

9797
* First argument: `filePath`
9898
* **TYPE:** *String or Array of Strings*
@@ -105,7 +105,7 @@ if (process.platform === 'win32') {
105105
* **DESCRIPTION:** This is an **optional** function that is called with a message and error object (if something fails, or you pass in bad inputs). Defaults to using `console.error` if not passed in.
106106

107107

108-
### Output
108+
### getWindowsShortcutProperties.sync Output
109109

110110
Returns `undefined` if all files errored, or an Array of Objects for each successful file:
111111

@@ -131,6 +131,51 @@ See [Microsoft's Shortcut Documentation](https://docs.microsoft.com/en-us/troubl
131131
If you pass in an array of files, and some succeed, you will get an array of the success and console errors for the other files (unless you pass in a `customLogger` function, in which case it gets called when errors occur).
132132

133133

134+
### getWindowsShortcutProperties.translate API
135+
136+
* First argument: `shortcutProperties`
137+
* **TYPE:** *Array of Objects*
138+
* **DESCRIPTION:** Each object in the array is the properties for one shortcut, we convert this over to something more human readable.
139+
* Second argument: `customLogger`
140+
* **TYPE:** *function*
141+
* **DESCRIPTION:** This is an **optional** function that is called with a message and error object (if something fails, or you pass in bad inputs). Defaults to using `console.error` if not passed in.
142+
143+
144+
### getWindowsShortcutProperties.translate Output
145+
146+
Takes in the ouput of `getWindowsShortcutProperties.sync`, and then translates it into the Input for `create-desktop-shortcuts` (a different Node.js library).
147+
148+
```js
149+
const microsoftNamingConventions = [
150+
{
151+
FullName: 'C:\\Users\\Owner\\Desktop\\DaVinci Resolve.lnk',
152+
Arguments: '--foo',
153+
Description: 'Video Editor',
154+
Hotkey: 'Ctrl+F10',
155+
IconLocation: 'C:\\Program Files\\Blackmagic Design\\DaVinci Resolve\\ResolveIcon.exe,0',
156+
RelativePath: '',
157+
TargetPath: 'C:\\Program Files\\Blackmagic Design\\DaVinci Resolve\\Resolve.exe',
158+
WindowStyle: '1',
159+
WorkingDirectory: 'C:\\Program Files\\Blackmagic Design\\DaVinci Resolve\\'
160+
}
161+
];
162+
const output = getWindowsShortcutProperties.translate(microsoftNamingConventions); // produces the below output
163+
const output = [
164+
{
165+
filePath: 'C:\\Users\\Owner\\Desktop\\DaVinci Resolve.lnk',
166+
arguments: '--foo',
167+
comment: 'Video Editor',
168+
hotkey: 'Ctrl+F10',
169+
icon: 'C:\\Program Files\\Blackmagic Design\\DaVinci Resolve\\ResolveIcon.exe,0',
170+
relativePath: '',
171+
targetPath: 'C:\\Program Files\\Blackmagic Design\\DaVinci Resolve\\Resolve.exe',
172+
windowMode: 'normal',
173+
workingDirectory: 'C:\\Program Files\\Blackmagic Design\\DaVinci Resolve\\'
174+
}
175+
];
176+
```
177+
178+
134179
* * *
135180

136181

index.js

Lines changed: 216 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,40 @@ const path = require('path');
1010

1111
const parseRawData = require('./src/parse-raw-data.js');
1212

13+
/**
14+
* OPTIONAL: console.error is called by default.
15+
*
16+
* Your own custom logging function called with helpful warning/error
17+
* messages from the internal validators.
18+
*
19+
* @typedef {Function} CUSTOMLOGGER
20+
* @callback {Function} CUSTOMLOGGER
21+
* @param {string} message The human readable warning/error message
22+
* @param {object} [error] Sometimes an error or options object is passed
23+
* @return {void}
24+
*/
25+
26+
/**
27+
* @typedef {object} SHORTCUTPROPERITES
28+
* @property {string} FullName 'C:\\Users\\Owner\\Desktop\\DaVinci Resolve.lnk'
29+
* @property {string} Arguments '--foo=bar'
30+
* @property {string} Description 'Video Editor'
31+
* @property {string} Hotkey 'CTRL+SHIFT+F10'
32+
* @property {string} IconLocation 'C:\\Program Files\\Blackmagic Design\\DaVinci Resolve\\ResolveIcon.exe,0'
33+
* @property {string} RelativePath ''
34+
* @property {string} TargetPath 'C:\\Program Files\\Blackmagic Design\\DaVinci Resolve\\Resolve.exe'
35+
* @property {string} WindowStyle '1'
36+
* @property {string} WorkingDirectory 'C:\\Program Files\\Blackmagic Design\\DaVinci Resolve\\'
37+
*/
38+
39+
/**
40+
* A generic function for errors/warnings. It will either call the passed in customLoger,
41+
* or use console.error to notify the user of this library of errors or validation warnings.
42+
*
43+
* @param {CUSTOMLOGGER} customLogger User provided function to handle logging human readable errors/warnings
44+
* @param {string} message A human readable message describing an error/warning
45+
* @param {any} error A programmatic error message or object, may be undefined
46+
*/
1347
function throwError (customLogger, message, error) {
1448
if (typeof(customLogger) === 'function') {
1549
customLogger(message, error);
@@ -23,41 +57,93 @@ function throwError (customLogger, message, error) {
2357
}
2458
}
2559

26-
function generateCommands (filePaths, customLogger) {
27-
const commands = [];
28-
29-
for (let filePath of filePaths) {
30-
const normalizedFile = normalizeFile(filePath, customLogger);
31-
if (normalizedFile) {
32-
// Escape (') and (’) in the file path for PowerShell syntax
33-
const safeFilePath = normalizedFile
34-
.replace(/'/g, "''")
35-
.replace(//g, "’’");
36-
37-
const command = [
38-
'(New-Object -COM WScript.Shell).CreateShortcut(\'',
39-
safeFilePath,
40-
'\');'
41-
].join('');
42-
commands.push(command);
43-
}
60+
/**
61+
* Generic type validation function. Ensures value passed in is an array
62+
* that contains at least one string, and only strings.
63+
*
64+
* @param {any} arr A value to be validated as an array of strings
65+
* @return {boolean} true = valid
66+
*/
67+
function isArrayOfStrings (arr) {
68+
let isValidArray = false;
69+
if (Array.isArray(arr) && arr.length) {
70+
isValidArray = arr.every(function (item) {
71+
return typeof(item) === 'string';
72+
});
4473
}
74+
return isValidArray;
75+
}
4576

46-
return commands;
77+
/**
78+
* Generic type validation function. Ensures value passed in is an array
79+
* that contains at least one object, and only objects.
80+
*
81+
* @param {any} arr A value to be validated as an array of objects
82+
* @return {boolean} true = valid
83+
*/
84+
function isArrayOfObjects (arr) {
85+
let isValidArray = false;
86+
if (Array.isArray(arr) && arr.length) {
87+
isValidArray = arr.every(function (item) {
88+
return !Array.isArray(item) && typeof(item) === 'object';
89+
});
90+
}
91+
return isValidArray;
4792
}
4893

49-
function inputsAreValid (filePath, customLogger) {
50-
let valid = true;
94+
/**
95+
* If a customLogger is passed in, ensures it is a valid function.
96+
*
97+
* @param {CUSTOMLOGGER} customLogger User provided function to handle logging human readable errors/warnings
98+
* @return {boolean} True if valid, false if invalid
99+
*/
100+
function customLoggerIsValid (customLogger) {
51101
if (customLogger && typeof(customLogger) !== 'function') {
52102
throwError(customLogger, 'The customLogger must be a function or undefined');
103+
return false;
104+
}
105+
return true;
106+
}
107+
108+
/**
109+
* Validates that shortcutProperties and customLogger are the correct expected types.
110+
*
111+
* @param {SHORTCUTPROPERITES[]} shortcutProperties Array of objects, each representing a successful or failed shortcut property
112+
* @param {CUSTOMLOGGER} customLogger User provided function to handle logging human readable errors/warnings
113+
* @return {boolean} True if valid, false if invalid
114+
*/
115+
function translateInputsAreValid (shortcutProperties, customLogger) {
116+
let valid = true;
117+
valid = customLoggerIsValid(customLogger);
118+
if (!isArrayOfObjects(shortcutProperties)) {
119+
throwError(customLogger, 'The shortcutProperties must be an array of objects');
53120
valid = false;
54121
}
122+
return valid;
123+
}
124+
125+
/**
126+
* Validates that the filePath and customLogger are the correct expected types.
127+
* Validates that this Windows specific library is actually being ran on Windows.
128+
*
129+
* @param {(string|string[])} filePath String or array of strings for the filepaths to shortcut files
130+
* @param {CUSTOMLOGGER} customLogger User provided function to handle logging human readable errors/warnings
131+
* @return {boolean} True if valid, false if invalid
132+
*/
133+
function syncInputsAreValid (filePath, customLogger) {
134+
let valid = true;
135+
valid = customLoggerIsValid(customLogger);
55136
if (process.platform !== 'win32') {
56137
throwError(customLogger, 'Platform is not Windows');
57138
valid = false;
58139
}
140+
59141
if (
60142
!filePath ||
143+
(
144+
Array.isArray(filePath) &&
145+
!isArrayOfStrings(filePath)
146+
) ||
61147
(
62148
!Array.isArray(filePath) &&
63149
typeof(filePath) !== 'string'
@@ -69,6 +155,14 @@ function inputsAreValid (filePath, customLogger) {
69155
return valid;
70156
}
71157

158+
/**
159+
* Normalizes a file path and ensures it exists and ends with .lnk or .url.
160+
* Warns and returns false if filePath does not meet these requirements.
161+
*
162+
* @param {string} filePath Path to a .lnk or .url Windows shortcut file
163+
* @param {CUSTOMLOGGER} customLogger User provided function to handle logging human readable errors/warnings
164+
* @return {string} The normalized full path to a Windows shortcut that is known to exist
165+
*/
72166
function normalizeFile (filePath, customLogger) {
73167
const normalizedFile = path.normalize(path.resolve(filePath));
74168
if (
@@ -87,53 +181,113 @@ function normalizeFile (filePath, customLogger) {
87181
}
88182

89183
/**
90-
* @callback customLoggerCallback
91-
* @param {string} message
92-
* @param {Error} error
93-
*
94-
* @typedef {{
95-
* FullName: string,
96-
* Arguments: string,
97-
* Description: string,
98-
* Hotkey: string,
99-
* IconLocation: string,
100-
* RelativePath: string,
101-
* TargetPath: string,
102-
* WindowStyle: string,
103-
* WorkingDirectory: string
104-
* }[]} shortcutProperties
184+
* Creates strings of PowerShell commands for each filePath to get the file properties.
185+
* Stores strings in a returned Array.
186+
*
187+
* @param {string[]} filePaths Array of strings for the filepaths to shortcut files
188+
* @param {CUSTOMLOGGER} customLogger Optional function to handle logging human readable errors/warnings
189+
* @return {string[]} Array of strings of PowerShell commands to get shortcut properties
105190
*/
191+
function generateCommands (filePaths, customLogger) {
192+
const commands = [];
106193

107-
/**
108-
* @param {string} filePath
109-
* @param {customLoggerCallback} customLogger
110-
* @returns {shortcutProperties}
111-
*/
112-
function getWindowsShortcutProperties (filePath, customLogger) {
113-
if (!inputsAreValid(filePath, customLogger)) {
114-
return;
115-
}
116-
if (typeof(filePath) === 'string') {
117-
filePath = [filePath];
118-
}
194+
for (let filePath of filePaths) {
195+
const normalizedFile = normalizeFile(filePath, customLogger);
196+
if (normalizedFile) {
197+
// Escape (') and (’) in the file path for PowerShell syntax
198+
const safeFilePath = normalizedFile
199+
.replace(/'/g, '\'\'')
200+
.replace(//g, '’’');
119201

120-
const commands = generateCommands(filePath, customLogger).join('');
121-
if (!commands || !commands.length) {
122-
return;
123-
}
124-
const command = 'powershell.exe -command "' + commands + '"';
125-
try {
126-
const rawData = exec(command);
127-
const parsed = parseRawData(rawData);
128-
return parsed;
129-
} catch (err) {
130-
if (err) {
131-
throwError(customLogger, 'Failed to run powershell command to get shortcut properties', err);
132-
return;
202+
const command = [
203+
'(New-Object -COM WScript.Shell).CreateShortcut(\'',
204+
safeFilePath,
205+
'\');'
206+
].join('');
207+
commands.push(command);
133208
}
134209
}
210+
211+
return commands;
135212
}
136213

137214
module.exports = {
138-
sync: getWindowsShortcutProperties
215+
/**
216+
* Retrieves the details of OS based Windows shortcuts.
217+
*
218+
* @example
219+
* const output = getWindowsShortcutProperties.sync([
220+
* '../Sublime Text.lnk',
221+
* 'C:\\Users\\Public\\Desktop\\Firefox.lnk'
222+
* ]);
223+
*
224+
* @param {(string|string[])} filePath String or array of strings for the filepaths to shortcut files
225+
* @param {CUSTOMLOGGER} customLogger Optional function to handle logging human readable errors/warnings
226+
* @return {SHORTCUTPROPERITES[]} Array of objects or undefined, each representing a successful or failed shortcut property
227+
*/
228+
sync: function (filePath, customLogger) {
229+
if (!syncInputsAreValid(filePath, customLogger)) {
230+
return;
231+
}
232+
if (typeof(filePath) === 'string') {
233+
filePath = [filePath];
234+
}
235+
236+
const commands = generateCommands(filePath, customLogger).join('');
237+
if (!commands || !commands.length) {
238+
return;
239+
}
240+
const command = 'powershell.exe -command "' + commands + '"';
241+
try {
242+
const rawData = exec(command);
243+
const parsed = parseRawData(rawData);
244+
return parsed;
245+
} catch (err) {
246+
if (err) {
247+
throwError(customLogger, 'Failed to run powershell command to get shortcut properties', err);
248+
return;
249+
}
250+
}
251+
},
252+
/**
253+
* Translates the official Microsoft shortcut property names to something more human readable and familiar to JavaScript developers.
254+
*
255+
* @param {SHORTCUTPROPERITES[]} shortcutProperties Array of objects, each representing a successful or failed shortcut property
256+
* @param {CUSTOMLOGGER} customLogger User provided function to handle logging human readable errors/warnings
257+
* @return {boolean} True if valid, false if invalid
258+
*/
259+
translate: function (shortcutProperties, customLogger) {
260+
if (!translateInputsAreValid(shortcutProperties, customLogger)) {
261+
return;
262+
}
263+
const windowModes = {
264+
1: 'normal',
265+
3: 'maximized',
266+
7: 'minimized'
267+
};
268+
const translatedProperties = shortcutProperties.map(function (shortcut) {
269+
const translatedShortcut = {
270+
// 'C:\\Users\\Owner\\Desktop\\DaVinci Resolve.lnk',
271+
filePath: shortcut.FullName || '',
272+
// '--foo=bar',
273+
arguments: shortcut.Arguments || '',
274+
// 'Video Editor',
275+
comment: shortcut.Description || '',
276+
// 'CTRL+SHIFT+F10',
277+
hotkey: shortcut.Hotkey || '',
278+
// 'C:\\Program Files\\Blackmagic Design\\DaVinci Resolve\\ResolveIcon.exe,0',
279+
icon: shortcut.IconLocation || '',
280+
// '',
281+
relativePath: shortcut.RelativePath || '',
282+
// 'C:\\Program Files\\Blackmagic Design\\DaVinci Resolve\\Resolve.exe',
283+
targetPath: shortcut.TargetPath || '',
284+
// '1',
285+
windowMode: windowModes[shortcut.WindowStyle] || 'normal',
286+
// 'C:\\Program Files\\Blackmagic Design\\DaVinci Resolve\\',
287+
workingDirectory: shortcut.WorkingDirectory || ''
288+
};
289+
return translatedShortcut;
290+
});
291+
return translatedProperties;
292+
}
139293
};

0 commit comments

Comments
 (0)