281 lines
8.5 KiB
JavaScript
281 lines
8.5 KiB
JavaScript
// yamlConfig.js - Module pour lire/écrire le fichier config.yaml de Pilot V2
|
|
// Version simple sans dépendances externes
|
|
|
|
import GLib from 'gi://GLib';
|
|
import Gio from 'gi://Gio';
|
|
|
|
/**
|
|
* Parser YAML simple - ne gère que les structures basiques nécessaires pour config.yaml
|
|
* Format attendu: clés avec indentation de 2 espaces
|
|
*/
|
|
export class YamlConfig {
|
|
constructor(configPath = null) {
|
|
// Chemins par défaut où chercher le config.yaml
|
|
this.configPath = configPath || this._findConfigPath();
|
|
this.config = null;
|
|
}
|
|
|
|
/**
|
|
* Trouve le fichier de configuration dans les emplacements standards
|
|
*/
|
|
_findConfigPath() {
|
|
const possiblePaths = [
|
|
GLib.build_filenamev([GLib.get_home_dir(), 'app/pilot/pilot-v2/config.yaml']),
|
|
GLib.build_filenamev([GLib.get_home_dir(), '.config/pilot/config.yaml']),
|
|
'/etc/pilot/config.yaml',
|
|
'./pilot-v2/config.yaml'
|
|
];
|
|
|
|
for (const path of possiblePaths) {
|
|
if (GLib.file_test(path, GLib.FileTest.EXISTS)) {
|
|
return path;
|
|
}
|
|
}
|
|
|
|
// Par défaut
|
|
return GLib.build_filenamev([GLib.get_home_dir(), 'app/pilot/pilot-v2/config.yaml']);
|
|
}
|
|
|
|
/**
|
|
* Lit le fichier YAML et retourne un objet JavaScript
|
|
*/
|
|
load() {
|
|
try {
|
|
const file = Gio.File.new_for_path(this.configPath);
|
|
const [success, contents] = file.load_contents(null);
|
|
|
|
if (!success) {
|
|
throw new Error(`Cannot read file: ${this.configPath}`);
|
|
}
|
|
|
|
const decoder = new TextDecoder('utf-8');
|
|
const text = decoder.decode(contents);
|
|
|
|
this.config = this._parseYaml(text);
|
|
return this.config;
|
|
} catch (error) {
|
|
console.error(`Error loading config: ${error.message}`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse simple de YAML (gère uniquement la structure de config.yaml)
|
|
* Convertit YAML en objet JavaScript
|
|
*/
|
|
_parseYaml(text) {
|
|
const lines = text.split('\n');
|
|
const result = {};
|
|
const stack = [{obj: result, indent: -1}];
|
|
|
|
for (let line of lines) {
|
|
// Ignorer les commentaires et lignes vides
|
|
if (line.trim().startsWith('#') || line.trim() === '') {
|
|
continue;
|
|
}
|
|
|
|
const indent = line.search(/\S/);
|
|
const trimmed = line.trim();
|
|
|
|
// Calculer le niveau d'indentation (2 espaces = 1 niveau)
|
|
const level = Math.floor(indent / 2);
|
|
|
|
// Remonter dans la pile si nécessaire
|
|
while (stack.length > 0 && stack[stack.length - 1].indent >= level) {
|
|
stack.pop();
|
|
}
|
|
|
|
const parent = stack[stack.length - 1].obj;
|
|
|
|
// Traiter la ligne
|
|
if (trimmed.includes(':')) {
|
|
const colonIndex = trimmed.indexOf(':');
|
|
const key = trimmed.substring(0, colonIndex).trim();
|
|
let value = trimmed.substring(colonIndex + 1).trim();
|
|
|
|
if (value === '') {
|
|
// C'est un objet (nouvelle section)
|
|
parent[key] = {};
|
|
stack.push({obj: parent[key], indent: level});
|
|
} else if (value === 'true' || value === 'false') {
|
|
// Boolean
|
|
parent[key] = value === 'true';
|
|
} else if (!isNaN(value) && value !== '') {
|
|
// Number
|
|
parent[key] = Number(value);
|
|
} else {
|
|
// String (enlever les quotes si présentes)
|
|
parent[key] = value.replace(/^["']|["']$/g, '');
|
|
}
|
|
} else if (trimmed.startsWith('- ')) {
|
|
// Liste
|
|
if (!Array.isArray(parent)) {
|
|
// Convertir le parent en tableau si ce n'est pas déjà le cas
|
|
const lastKey = Object.keys(stack[stack.length - 2].obj).pop();
|
|
stack[stack.length - 2].obj[lastKey] = [];
|
|
stack[stack.length - 1].obj = stack[stack.length - 2].obj[lastKey];
|
|
}
|
|
|
|
const value = trimmed.substring(2).trim();
|
|
parent.push(value.replace(/^["']|["']$/g, ''));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Sauvegarde l'objet JavaScript en YAML
|
|
*/
|
|
save(config = null) {
|
|
const dataToSave = config || this.config;
|
|
|
|
if (!dataToSave) {
|
|
throw new Error('No config data to save');
|
|
}
|
|
|
|
try {
|
|
const yamlText = this._toYaml(dataToSave);
|
|
const file = Gio.File.new_for_path(this.configPath);
|
|
|
|
// Créer une sauvegarde
|
|
this._createBackup();
|
|
|
|
// Écrire le nouveau fichier
|
|
file.replace_contents(
|
|
new TextEncoder().encode(yamlText),
|
|
null,
|
|
false,
|
|
Gio.FileCreateFlags.REPLACE_DESTINATION,
|
|
null
|
|
);
|
|
|
|
console.log(`Config saved to: ${this.configPath}`);
|
|
return true;
|
|
} catch (error) {
|
|
console.error(`Error saving config: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convertit un objet JavaScript en YAML
|
|
*/
|
|
_toYaml(obj, indent = 0) {
|
|
let yaml = '';
|
|
const spaces = ' '.repeat(indent);
|
|
|
|
for (const [key, value] of Object.entries(obj)) {
|
|
if (value === null || value === undefined) {
|
|
continue;
|
|
}
|
|
|
|
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
// Objet imbriqué
|
|
yaml += `${spaces}${key}:\n`;
|
|
yaml += this._toYaml(value, indent + 1);
|
|
} else if (Array.isArray(value)) {
|
|
// Tableau
|
|
yaml += `${spaces}${key}:\n`;
|
|
for (const item of value) {
|
|
yaml += `${spaces} - ${item}\n`;
|
|
}
|
|
} else {
|
|
// Valeur simple
|
|
const valueStr = typeof value === 'string' ? value : String(value);
|
|
yaml += `${spaces}${key}: ${valueStr}\n`;
|
|
}
|
|
}
|
|
|
|
return yaml;
|
|
}
|
|
|
|
/**
|
|
* Crée une sauvegarde du fichier de config actuel
|
|
*/
|
|
_createBackup() {
|
|
try {
|
|
const timestamp = GLib.DateTime.new_now_local().format('%Y%m%d_%H%M%S');
|
|
const backupPath = `${this.configPath}.backup_${timestamp}`;
|
|
|
|
const source = Gio.File.new_for_path(this.configPath);
|
|
const dest = Gio.File.new_for_path(backupPath);
|
|
|
|
if (source.query_exists(null)) {
|
|
source.copy(dest, Gio.FileCopyFlags.OVERWRITE, null, null);
|
|
console.log(`Backup created: ${backupPath}`);
|
|
}
|
|
} catch (error) {
|
|
console.warn(`Could not create backup: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtient les métriques de télémétrie configurées
|
|
*/
|
|
getTelemetryMetrics() {
|
|
if (!this.config?.features?.telemetry?.metrics) {
|
|
return {};
|
|
}
|
|
return this.config.features.telemetry.metrics;
|
|
}
|
|
|
|
/**
|
|
* Obtient la liste des commandes autorisées
|
|
*/
|
|
getCommandsAllowlist() {
|
|
if (!this.config?.features?.commands?.allowlist) {
|
|
return [];
|
|
}
|
|
return this.config.features.commands.allowlist;
|
|
}
|
|
|
|
/**
|
|
* Met à jour une métrique de télémétrie
|
|
*/
|
|
updateTelemetryMetric(metricName, updates) {
|
|
if (!this.config?.features?.telemetry?.metrics?.[metricName]) {
|
|
return false;
|
|
}
|
|
|
|
Object.assign(this.config.features.telemetry.metrics[metricName], updates);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Active/désactive la télémétrie globale
|
|
*/
|
|
setTelemetryEnabled(enabled) {
|
|
if (!this.config?.features?.telemetry) {
|
|
return false;
|
|
}
|
|
|
|
this.config.features.telemetry.enabled = enabled;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Active/désactive les commandes globales
|
|
*/
|
|
setCommandsEnabled(enabled) {
|
|
if (!this.config?.features?.commands) {
|
|
return false;
|
|
}
|
|
|
|
this.config.features.commands.enabled = enabled;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Met à jour la liste des commandes autorisées
|
|
*/
|
|
updateCommandsAllowlist(newAllowlist) {
|
|
if (!this.config?.features?.commands) {
|
|
return false;
|
|
}
|
|
|
|
this.config.features.commands.allowlist = newAllowlist;
|
|
return true;
|
|
}
|
|
}
|