".zshenv",
".zshrc"
],
- "templates": [".corpusrc.erb"]
+ "templates": [".corpusrc.erb", ".gitconfig.erb"]
}
}
-import {command, file, template, task, variable} from '../../src/Fig/index.js';
+import {
+ command,
+ file,
+ template,
+ task,
+ variable,
+ variables,
+} from '../../src/Fig/index.js';
import assert from '../../src/assert.js';
import stat from '../../src/fs/stat.js';
import path from '../../src/path.js';
+variables(({identity}) => ({
+ gitUserEmail: identity === 'wincent' ? 'greg@hurrell.net' : '',
+ gitUserName: identity === 'wincent' ? 'Greg Hurrell' : '',
+ gitHubUsername: identity === 'wincent' ? 'wincent' : '',
+}));
+
task('make directories', async () => {
await file({path: '~/.backups', state: 'directory'});
await file({path: '~/.config', state: 'directory'});
-# {{ ansible_managed }}
+# <%= variables.figManaged %>
[alias]
abbrev = !sh -c 'git rev-parse --short ${1-`echo HEAD`}' -
get = "!f() { git fresh && git ff \"$@\"; }; f"
# equivalent to: graph --all
- gr = log --graph --all --pretty=format:'%C(auto)%h%Creset%C(auto)%d%Creset %s %C(magenta bold)(%cr)%Creset %C(cyan)<%aN>%Creset'
+ gr = log --graph --all --pretty=format:'%C(auto)%h%Creset%C(auto)%d%Creset %s %C(magenta bold)(%cr)%Creset %C(cyan)<%= '<\%' %>aN>%Creset'
# requires Git 1.6.3 or later; %C(auto) requires Git 1.8.3 or later
- graph = log --graph --pretty=format:'%C(auto)%h%Creset%C(auto)%d%Creset %s %C(magenta bold)(%cr)%Creset %C(cyan)<%aN>%Creset'
+ graph = log --graph --pretty=format:'%C(auto)%h%Creset%C(auto)%d%Creset %s %C(magenta bold)(%cr)%Creset %C(cyan)<%= '<\%' %>aN>%Creset'
# Show just the HEAD commit message (no indent) and nothing else
message = log -1 --pretty=format:%B
no-edit = commit --amend --no-edit
# %C(auto) requires Git 1.8.3 or later
- one = log --pretty=format:'%C(auto)%h%Creset %s%C(auto)%d%Creset %C(magenta bold)(%cr)%Creset %C(cyan)<%aN>%Creset'
- oneline = log --pretty=format:'%C(auto)%h%Creset %s%C(auto)%d%Creset %C(magenta bold)(%cr)%Creset %C(cyan)<%aN>%Creset'
+ one = log --pretty=format:'%C(auto)%h%Creset %s%C(auto)%d%Creset %C(magenta bold)(%cr)%Creset %C(cyan)<%= '<\%' %>aN>%Creset'
+ oneline = log --pretty=format:'%C(auto)%h%Creset %s%C(auto)%d%Creset %C(magenta bold)(%cr)%Creset %C(cyan)<%= '<\%' %>aN>%Creset'
# requires Git 1.5.4 or later
p = add -p
# "smartlog", although it's not that smart.
# Equivalent to `git graph --all --simplify-by-decoration.
- sl = log --graph --pretty=format:'%C(auto)%h%Creset%C(auto)%d%Creset %s %C(magenta bold)(%cr)%Creset %C(cyan)<%aN>%Creset' --all --simplify-by-decoration
+ sl = log --graph --pretty=format:'%C(auto)%h%Creset%C(auto)%d%Creset %s %C(magenta bold)(%cr)%Creset %C(cyan)<%= '<\%' %>aN>%Creset' --all --simplify-by-decoration
st = status
staged = diff --cached --ignore-submodules=dirty
# %C(auto) requires Git 1.8.3 or later
- ten = log -10 --pretty=format:'%C(auto)%h%Creset%C(auto)%d%Creset %s %C(magenta bold)(%cr)%Creset %C(cyan)<%aN>%Creset'
+ ten = log -10 --pretty=format:'%C(auto)%h%Creset%C(auto)%d%Creset %s %C(magenta bold)(%cr)%Creset %C(cyan)<%= '<\%' %>aN>%Creset'
# compensate for brain damage caused by using Mercurial
up = checkout
[difftool]
prompt = false
-{% if github_username != '' %}
+<%- if (variables.gitHubUsername) { -%>
[github]
- username = {{ github_username }}
-{% endif %}
+ username = <%= variables.gitHubUsername %>
+<%- } -%>
[grep]
lineNumber = true
- # requires Git built with PCRE support; ie:
- # brew install git --with-pcre (on OS X)
+ # Requires PCRE support; ie: `brew install git --with-pcre` (on macOS).
patternType = perl
[help]
smtpEncryption = tls
smtpServer = smtp.gmail.com
smtpServerPort = 587
-{% if git_user_email != '' %}
- smtpUser = {{ git_user_email }}
-{% endif %}
+<%- if (variables.gitUserEmail) { -%>
+ smtpUser = <%= variables.gitUserEmail %>
+<%- } -%>
[status]
submodulesummary = true
fetchJobs = 4
[user]
-{% if git_user_email != '' %}
- email = {{ git_user_email }}
-{% endif %}
-{% if git_user_name != '' %}
- name = {{ git_user_name }}
-{% endif %}
+<%- if (variables.gitUserEmail) { -%>
+ email = <%= variables.gitUserEmail %>
+<%- } -%>
+<%- if (variables.gitUserName) { -%>
+ name = <%= variables.gitUserName %>
+<%- } -%>
# ignored by Git older than 1.7.10
[include]
---
git_cipher_path: vendor/git-cipher/bin/git-cipher
http_proxy: ""
+
+# DONE
identity: '{{ "wincent" if lookup("env", "USER") == "glh" or lookup("env", "USER") == "greghurrell" else "unknown" }}'
+
iterm_dynamic_profiles:
external:
- dest: Mutt.json
"license": "Unlicense",
"scripts": {
"ci": "yarn format:check",
- "format:check": "npx prettier --check \"**/*.{js,json,ts}\" \"*.md\" aspects/dotfiles/files/.zsh/liferay/bin/portool",
- "format": "npx prettier --write \"**/*.{js,json,ts}\" \"*.md\" aspects/dotfiles/files/.zsh/liferay/bin/portool"
+ "format:check": "npx prettier --check \"**/*.{js,json,ts}\" \"*.{md,ts}\" aspects/dotfiles/files/.zsh/liferay/bin/portool",
+ "format": "npx prettier --write \"**/*.{js,json,ts}\" \"*.{md,ts}\" aspects/dotfiles/files/.zsh/liferay/bin/portool"
},
"type": "module",
"dependencies": {
dotfile_templates:
- .config/karabiner/karabiner.json
- .gemrc
- - .gitconfig
- .hammerspoon/iterm.lua
- .imapfilter/config.lua
- .mbsyncrc
- .mutt/hooks/presync.sh
- .notmuch-config
+# DONE
git_user_email: '{{"greg@hurrell.net" if identity == "wincent" else ""}}'
git_user_name: '{{"Greg Hurrell" if identity == "wincent" else ""}}'
github_username: '{{"wincent" if identity == "wincent" else ""}}'
+// import {log} from './console/index.js';
import {promises as fs} from './fs.js';
import {compile, fill} from './template.js';
const compiled = compile(source);
+ // BUG: too verbose?
+ // log.debug(`Compiled template source:\n\n${compiled}\n`);
+
map.set(path, {
fill(scope) {
return fill(compiled, scope);
import * as status from './status.js';
import Compiler from '../Compiler.js';
import TaskRegistry from './TaskRegistry.js';
+import VariableRegistry from './VariableRegistry.js';
import type {Metadata} from '../ErrorWithMetadata.js';
import type {Aspect} from '../types/Project.js';
#sudoPassphrase?: Promise<string>;
#tasks: TaskRegistry;
+ // TODO: rename stuff to avoid confusion about `variables`
+ // (VariableRegistry) vs `currentVariables` (merged variables set
+ // from main.ts).
+ #variables: VariableRegistry;
+
constructor() {
this.#attributes = new Attributes();
this.#compiler = new Compiler();
};
this.#tasks = new TaskRegistry();
+ this.#variables = new VariableRegistry();
}
compile(path: string) {
get tasks(): TaskRegistry {
return this.#tasks;
}
+
+ get variables(): VariableRegistry {
+ return this.#variables;
+ }
}
export default new Context();
--- /dev/null
+import type {Aspect} from '../types/Project.js';
+
+type Callback = (variables: Variables) => Variables;
+
+export default class VariableRegistry {
+ #callbacks: Map<Aspect, Callback>;
+
+ constructor() {
+ this.#callbacks = new Map();
+ }
+
+ register(aspect: Aspect, callback: Callback) {
+ if (this.#callbacks.has(aspect)) {
+ throw new Error(
+ `Variables have already been registered for aspect ${aspect}`
+ );
+ }
+
+ this.#callbacks.set(aspect, callback);
+ }
+
+ get(aspect: Aspect): Callback {
+ return this.#callbacks.get(aspect) || (() => ({}));
+ }
+}
import {default as task} from './task.js';
import {default as template} from './operations/template.js';
import {default as variable} from './variable.js';
+import {default as variables} from './variables.js';
export {command};
export {file};
export {task};
export {template};
export {variable};
+export {variables};
export interface Fig {
command: typeof command;
task: typeof task;
template: typeof template;
variable: typeof variable;
+ variables: typeof variables;
}
assertAspect(aspect);
- // TODO: we use `caller` to make namespaced task name.
- // (will be useful for --start-at)
- // also, we can make an interactive mode that lets us choose where to start
Context.tasks.register(aspect, callback, `${aspect} | ${name}`);
}
--- /dev/null
+import {relative, sep} from 'path';
+import * as url from 'url';
+
+import {assertAspect} from '../types/Project.js';
+import getCaller from '../getCaller.js';
+import Context from './Context.js';
+import {default as root} from './root.js';
+
+/**
+ * Register a callback to dynamically contribute variables when an aspect is
+ * running (useful for values that cannot be determined statically ahead of time
+ * and stored in JSON).
+ */
+export default function variables(callback: (v: Variables) => Variables) {
+ const caller = getCaller();
+
+ const path = url.fileURLToPath(caller);
+
+ const ancestors = relative(root, path).split(sep);
+
+ const aspect =
+ ancestors[0] === 'lib' && ancestors[1] === 'aspects' && ancestors[2];
+
+ if (!aspect) {
+ throw new Error(`Unable to determine aspect for ${caller}`);
+ }
+
+ assertAspect(aspect);
+
+ Context.variables.register(aspect, callback);
+}
+
+// TODO: dedupe this, which is almost identical to task.ts
+// TODO: rename this folder from "Fig" to "dsl"
import * as os from 'os';
import {join} from 'path';
+import variables from '../variables.js';
import ErrorWithMetadata from './ErrorWithMetadata.js';
import Context from './Fig/Context.js';
import {root} from './Fig/index.js';
const baseVariables = merge(
defaultVariables,
profileVariables,
- platformVariables
+ platformVariables,
+ variables
);
// Execute tasks.
continue;
}
- const variables = merge(aspectVariables, baseVariables);
+ const mergedVariables = merge(baseVariables, aspectVariables);
+
+ const variables = merge(
+ mergedVariables,
+ Context.variables.get(aspect)(mergedVariables)
+ );
log.debug(`Variables:\n\n${stringify(variables)}\n`);
}
}
} else {
+ // TODO: may want to tolerate this so that we can write
+ // things like: <%= '<%' %>
+ // would be useful in .gitconfig.erb
throw new Error(
- `Unexpected start delimiter "${text}" at index ${match.index}`
+ `Unexpected start delimiter "${text}" at index ${match.index}:\n\n` +
+ excerpt(input, match.index)
);
}
} else {
if (text === '<%-') {
- // Eat whitspace between previous newline and delimiter.
+ // Eat whitespace between previous newline and delimiter.
yield {
kind: 'TemplateText',
text: input
};
} else if (text === '%>') {
throw new Error(
- `Unexpected end delimiter "%>" at index ${match.index}`
+ `Unexpected end delimiter "%>" at index ${match.index}:\n\n` +
+ excerpt(input, match.index)
);
}
}
}
}
}
+
+/**
+ * Produce an except of `input` around position `index` for error-reporting.
+ */
+function excerpt(input: string, index: number): string {
+ return JSON.stringify(input.slice(Math.max(0, index - 10), index + 10));
+}
"strict": true,
"target": "ES2019"
},
- "include": ["aspects/**/*.ts", "src/**/*.ts"]
+ "include": ["aspects/**/*.ts", "src/**/*.ts", "variables.ts"]
}
--- /dev/null
+import Context from './src/Fig/Context.js';
+
+/**
+ * @file
+ *
+ * Dynamic variables.
+ *
+ * Priority (from lowest to highest):
+ *
+ * 1. Defaults from "variables" property in project.json.
+ * 2. Profile-specific overrides from "variables" properties in "profiles" in
+ * project.json.
+ * 3. Platform-specific overrides from "variables" properties in "platforms" in
+ * project.json.
+ * 4. Dynamic variables exported from variables.ts (ie. this file).
+ * 5. Aspect-specific overrides from "variables" property in aspect.json files.
+ * 6. Dynamic aspect-specific overrides registered using the `variables` DSL
+ * function inside an aspect's index.ts file.
+ *
+ */
+const variables = {
+ get identity() {
+ if (
+ Context.attributes.username === 'glh' ||
+ Context.attributes.username === 'greghurrell'
+ ) {
+ return 'wincent';
+ } else {
+ return 'unknown';
+ }
+ },
+};
+
+export default variables;