@jbrowse/launch-args 5.0.0-beta.17 → 5.0.0-beta.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,62 @@
1
+ import type { Adapter } from './fileAdapter.ts';
2
+ import type { Assembly, Track } from './fileInput.ts';
3
+ import type { Entry } from './parseArgv.ts';
4
+ /** The comparison flags, each naming the adapter that reads its file. */
5
+ export declare const syntenyFlagAdapters: {
6
+ readonly paf: 'PAFAdapter';
7
+ readonly blasttab: 'BlastTabularAdapter';
8
+ readonly chain: 'ChainAdapter';
9
+ readonly delta: 'DeltaAdapter';
10
+ };
11
+ export type SyntenyFlag = keyof typeof syntenyFlagAdapters;
12
+ export declare const syntenyFlags: SyntenyFlag[];
13
+ export declare function isSyntenyFlag(key: string): key is SyntenyFlag;
14
+ /**
15
+ * What an entry is to a comparative view: a genome (`--genome`, `--fasta`,
16
+ * `--chromSizes` or a bare genome file), a comparison (a comparison flag or a
17
+ * bare file the format table reads as a SyntenyTrack), or neither. A bare
18
+ * file's role comes from its name alone.
19
+ */
20
+ export declare function comparativeRole([key, [file]]: Entry): "genome" | "synteny" | undefined;
21
+ /**
22
+ * Whether a command line describes a comparative view: a second genome, or a
23
+ * comparison file. A lone genome is a linear view.
24
+ */
25
+ export declare function hasComparativeArgs(entries: Entry[]): boolean;
26
+ /**
27
+ * The SyntenyTrack for a comparison file between two stacked genomes, upper
28
+ * first. The adapter's file format decides which is the query.
29
+ */
30
+ export declare function syntenyTrack(adapter: Adapter, file: string, upperAssembly: string, lowerAssembly: string, name?: string): Track;
31
+ /** One row of a comparative view, as a session spec's `views` lists it. */
32
+ export interface ComparativeRow {
33
+ assembly: string;
34
+ loc?: string;
35
+ }
36
+ export interface ComparativeData {
37
+ /** each genome file once, in the order the command line first names it */
38
+ assemblies: Assembly[];
39
+ /**
40
+ * each `--genome` once, whose assembly and comparisons come from its hosted
41
+ * config: see `hostedComparative`
42
+ */
43
+ hosted: string[];
44
+ /** in stacking order: the first is the top row and a dotplot's x axis */
45
+ rows: ComparativeRow[];
46
+ syntenyTracks: Track[];
47
+ }
48
+ /**
49
+ * The genomes and comparisons of a comparative view, in command-line order.
50
+ *
51
+ * - Each genome opens a row, at its `loc:` or whole, with its `aliases:` and
52
+ * `cytobands:`, named after its file, and two files of one name throw.
53
+ * - One file named twice is a self-comparison, two rows of one assembly that
54
+ * take its `aliases:` and `cytobands:` from the first row.
55
+ * - A `--genome` opens a row of a hosted genome, at its `loc:` or whole, and
56
+ * throws on a genome file of the same name.
57
+ * - A comparison file binds to the gap between the genome before it and the
58
+ * next one; a trailing one, written after both, clamps to the last gap.
59
+ * - A comparison takes `name:`; any other modifier, on it or on a genome,
60
+ * throws.
61
+ */
62
+ export declare function buildComparative(entries: Entry[]): ComparativeData;
@@ -0,0 +1,187 @@
1
+ import path from 'node:path';
2
+ import { fileNameOf } from '@jbrowse/add-track-core';
3
+ import { adapterTypeByName, syntenyAdapterTypes } from './fileAdapter.js';
4
+ import { fileAdapter, genomeAssembly, isGenomeFlag, isSequenceAdapter, } from './fileInput.js';
5
+ import { FILE_FLAG, isUrl, modifierValue, splitModifier } from './parseArgv.js';
6
+ /** The comparison flags, each naming the adapter that reads its file. */
7
+ export const syntenyFlagAdapters = {
8
+ paf: 'PAFAdapter',
9
+ blasttab: 'BlastTabularAdapter',
10
+ chain: 'ChainAdapter',
11
+ delta: 'DeltaAdapter',
12
+ };
13
+ export const syntenyFlags = Object.keys(syntenyFlagAdapters);
14
+ /**
15
+ * Whether a comparison file's query is the upper of the two genomes it sits
16
+ * between, the one written first, keyed by the adapter that reads it.
17
+ *
18
+ * - PAF, PIF, MashMap and BLAST list the query first, as minimap2 and blast
19
+ * align a query against a target, so the upper genome is the query.
20
+ * - A chain or delta file maps target to query (a UCSC
21
+ * `targetToQuery.over.chain` maps FROM the target), so the upper genome is
22
+ * the target. Getting it backwards mis-maps every coordinate and most
23
+ * alignments drop out.
24
+ */
25
+ const upperIsQuery = {
26
+ PAFAdapter: true,
27
+ PairwiseIndexedPAFAdapter: true,
28
+ MashMapAdapter: true,
29
+ BlastTabularAdapter: true,
30
+ ChainAdapter: false,
31
+ DeltaAdapter: false,
32
+ };
33
+ export function isSyntenyFlag(key) {
34
+ return Object.hasOwn(syntenyFlagAdapters, key);
35
+ }
36
+ /**
37
+ * What an entry is to a comparative view: a genome (`--genome`, `--fasta`,
38
+ * `--chromSizes` or a bare genome file), a comparison (a comparison flag or a
39
+ * bare file the format table reads as a SyntenyTrack), or neither. A bare
40
+ * file's role comes from its name alone.
41
+ */
42
+ export function comparativeRole([key, [file]]) {
43
+ if (key === 'genome' || isGenomeFlag(key)) {
44
+ return 'genome';
45
+ }
46
+ else if (isSyntenyFlag(key)) {
47
+ return 'synteny';
48
+ }
49
+ const type = key === FILE_FLAG && file ? adapterTypeByName(file) : undefined;
50
+ return !type
51
+ ? undefined
52
+ : isSequenceAdapter({ type })
53
+ ? 'genome'
54
+ : syntenyAdapterTypes.has(type)
55
+ ? 'synteny'
56
+ : undefined;
57
+ }
58
+ /**
59
+ * Whether a command line describes a comparative view: a second genome, or a
60
+ * comparison file. A lone genome is a linear view.
61
+ */
62
+ export function hasComparativeArgs(entries) {
63
+ const roles = entries.map(comparativeRole);
64
+ return (roles.includes('synteny') || roles.filter(r => r === 'genome').length > 1);
65
+ }
66
+ /**
67
+ * The SyntenyTrack for a comparison file between two stacked genomes, upper
68
+ * first. The adapter's file format decides which is the query.
69
+ */
70
+ export function syntenyTrack(adapter, file, upperAssembly, lowerAssembly, name = fileNameOf(file)) {
71
+ const queryFirst = upperIsQuery[adapter.type];
72
+ if (queryFirst === undefined) {
73
+ throw new Error(`${file} is a ${adapter.type} file, which the command line cannot place between two genomes; add it as a track in the app`);
74
+ }
75
+ const queryAssembly = queryFirst ? upperAssembly : lowerAssembly;
76
+ const targetAssembly = queryFirst ? lowerAssembly : upperAssembly;
77
+ return {
78
+ type: 'SyntenyTrack',
79
+ trackId: fileNameOf(file),
80
+ name,
81
+ // [query, target], as comparative-adapters reads it
82
+ assemblyNames: [queryAssembly, targetAssembly],
83
+ adapter: { ...adapter, queryAssembly, targetAssembly },
84
+ };
85
+ }
86
+ const ASSEMBLY_MODIFIERS = ['aliases', 'cytobands'];
87
+ // local paths compare resolved, so `./a.fa` and `a.fa` are one file
88
+ function sameFile(a, b) {
89
+ return (a === b || (!isUrl(a) && !isUrl(b) && path.resolve(a) === path.resolve(b)));
90
+ }
91
+ // whether a repeat row sets `key:` to a value its first row did not
92
+ function changes(firstMods, mods, key) {
93
+ const value = modifierValue(mods, key);
94
+ const was = modifierValue(firstMods, key);
95
+ return value !== undefined && (was === undefined || !sameFile(was, value));
96
+ }
97
+ /**
98
+ * The genomes and comparisons of a comparative view, in command-line order.
99
+ *
100
+ * - Each genome opens a row, at its `loc:` or whole, with its `aliases:` and
101
+ * `cytobands:`, named after its file, and two files of one name throw.
102
+ * - One file named twice is a self-comparison, two rows of one assembly that
103
+ * take its `aliases:` and `cytobands:` from the first row.
104
+ * - A `--genome` opens a row of a hosted genome, at its `loc:` or whole, and
105
+ * throws on a genome file of the same name.
106
+ * - A comparison file binds to the gap between the genome before it and the
107
+ * next one; a trailing one, written after both, clamps to the last gap.
108
+ * - A comparison takes `name:`; any other modifier, on it or on a genome,
109
+ * throws.
110
+ */
111
+ export function buildComparative(entries) {
112
+ const assemblies = [];
113
+ const genomes = new Map();
114
+ const hosted = new Set();
115
+ const rows = [];
116
+ const comparisons = [];
117
+ const pushRow = (assembly, mods) => {
118
+ const loc = modifierValue(mods, 'loc');
119
+ rows.push(loc ? { assembly, loc } : { assembly });
120
+ };
121
+ for (const entry of entries) {
122
+ const [key, vals] = entry;
123
+ const [file = '', ...mods] = vals;
124
+ const role = comparativeRole(entry);
125
+ const label = key === FILE_FLAG ? file : `--${key} ${file}`;
126
+ if (key === 'genome') {
127
+ if (!file) {
128
+ throw new Error('--genome requires a genome name, such as hg38');
129
+ }
130
+ const extra = mods.filter(mod => splitModifier(mod)?.[0] !== 'loc');
131
+ if (extra.length) {
132
+ throw new Error(`${label} takes loc:, not ${extra.join(' ')}; its hosted config supplies the rest`);
133
+ }
134
+ const sameName = genomes.get(file);
135
+ if (sameName) {
136
+ throw new Error(`${sameName.file} and --genome ${file} are both named ${file}; rename or link the file`);
137
+ }
138
+ hosted.add(file);
139
+ pushRow(file, mods);
140
+ }
141
+ else if (role === 'genome') {
142
+ const { assembly, unused } = genomeAssembly(key, vals, assemblies.length === 0 ? 'refseq' : `${fileNameOf(file)}-refseq`);
143
+ const extra = unused.filter(mod => splitModifier(mod)?.[0] !== 'loc');
144
+ if (extra.length) {
145
+ throw new Error(`${label} takes loc:, aliases: and cytobands:, not ${extra.join(' ')}`);
146
+ }
147
+ const first = genomes.get(assembly.name);
148
+ if (hosted.has(assembly.name)) {
149
+ throw new Error(`--genome ${assembly.name} and ${file} are both named ${assembly.name}; rename or link the file`);
150
+ }
151
+ else if (!first) {
152
+ genomes.set(assembly.name, { file, mods });
153
+ assemblies.push(assembly);
154
+ }
155
+ else if (!sameFile(first.file, file)) {
156
+ throw new Error(`${first.file} and ${file} are both named ${assembly.name}, and a genome takes its file's name; rename or link one of them`);
157
+ }
158
+ else if (ASSEMBLY_MODIFIERS.some(key => changes(first.mods, mods, key))) {
159
+ throw new Error(`${label} repeats ${assembly.name}, which takes its aliases: and cytobands: on its first row; a repeat row takes loc:, or restates them unchanged`);
160
+ }
161
+ pushRow(assembly.name, mods);
162
+ }
163
+ else if (role === 'synteny') {
164
+ if (!file) {
165
+ throw new Error(`--${key} requires a file argument`);
166
+ }
167
+ const extra = mods.filter(mod => splitModifier(mod)?.[0] !== 'name');
168
+ if (extra.length) {
169
+ throw new Error(`${label} takes name:, not ${extra.join(' ')}`);
170
+ }
171
+ comparisons.push({ entry, before: rows.length });
172
+ }
173
+ }
174
+ const numLevels = rows.length - 1;
175
+ const syntenyTracks = comparisons.map(({ entry, before }) => {
176
+ const [key, [file = '', ...mods]] = entry;
177
+ if (numLevels < 1) {
178
+ throw new Error(`${file} compares two genomes, and this command line names one; add another FASTA, 2bit, .chrom.sizes or --genome`);
179
+ }
180
+ const level = Math.min(Math.max(before - 1, 0), numLevels - 1);
181
+ const adapter = fileAdapter(file, {
182
+ adapterType: isSyntenyFlag(key) ? syntenyFlagAdapters[key] : undefined,
183
+ });
184
+ return syntenyTrack(adapter, file, rows[level].assembly, rows[level + 1].assembly, modifierValue(mods, 'name'));
185
+ });
186
+ return { assemblies, hosted: [...hosted], rows, syntenyTracks };
187
+ }
@@ -0,0 +1,160 @@
1
+ import { mergeSettings } from './slotPath.ts';
2
+ /**
3
+ * The family of display a track opens as, which says which modifiers write
4
+ * keys that display declares. `other` takes the all-tracks modifiers and slot
5
+ * writes alone.
6
+ */
7
+ export type Category = 'alignments' | 'wiggle' | 'feature' | 'variant' | 'hic' | 'other';
8
+ export { mergeSettings };
9
+ export declare const ALL_CATEGORIES: readonly ["alignments", "wiggle", "feature", "variant", "hic", "other"];
10
+ export declare const categorizedDisplayTypes: string[];
11
+ export declare function displayCategory(displayType: string | undefined): Category;
12
+ export declare const ALIGNMENTS_COLOR_FIELDS: readonly ['strand', 'firstOfPairStrand', 'mapq', 'insertSize', 'pairOrientation', 'insertSizeAndOrientation', 'mateRefName', 'mateAssembly'];
13
+ export declare const FACET_ONLY_FIELDS: readonly ['pairing', 'split', 'spliced', 'svSignal'];
14
+ export declare const COLOR_SCHEMES: readonly ['viridis', 'magma', 'inferno', 'cividis', 'juicebox', 'fall', 'reds', 'blues', 'redblue', 'purpleorange', 'redgreyblue', 'redyellowblue'];
15
+ /** The alignments compactness presets' read heights. */
16
+ export declare const ALIGNMENTS_COMPACTNESS: {
17
+ readonly normal: 7;
18
+ readonly compact: 3;
19
+ readonly 'super-compact': 1;
20
+ };
21
+ export declare const HEIGHT_MODES: readonly ['fixed', 'grow', 'fit'];
22
+ export declare const READ_CATEGORY_KEYS: readonly ['spliced', 'svSignal', 'alone', 'split'];
23
+ type Scalar = string | number | boolean;
24
+ /**
25
+ * One entry of a display's `filter`, core's `FieldPredicate` (ADR-238): the
26
+ * tests a record's `field` must pass. On an alignments display (ADR-240) the
27
+ * bare `{ field }` clears the field, defaults included.
28
+ */
29
+ export interface FilterEntry {
30
+ field: string;
31
+ equal?: Scalar;
32
+ lt?: number;
33
+ lte?: number;
34
+ gt?: number;
35
+ gte?: number;
36
+ oneOf?: Scalar[];
37
+ valid?: boolean;
38
+ }
39
+ /**
40
+ * A `filter` list as a JSON bag or a `filter=` write gives it, each entry
41
+ * checked as the config schema checks one and a string read as its
42
+ * shorthand. Throws on what the schema would refuse, v4's object form
43
+ * included.
44
+ */
45
+ export declare function filterEntriesOf(list: unknown): FilterEntry[];
46
+ export interface ColorScaleObject {
47
+ type?: string;
48
+ domain?: unknown[];
49
+ range?: string[];
50
+ names?: string[];
51
+ scheme?: string;
52
+ reverse?: boolean;
53
+ unknown?: string;
54
+ invalid?: string;
55
+ }
56
+ /** A color channel as written; a bare string is the field. */
57
+ export interface ColorObject {
58
+ value?: string;
59
+ field?: string;
60
+ scale?: ColorScaleObject;
61
+ legend?: {
62
+ title?: string;
63
+ descending?: boolean;
64
+ };
65
+ }
66
+ export type ColorChannel = string | ColorObject;
67
+ /**
68
+ * The settings the named modifiers write. Every other setting is a
69
+ * `path=value` slot write, which the display validates itself.
70
+ */
71
+ export interface DisplaySnapshot {
72
+ height?: number;
73
+ forceLoad?: boolean;
74
+ mark?: {
75
+ height?: number;
76
+ };
77
+ displayMode?: 'normal' | 'compact' | 'superCompact';
78
+ heightMode?: (typeof HEIGHT_MODES)[number];
79
+ growMaxHeight?: number;
80
+ sort?: string | {
81
+ field: string;
82
+ pos: number;
83
+ refName: string;
84
+ within?: number;
85
+ };
86
+ pileup?: {
87
+ mark?: {
88
+ height?: number;
89
+ };
90
+ bases?: {
91
+ show?: boolean;
92
+ encoding?: {
93
+ color?: string;
94
+ };
95
+ unmodified?: {
96
+ show?: boolean;
97
+ };
98
+ };
99
+ };
100
+ readConnections?: {
101
+ show?: boolean;
102
+ mark?: 'link' | 'rule';
103
+ placement?: 'up' | 'down';
104
+ };
105
+ sashimi?: {
106
+ show?: boolean;
107
+ placement?: 'up' | 'down' | 'auto';
108
+ };
109
+ filter?: FilterEntry[];
110
+ encoding?: {
111
+ color?: ColorChannel;
112
+ };
113
+ legend?: {
114
+ show?: boolean;
115
+ };
116
+ variantLayout?: 'genomic' | 'columns';
117
+ }
118
+ /**
119
+ * A track's modifiers read into display settings. `sort` is the center-line
120
+ * sort's field, which needs the view's center to become a setting, and
121
+ * `displayType` the display `display:` picks.
122
+ */
123
+ export interface DisplaySettings {
124
+ snap: DisplaySnapshot;
125
+ sort?: {
126
+ field: string;
127
+ };
128
+ displayType?: string;
129
+ }
130
+ /** The display a `display:` modifier asks for; the last one given wins. */
131
+ export declare function requestedDisplayType(modifiers: string[]): string | undefined;
132
+ /** The named track modifiers; any other setting is a `path=value` write. */
133
+ export declare const trackModifierNames: string[];
134
+ /**
135
+ * A track's modifiers read into display settings for `displayType`, the
136
+ * display the track opens as. A named modifier writes only slots that display
137
+ * declares. Without a display type, a modifier whose write depends on it
138
+ * throws, and every other one writes its setting for the display to accept or
139
+ * refuse. A member write is `memberWrites`'s, and `warn` hears of a modifier
140
+ * that does nothing on the display.
141
+ */
142
+ export declare function buildDisplaySnapshot(displayType: string | undefined, opts: string[], warn?: (message: string) => void): DisplaySettings;
143
+ /**
144
+ * Throws for a modifier no kind of track takes: an unknown name, a malformed
145
+ * slot write, or a value each display it applies to refuses. An app that
146
+ * applies the modifiers only once the track is open checks them first, so the
147
+ * command line fails rather than the open.
148
+ */
149
+ export declare function checkModifiers(opts: string[]): void;
150
+ /**
151
+ * A track's member writes: each `encoding.color.field=…` modifier, and a hic
152
+ * track's `color:<scheme>`, which names one member of the color channel the
153
+ * track already configures.
154
+ */
155
+ export declare function memberWrites(displayType: string | undefined, opts: string[]): string[];
156
+ /**
157
+ * The patch a track's member writes make: each write applied over the value
158
+ * `configured` gives the setting it names, so the rest of that setting stays.
159
+ */
160
+ export declare function memberPatch(members: string[], configured: (setting: string) => unknown): Record<string, unknown>;