@atomist/sdm-pack-aspect
Version:
an Atomist SDM Extension Pack for visualizing drift across an organization
123 lines (112 loc) • 4.08 kB
text/typescript
/*
* Copyright © 2019 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { gatherFromFiles } from "@atomist/automation-client/lib/project/util/projectUtils";
import {
Aspect,
fingerprintOf,
FP,
} from "@atomist/sdm-pack-fingerprint";
import { AspectMetadata } from "./commonTypes";
export interface GlobMatch {
path: string;
size: number;
}
export interface GlobAspectData<D = {}> {
kind: "glob";
glob: string;
matches: Array<GlobMatch & D>;
}
export function isGlobMatchFingerprint(fp: FP): fp is FP<GlobAspectData> {
const maybe = fp.data as GlobAspectData;
return !!maybe && maybe.kind === "glob" && !!maybe.glob && maybe.matches !== undefined;
}
export interface Validated {
/** Test this for a match */
contentTest?: (content: string, path: string) => boolean;
}
export interface Extracted<D> {
/** Extract the data object */
extract: (content: string, path: string) => Promise<D>;
}
export type GlobAspectOptions<D> = AspectMetadata &
{
glob: string,
} & (Validated | Extracted<D>);
function isExtracted(gao: GlobAspectOptions<any>): gao is GlobAspectOptions<any> & Extracted<any> {
const maybe = gao as Extracted<any>;
return !!maybe.extract;
}
/**
* Check for presence of a glob.
* Always extracts a fingerprint, but may have an empty array of matches.
* Entropy stat is disabled by default, but callers can override this.
* Can optionally test file content to exclude matches, or extract additional data for
* each match with the extract method.
*/
export function globAspect<D = {}>(config: GlobAspectOptions<D>): Aspect<GlobAspectData<D>> {
if (!config.glob) {
throw new Error("Glob pattern must be supplied");
}
return {
toDisplayableFingerprintName: name => `Glob pattern '${name}'`,
toDisplayableFingerprint: fp =>
fp.data.matches.length === 0 ?
"None" :
fp.data.matches
.map(m => `${m.path}(${m.size})`)
.join(),
stats: {
defaultStatStatus: {
entropy: false,
},
},
...config,
extract: async p => {
const data = {
glob: config.glob,
kind: "glob" as any,
matches: await gatherFromFiles(p, config.glob, async f => {
const content = await f.getContent();
if (isExtracted(config)) {
const extracted = await config.extract(content, f.path);
return extracted ? {
path: f.path,
size: content.length,
...extracted,
} as any : undefined;
} else {
const testToUse = config.contentTest || (() => true);
return testToUse(content, f.path) ? {
path: f.path,
size: content.length,
} : undefined;
}
}),
};
return fingerprintOf({
type: config.name,
data,
});
},
};
}
/**
* Count the number of glob matches of the given type
*/
export function countGlobMatches(fps: FP[], type: string): number {
const matches = fps.find(fp => fp.type === type && isGlobMatchFingerprint(fp));
return matches ? matches.data.matches.length : 0;
}