I won't distribute this as a packaged macro, and anyone should test it carefully before using it, but you could experiment with dropping something along these lines into an Execute JavaScript for Automation action.
(on my system I am splitting out the the boolean result from the report string, and playing a sound that depends on the result, as well as displaying the report)
// Copyright (c) 2016 Rob Trew
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
(function () {
'use strict';
// show :: a -> String
var show = function (x) {
return JSON.stringify(x, null, 2);
};
// clipSHA :: () -> Maybe String
var clipSHA = function () {
var a = Application.currentApplication(),
sa = (a.includeStandardAdditions = true, a),
mbClip = sa.theClipboard();
if (mbClip && typeof mbClip === 'string') {
var strClip = mbClip.trim();
return strClip.match(/^[0-9a-fA-F]{40,}$/) ? strClip : undefined;
} else return undefined;
};
// 6 SHA strings (SHA, SHA1 ... SHA512) for file at path
// fileSHAs :: pathString -> [{shaType:String, sha:String, charCount:Int}]
var fileSHAs = function (strPath) {
var a = Application.currentApplication(),
sa = (a.includeStandardAdditions = true, a),
dctPath = pathExistsAndisFolder(strPath),
pathIsFile = dctPath.exists && !dctPath.isFolder;
return (pathIsFile ? sa.doShellScript([0, 1, 224, 256, 384, 512]
.map(function (x) {
return 'openssl dgst -sha' +
(x ? x.toString() : '') + ' "' + strPath + '"';
})
.join('\n')) : [])
.split(/[\n\r]+/)
.reduce(function (a, s) {
if (s.indexOf(')= ') !== -1) {
var pair = s.split(')= '),
strSHA = pair[1];
return a.concat({
shaType: pair[0].split('(')[0],
sha: strSHA,
charCount: strSHA.length
});
} else return a;
}, []);
};
// fileSHAsOfClipLength :: pathString -> shaString -> [{type:a, sha:b}]
var fileSHAsOfClipLength = function (strClipSHA, strPath) {
var lngClipSHA = strClipSHA.length;
return fileSHAs(strPath)
.filter(function (dct) {
return dct.charCount === lngClipSHA;
});
};
// diffIndices :: String -> String -> [Int]
var diffIndices = function (sa, sb) {
var as = sa.split(''),
bs = sb.split(''),
lngA = sa.length,
lngB = sb.length;
var xs = zipWith(function (a, b) {
return a === b;
}, as, bs),
deltas = all(function (x) {
return x;
}, xs) ? [] : xs.reduce(function (a, x, i) {
return x ? a : a.concat(i);
}, []);
return deltas.concat(
lngA !== lngB ? range(min(lngA, lngB), max(lngA, lngB)) : []
);
};
// showDiff :: String -> String -> String
var showDiff = function (s1, s2) {
var ds = diffIndices(s1, s2);
var blnOK = ds.length === 0;
return {
shaOK: blnOK,
display: !blnOK ? ['', s1, s2, replicateS(
max(s1.length, s2.length), ' '
)
.split('')
.map(function (x, i) {
return elem(i, ds) ? '^' : x;
})
.join('')
].join('\n') : s1
};
};
// shaLengths :: Dictionary
var shaLengths = {
SHA: 40,
SHA1: 40,
SHA224: 56,
SHA256: 64,
SHA384: 96,
SHA512: 128
};
// shaTypeLength :: String -> Int
var shaTypeLength = function (shaName) {
return shaLengths[shaName];
};
// shaLengthTypes :: Int -> [String]
var shaLengthTypes = function (intChars) {
return Object.keys(shaLengths)
.filter(function (k) {
return shaLengths[k] === intChars;
});
};
// shaReport :: shaString -> pathString -> {matched:Bool, report:String}
var shaReport = function (strClipSHA, strPath) {
var shaSet = fileSHAsOfClipLength(strClipSHA, strPath),
shaDiffs = shaSet.map(function (x) {
return showDiff(strClipSHA, x.sha);
}),
mbIntMatch = findIndex(function (dct) {
return dct.shaOK === true;
}, shaDiffs),
lngClipChars = strClipSHA.length,
blnMatched = mbIntMatch !== undefined;
return {
matched: blnMatched,
report: blnMatched ? [strClipSHA, shaDiffs[mbIntMatch].display]
.join('\n') : 'FAILURE: OpenSSL SHA of file:\n\n\t' +
strPath + '\n\ndoes NOT match SHA in clipboard.\n\n' +
shaDiffs.map(function (dct, i) {
return 'Clipboard expectation, then ' + shaSet[i].shaType +
' found:' + dct.display + '\n';
})
.join('\n'),
shaType: blnMatched ?
shaSet[mbIntMatch]
.shaType : 'Possible SHA type(s) for clipboard of ' +
'this length:' + (' (' + lngClipChars + ' chars) ') +
show(shaLengthTypes(lngClipChars))
};
};
// GENERIC FUNCTIONS -----------------------------------------------------
// pathExistsAndisFolder :: String -> (Bool, Int)
var pathExistsAndisFolder = function (strPath) {
var ref = Ref();
return $.NSFileManager.defaultManager
.fileExistsAtPathIsDirectory($(strPath)
.stringByStandardizingPath, ref) ? {
'exists': true,
'isFolder': ref[0] === 1
} : {
'exists': false
};
};
// selectedPaths :: () -> [pathString]
var selectedPaths = function () {
return Application('Finder')
.selection()
.map(function (x) {
return decodeURI(x.url())
.slice(7);
});
};
// findIndex :: (a -> Bool) -> [a] -> Maybe Int
var findIndex = function (f, xs) {
for (var i = 0, lng = xs.length; i < lng; i++) {
if (f(xs[i])) return i;
}
return undefined;
};
// all :: (a -> Bool) -> [a] -> Bool
var all = function (f, xs) {
return xs.every(f);
};
// elem :: Eq a => a -> [a] -> Bool
var elem = function (x, xs) {
return xs.indexOf(x) !== -1;
};
// max :: Ord a => a -> a -> a
var max = function (a, b) {
return b > a ? b : a;
};
// min :: Ord a => a -> a -> a
var min = function (a, b) {
return b < a ? b : a;
};
// range :: Int -> Int -> [Int]
var range = function (m, n) {
return Array.from({
length: Math.floor(n - m) + 1
}, function (_, i) {
return m + i;
});
};
// replicateS :: Int -> String -> String
var replicateS = function (n, s) {
var v = s,
o = '';
if (n < 1) return o;
while (n > 1) {
if (n & 1) o = o.concat(v);
n >>= 1;
v = v.concat(v);
}
return o.concat(v);
};
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
var zipWith = function (f, xs, ys) {
var ny = ys.length;
return (xs.length <= ny ? xs : xs.slice(0, ny))
.map(function (x, i) {
return f(x, ys[i]);
});
};
// -----------------------------------------------------------------------
// MAIN (SHA in clipboard vs SHA of selected file)
// Clipboard -> Finder selection -> Report
// 1. SHA in clipboard ?
var strClipSHA = clipSHA();
if (strClipSHA === undefined) {
return show({
result: false,
report: 'No SHA in clipboard ?\n\n' +
'(Expected hexadecimal string of 40-128 characters)'
});
}
// 2. File selected ?
var selns = selectedPaths(),
strPath = selns.length > 0 ? selns[0] : undefined;
if (strPath === undefined) return 'No file selected in Finder ?';
if (pathExistsAndisFolder(strPath)
.isFolder) {
return show({
result: false,
report: 'Selection: \n\n\t' + strPath +
'\n\nis a folder – please select a file to check SHA ...\n'
});
}
// 3. openSSL cmd yields any SHA for this file which matches clipboard ?
var dctReport = shaReport(strClipSHA, strPath);
return show({
result: dctReport.matched,
report: dctReport.matched ? 'OK for ' + dctReport.shaType +
'\n\nClipboard matches ' + dctReport.shaType +
' returned by the openssl command for :\n\n\t' +
strPath + ' \n\n' +
dctReport.report : [dctReport.report, dctReport.shaType].join('\n')
});
})();
// Testing 40 char SHA | SHA1 (should also work with other SHA sizes)
// aa508b4ee87c8dbf7dc48fa386591c73efd747c6
// 3d63e562d8f0be2e8727efa5ade5c507c395bbda
// 094742fa10af057d610d88f9ea1602e846c56d19
