_import {stdout} from 'node:process';
import path from 'node:path';
import dig from 'obj-digger';
import {io} from '@amekusa/nodeutil';
import {IO} from './IO.js';
import {Rule} from './Rule.js';
/**
* User configuration of Karabiner-Elements.
*/
export class Config {
/**
* Instantiates Config from a JSON string or object.
* @param {string|object} data - JSON string or object
* @return {Config} New instance
*/
static fromJSON(data) {
return new this().loadJSON(data);
}
/**
* Instantiates Config from a JSON file.
* Config file is normally located at `~/.config/karabiner/karabiner.json`
* @param {string} file - JSON file path
* @param {object} [opts] - IO options
* @return {Config} New instance
*/
static fromFile(file, opts = {}) {
return new this().setIO(file, opts).load();
}
constructor() {
/**
* Config data
* @type {object}
*/
this.data;
/**
* IO object for reading/writing this config from/to a file.
* @type {IO}
*/
this.io;
}
/**
* Returns a JSON representation of this config.
* @param {boolean} [stringify=false] - If `true`, returns a stringified result
* @return {object|string} A JSON object
*/
toJSON(stringify = false) {
let r = this.data;
return stringify ? JSON.stringify(r, null, 4) : r;
}
/**
* Outputs JSON representation of this config to STDOUT.
*/
out() {
stdout.write(this.toJSON(true));
}
/**
* Setup {@link IO} object for reading/writing this config from/to a file.
* @param {string} [file='~/.config/karabiner/karabiner.json'] - Config file path
* @param {object} [opts] - IO options
* @return {Config} Itself
*/
setIO(file = null, opts = {}) {
this.io = new IO(file || path.join(io.home, '.config', 'karabiner', 'karabiner.json'), opts);
return this;
}
/**
* Loads JSON data.
* @param {string|object} data - JSON string or object
* @return {Config} Itself
*/
loadJSON(data) {
this.data = (typeof data == 'string') ? JSON.parse(data) : data;
return this;
}
/**
* Loads data from the config file.
* @return {Config} Itself
*/
load() {
if (!this.io) throw `io is not set`;
return this.loadJSON(this.io.read());
}
/**
* Writes the current data on the config file.
* @return {Config} Itself
*/
save() {
if (!this.io) throw `io is not set`;
this.io.write(this.toJSON(true));
return this;
}
/**
* The current profile object.
* @type {object}
*/
get currentProfile() {
if (!this.data) this.load();
let profs = this.data.profiles;
if (!profs.length) throw `no profiles`;
for (let i = 0; i < profs.length; i++) {
if (profs[i].selected) return profs[i];
}
throw `no active profile`;
}
/**
* Switches to the specified profile.
* @param {number|string|RegExp} prof - Profile index, name, or regex for name
* @return {Config} Itself
*/
selectProfile(prof) {
let curr = this.currentProfile;
let profs = this.data.profiles;
switch (typeof prof) {
case 'number': // by index
if (!profs[prof]) throw `index out of bounds`;
curr.selected = false;
profs[prof].selected = true;
break;
case 'string': // by name
for (let i = 0; i < profs.length; i++) {
if (profs[i].name == prof) {
curr.selected = false;
profs[i].selected = true;
break;
}
}
break;
case 'object': // by regex
if (!(prof instanceof RegExp)) throw `invalid argument`;
for (let i = 0; i < profs.length; i++) {
if (profs[i].name.match(prof)) {
curr.selected = false;
profs[i].selected = true;
break;
}
}
break;
default:
throw `invalid argument`;
}
return this;
}
/**
* Clears all the rules in the current profile.
* @return {Config} Itself
*/
clearRules() {
return this.setRules([]);
}
/**
* Sets the given rules to the current profile.
* @param {object[]|Rule[]} rules - An array of rule definitions
* @return {Config} Itself
*/
setRules(rules) {
dig(this.currentProfile, 'complex_modifications.rules', {
set: rules.map(rule => (rule instanceof Rule) ? rule.toJSON() : rule),
makePath: true,
throw: true
});
return this;
}
}