38c312bde2902cf7a2fdb5ae5a97c53b2124585d
[WebKit-https.git] / Tools / CopyPermalink / Copy WebKit Permalink.workflow / Contents / document.wflow
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 <plist version="1.0">
4 <dict>
5         <key>AMApplicationBuild</key>
6         <string>428</string>
7         <key>AMApplicationVersion</key>
8         <string>2.7</string>
9         <key>AMDocumentVersion</key>
10         <string>2</string>
11         <key>actions</key>
12         <array>
13                 <dict>
14                         <key>action</key>
15                         <dict>
16                                 <key>AMAccepts</key>
17                                 <dict>
18                                         <key>Container</key>
19                                         <string>List</string>
20                                         <key>Optional</key>
21                                         <true/>
22                                         <key>Types</key>
23                                         <array>
24                                                 <string>com.apple.applescript.object</string>
25                                         </array>
26                                 </dict>
27                                 <key>AMActionVersion</key>
28                                 <string>1.0</string>
29                                 <key>AMApplication</key>
30                                 <array>
31                                         <string>Automator</string>
32                                 </array>
33                                 <key>AMParameterProperties</key>
34                                 <dict>
35                                         <key>source</key>
36                                         <dict/>
37                                 </dict>
38                                 <key>AMProvides</key>
39                                 <dict>
40                                         <key>Container</key>
41                                         <string>List</string>
42                                         <key>Types</key>
43                                         <array>
44                                                 <string>com.apple.applescript.object</string>
45                                         </array>
46                                 </dict>
47                                 <key>ActionBundlePath</key>
48                                 <string>/System/Library/Automator/Run JavaScript.action</string>
49                                 <key>ActionName</key>
50                                 <string>Run JavaScript</string>
51                                 <key>ActionParameters</key>
52                                 <dict>
53                                         <key>source</key>
54                                         <string>/*
55  *  Copyright (C) 2017 Apple Inc. All rights reserved.
56  *
57  *  Redistribution and use in source and binary forms, with or without
58  *  modification, are permitted provided that the following conditions
59  *  are met:
60  *  1. Redistributions of source code must retain the above copyright
61  *     notice, this list of conditions and the following disclaimer.
62  *  2. Redistributions in binary form must reproduce the above copyright
63  *     notice, this list of conditions and the following disclaimer in the
64  *     documentation and/or other materials provided with the distribution.
65  *
66  *  THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
67  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
68  *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
69  *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
70  *  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
71  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
72  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
73  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
74  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
75  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
76  *  THE POSSIBILITY OF SUCH DAMAGE.
77  */
78
79 ObjC.import("Cocoa");
80
81 var g_isSVN;
82 var g_isGit;
83 var g_isGitSVN;
84 var g_lastSVNInfo;
85
86 var App = Application.currentApplication();
87 App.includeStandardAdditions = true;
88
89 function run(input, parameters) {
90     var xcodeDocument = xcodeActiveDocument();
91     if (!xcodeDocument)
92         return;
93
94     var xcodeDocumentPath = xcodeDocument.path();
95     determineVCSFromPath(xcodeDocumentPath);
96
97     if (!pathIsInWebKitCheckout(xcodeDocumentPath))
98         return;
99
100     var lineNumber = xcodeSelectedLineInDocument(xcodeDocument);
101     var path = pathRelativeToRepositoryRootForPath(xcodeDocumentPath);
102     var revisionInfo = revisionInfoForPath(xcodeDocumentPath);
103     var annotateBlame = $.NSEvent.modifierFlags &amp; $.NSAlternateKeyMask;
104
105     App.setTheClipboardTo(permalinkForPath(path, lineNumber, revisionInfo, annotateBlame));
106 }
107
108 function pathIsInWebKitCheckout(path)
109 {
110     var repositoryURL = revisionInfoForPath(path).repositoryURL;
111     return !!repositoryURL.match(/^\w+:\/\/\w+\.webkit.org/);
112 }
113
114 function permalinkForPath(path, lineNumber, revisionInfo, annotateBlame)
115 {
116     var revision = revisionInfo.revision ? "?rev=" + revisionInfo.revision : "";
117     var lineNumber = lineNumber ? "#L" + lineNumber : "";
118     var branch = revisionInfo.branch || "trunk";
119     var withBlame = annotateBlame ? "&amp;annotate=blame" : "";
120     return `https://trac.webkit.org/browser/${branch}/${path}${revision}${withBlame}${lineNumber}`;
121 }
122
123 // MARK: Xcode
124
125 function xcodeActiveDocument()
126 {
127     var xcode = Application("Xcode");
128     var windows = xcode.windows();
129     var numberOfWindows = windows.length;
130     if (!numberOfWindows)
131         return null;
132
133     // The title of an Xcode Workspace window is the title of the document in the editor pane.
134     // Ignore windows without a name (e.g. "Edit all occurrences of a symbol" pop-up menu).
135     var documentName;
136     for (var i = 0; !documentName &amp;&amp; i &lt; numberOfWindows; ++i)
137         documentName = windows[i].name();
138     if (!documentName)
139         return null;
140
141     // The title of a modified document that has not been saved will have a suffix. Remove
142     // the suffix.
143     const editedSuffix = " — Edited";
144     if (documentName.endsWith(editedSuffix))
145         documentName = documentName.substr(0, documentName.lastIndexOf(editedSuffix));
146     return xcode.documents.byName(documentName);
147 }
148
149 function xcodeSelectedLineInDocument(xcodeDocument)
150 {
151     if (!xcodeDocument)
152         return -1;
153     var range = xcodeDocument.selectedCharacterRange();
154     if (!range)
155         return -1;
156     var beginPosition = range[0] - 1;
157     if (!beginPosition)
158         return 0;
159     // FIXME: It would be more efficient to count the CRLF, CR, or LF characters
160     // in the substring from [0, beginPosition].
161     var lines = xcodeDocument.text().split(/\r?\n|\r/);
162     var numberOfLines = lines.length;
163     var characterCount = 0;
164     var i = 0;
165     do {
166         characterCount += lines[i].length + 1;
167         if (characterCount &gt; beginPosition)
168             break;
169     } while (++i &lt; numberOfLines);
170     return i + 1;
171 }
172
173 // MARK: VCS utilities
174
175 function determineVCSFromPath(path)
176 {
177     if (!isDirectory(path))
178         path = dirname(path);
179
180     g_isSVN = false;
181     g_isGit = false;
182     g_isGitSVN = false;
183
184     if (isSVNDirectory(path)) {
185         g_isSVN = true;
186         return;
187     }
188
189     if (isGitSVNDirectory(path)) {
190         g_isGit = true;
191         g_isGitSVN = true;
192         return;
193     }
194
195     if (isGitDirectory(path)) {
196         g_isGit = true;
197         return;
198     }
199 }
200
201 function pathRelativeToRepositoryRootForPath(path)
202 {
203     var checkoutDirectory = isDirectory(path) ? path : dirname(path);
204     if (g_isSVN)
205         return svnPathRelativeToRepositoryRootForPath(path, checkoutDirectory);
206     if (g_isGit)
207         return gitPathRelativeToRepositoryRootForPath(path, checkoutDirectory);
208     return "";
209 }
210
211 function gitPathRelativeToRepositoryRootForPath(path, checkoutDirectory)
212 {
213     return App.doShellScript(`git -C '${checkoutDirectory}' ls-tree --full-name --name-only HEAD '${path}'`);
214 }
215
216 function svnPathRelativeToRepositoryRootForPath(path, checkoutDirectory)
217 {
218     return svnInfoForPath(path, checkoutDirectory).path;
219 }
220
221 function revisionInfoForPath(path)
222 {
223     var checkoutDirectory = isDirectory(path) ? path : dirname(path);
224     if (g_isSVN || g_isGitSVN)
225         return svnRevisionInfoForPath(path, checkoutDirectory);
226     if (g_isGit)
227         return gitRevisionInfoForPath(path, checkoutDirectory);
228     return "";
229 }
230
231 function svnRevisionInfoForPath(path, checkoutDirectory)
232 {
233     var svnInfo = svnInfoForPath(path, checkoutDirectory);
234     return { "branch": svnInfo.branch, "revision": svnInfo.revision, "repositoryURL": svnInfo.repositoryRoot };
235 }
236
237 function gitRevisionInfoForPath(path, checkoutDirectory)
238 {
239     var repositoryURL = App.doShellScript(`git -C '${checkoutDirectory}' remote get-url origin`);
240     var revision = App.doShellScript(`git -C '${checkoutDirectory}' log -1 --format='%H' '${path}'`);
241     var branch = App.doShellScript(`git -C '${checkoutDirectory}' symbolic-ref -q HEAD`);
242     branch = branch.replace(/^refs\/heads\//, "") || "master";
243     return { branch, revision, repositoryURL };
244 }
245
246 function svnInfoForPath(path, checkoutDirectory)
247 {
248     if (g_lastSVNInfo &amp;&amp; g_lastSVNInfo.path === path) {
249         // FIXME: We should also ensure that the checkout directory for the cached SVN info is
250         // the same as the specified checkout directory.
251         return g_lastSVNInfo;
252     }
253
254     var svnInfoCommand = "svn info";
255     if (g_isGitSVN)
256         svnInfoCommand = "git " + svnInfoCommand;
257     var output = App.doShellScript(`cd '${checkoutDirectory}' &amp;&amp; ${svnInfoCommand} '${path}'`, {"alteringLineEndings": false});
258     if (!output)
259         return { };
260
261     var temp = { };
262     var lines = output.split("\n");
263     for (var line of lines) {
264         var [key, value] = line.split(": ", 2);
265         if (key &amp;&amp; value)
266             temp[key] = value;
267     }
268     var svnInfo = {
269         "path": temp["Path"],
270         "pathAsURL": temp["URL"],
271         "repositoryRoot": temp["Repository Root"],
272         "revision": temp["Revision"],
273     };
274     var branch = svnInfo.pathAsURL.replace(svnInfo.repositoryRoot + "/", "");
275     branch = branch.substr(0, branch.indexOf("/"));
276     svnInfo.branch = branch;
277
278     g_lastSVNInfo = svnInfo;
279
280     return svnInfo;
281 }
282
283 function isSVNDirectory(directory)
284 {
285     try {
286         App.doShellScript(`cd '${directory}' &amp;&amp; svn info &gt; /dev/null 2&gt;&amp;1`);
287         return true;
288     } catch (e) {
289         return false;
290     }
291 }
292
293 function isGitDirectory(directory)
294 {
295     try {
296         App.doShellScript(`git -C '${directory}' rev-parse &gt; /dev/null 2&gt;&amp;1`);
297         return true;
298     } catch (e) {
299         return false;
300     }
301 }
302
303 function isGitSVNDirectory(directory)
304 {
305     var output = "";
306     try {
307         output = App.doShellScript(`git -C '${directory}' config --get svn-remote.svn.fetch 2&gt;&amp;1`);
308     } catch (e) { }
309     return output !== "";
310 }
311
312 // MARK: Utilities
313
314 function isDirectory(path)
315 {
316     try {
317         return App.infoFor(path).folder;
318     } catch (e) {
319         return false;
320     }
321 }
322
323 function dirname(path)
324 {
325     return path.substr(0, path.lastIndexOf("/"));
326 }
327 </string>
328                                 </dict>
329                                 <key>BundleIdentifier</key>
330                                 <string>com.apple.Automator.RunJavaScript</string>
331                                 <key>CFBundleVersion</key>
332                                 <string>1.0</string>
333                                 <key>CanShowSelectedItemsWhenRun</key>
334                                 <false/>
335                                 <key>CanShowWhenRun</key>
336                                 <true/>
337                                 <key>Category</key>
338                                 <array>
339                                         <string>AMCategoryUtilities</string>
340                                 </array>
341                                 <key>Class Name</key>
342                                 <string>RunJavaScriptAction</string>
343                                 <key>InputUUID</key>
344                                 <string>0C0655EF-7893-4A61-ADD0-BA803AF3C2CD</string>
345                                 <key>Keywords</key>
346                                 <array>
347                                         <string>Run</string>
348                                         <string>JavaScript</string>
349                                 </array>
350                                 <key>OutputUUID</key>
351                                 <string>5BAD8148-07E0-4FA2-AAA1-990A7BE926FC</string>
352                                 <key>UUID</key>
353                                 <string>24BFD6CC-7A96-42C2-8469-5D83FA921DB2</string>
354                                 <key>UnlocalizedApplications</key>
355                                 <array>
356                                         <string>Automator</string>
357                                 </array>
358                                 <key>arguments</key>
359                                 <dict>
360                                         <key>0</key>
361                                         <dict>
362                                                 <key>default value</key>
363                                                 <string>function run(input, parameters) {
364         
365         // Your script goes here
366
367         return input;
368 }</string>
369                                                 <key>name</key>
370                                                 <string>source</string>
371                                                 <key>required</key>
372                                                 <string>0</string>
373                                                 <key>type</key>
374                                                 <string>0</string>
375                                                 <key>uuid</key>
376                                                 <string>0</string>
377                                         </dict>
378                                 </dict>
379                                 <key>isViewVisible</key>
380                                 <true/>
381                                 <key>location</key>
382                                 <string>480.500000:316.000000</string>
383                                 <key>nibPath</key>
384                                 <string>/System/Library/Automator/Run JavaScript.action/Contents/Resources/Base.lproj/main.nib</string>
385                         </dict>
386                         <key>isViewVisible</key>
387                         <true/>
388                 </dict>
389         </array>
390         <key>connectors</key>
391         <dict/>
392         <key>workflowMetaData</key>
393         <dict>
394                 <key>serviceApplicationBundleID</key>
395                 <string>com.apple.dt.Xcode</string>
396                 <key>serviceApplicationPath</key>
397                 <string>/Applications/Xcode.app</string>
398                 <key>serviceInputTypeIdentifier</key>
399                 <string>com.apple.Automator.nothing</string>
400                 <key>serviceOutputTypeIdentifier</key>
401                 <string>com.apple.Automator.nothing</string>
402                 <key>serviceProcessesInput</key>
403                 <integer>0</integer>
404                 <key>workflowTypeIdentifier</key>
405                 <string>com.apple.Automator.servicesMenu</string>
406         </dict>
407 </dict>
408 </plist>