|
|
@ -0,0 +1,207 @@ |
|
|
|
; Generate script with compose-definitions from XCompose files |
|
|
|
; |
|
|
|
; Usage: |
|
|
|
; Run as standalone script with AutoHotkey.exe. |
|
|
|
; |
|
|
|
; Generates the AHK-source-script which sets up the available compose- |
|
|
|
; sequences in the neovars driver. |
|
|
|
; |
|
|
|
; Compose-sequences must be provided in input files, whose names along |
|
|
|
; with other configuration are read from "..\config\compose-config.ini", |
|
|
|
; |
|
|
|
; The output script is written to "..\src\compos.generated.ahk", if all |
|
|
|
; input files reside inside the neovars git-repository and have no uncommitted |
|
|
|
; changes; or "..\src\composee-tainted.generated.ahk" otherwise. |
|
|
|
; If either output script exist, it will be removed. Depending on |
|
|
|
; configuration options, a backup copy is made (overwriting existing backups). |
|
|
|
; |
|
|
|
; Only a subset of the XCompose syntax is supported. For instance, |
|
|
|
; input files shall not define includes, and must not specify modifiers. |
|
|
|
; See also https://www.x.org/releases/X11R7.7/doc/man/man5/Compose.5.xhtml |
|
|
|
|
|
|
|
|
|
|
|
FileEncoding, UTF-8 |
|
|
|
|
|
|
|
logInit("Compose-update") |
|
|
|
SetWorkingDir %A_ScriptDir% |
|
|
|
#Include %A_ScriptDir%\ |
|
|
|
#Include script/logwindow.ahk |
|
|
|
#Include script/util.ahk |
|
|
|
#include ../src/compose-parse.ahk |
|
|
|
#include ../src/util.ahk |
|
|
|
|
|
|
|
; Paths |
|
|
|
srcDir := A_ScriptDir . "\..\src" |
|
|
|
binDir := A_ScriptDir . "\..\bin" |
|
|
|
configDir := A_ScriptDir . "\..\config" |
|
|
|
iniFile := util_getFullPath(configDir . "\compose-config.ini") |
|
|
|
|
|
|
|
; |
|
|
|
; Read user-defined configuration |
|
|
|
; |
|
|
|
|
|
|
|
; Default configs (keys are not case-sensitive) |
|
|
|
userConfigDefaults := {composeFilesPrefix: "", composeFiles: "", makeBackups: True} |
|
|
|
configs := {revision: "<unknown>", composeFilesFull: [], outputFile: ""} |
|
|
|
|
|
|
|
; Read user-defined configs from ini-file |
|
|
|
if not FileExist(iniFile) |
|
|
|
{ |
|
|
|
logError("Configuration file not found at '" . iniFile . "'") |
|
|
|
logFinal() |
|
|
|
} |
|
|
|
logEntry("Read user-defined configuration...") |
|
|
|
for key, value in userConfigDefaults |
|
|
|
{ |
|
|
|
IniRead, valueNew, %iniFile%, Global, %key% , %value% |
|
|
|
configs[key] := valueNew |
|
|
|
} |
|
|
|
|
|
|
|
; Asseble full pathnames for input files |
|
|
|
composeFilesCSV := configs["composeFiles"] |
|
|
|
Loop, parse, composeFilesCSV, CSV, %A_Space%%A_Tab% |
|
|
|
{ |
|
|
|
configs["composeFilesFull"].push(util_getFullPath(configs["composeFilesPrefix"] . A_LoopField)) |
|
|
|
} |
|
|
|
numComposeFiles := configs["composeFilesFull"].Length() |
|
|
|
if (numComposeFiles == 0) |
|
|
|
logWarning("No compose-files provided! Compose-definitions will be empty.") |
|
|
|
|
|
|
|
; Validate input filenames (early exit on errors) |
|
|
|
Loop, % numComposeFiles |
|
|
|
{ |
|
|
|
file := configs["composeFilesFull"][A_Index] |
|
|
|
if not FileExist(file) |
|
|
|
{ |
|
|
|
logError("Compose-file not found at '" . file . "'") |
|
|
|
logFinal() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
; |
|
|
|
; Other configurations |
|
|
|
; |
|
|
|
|
|
|
|
; Query revsion information for the compose files (requires git) |
|
|
|
; If no revision can be determined (e.g. git not installed or input- |
|
|
|
; files from outside the repository), a warning will be printed. |
|
|
|
logEntry("Query revision information...") |
|
|
|
composePathsQuoted := "" |
|
|
|
for i, file in configs["composeFilesFull"] |
|
|
|
composePathsQuoted .= """" . file . """ " |
|
|
|
exitCode := util_runWaitCMD("git rev-list -n 1 HEAD -- " . composePathsQuoted, strOut, strErr) |
|
|
|
if not exitCode and StrLen(strOut) >= 7 |
|
|
|
configs["revision"] := SubStr(strOut, 1, 7) |
|
|
|
else |
|
|
|
logWarning("Could not query revision information from git repository. " . strErr) |
|
|
|
|
|
|
|
; Clear any existing output files (if exist) |
|
|
|
logEntry("Deleting old compose scripts") |
|
|
|
outputFileDefault := util_getFullPath(srcDir . "\compose.generated.ahk") |
|
|
|
outputFileTainted := util_getFullPath(srcDir . "\compose-tainted.generated.ahk") |
|
|
|
for i,f in [outputFileDefault, outputFileTainted] |
|
|
|
{ |
|
|
|
; Move existing scripts to backup copy (overwrite existing backups) |
|
|
|
if (configs["makeBackups"]) |
|
|
|
{ |
|
|
|
backupFile := f . "~" |
|
|
|
FileMove, % f, % backupFile, 1 |
|
|
|
} |
|
|
|
else |
|
|
|
FileDelete, % f |
|
|
|
if FileExist(f) |
|
|
|
{ |
|
|
|
logError("Could not remove old compose script '" . f . "'") |
|
|
|
logFinal() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
; Name of the output script (in source-directory): |
|
|
|
; - If uncommitted changes pending: (over-)write to "compose-tainted.generated.ahk" |
|
|
|
; - If working-dir clean: (over-)write to "compose.generated.ahk" |
|
|
|
exitCode := util_runWaitCMD("git diff --quiet -- " . composePathsQuoted) |
|
|
|
configs["outputFile"] := (exitCode ? outputFileTainted : outputFileDefault) |
|
|
|
logEntry("Set output file to '" . configs["outputFile"] . "'") |
|
|
|
|
|
|
|
; |
|
|
|
; Parse compose-definitions into source script |
|
|
|
; |
|
|
|
|
|
|
|
; Write header |
|
|
|
logEntry("Write header information.") |
|
|
|
scriptGeneratedBy := A_ScriptName |
|
|
|
scriptLineRevision := "compRevision := """ . configs["revision"] . """" |
|
|
|
FileAppend, |
|
|
|
( |
|
|
|
; -*- encoding: utf-8 -*- |
|
|
|
; |
|
|
|
; ** THIS FILE WAS GENERATED BY %scriptGeneratedBy% ** |
|
|
|
; ** DO NOT EDIT BY HAND -- FILE MAY BE OVERWRITTEN ANYTIME ** |
|
|
|
; |
|
|
|
|
|
|
|
; Revision information |
|
|
|
%scriptLineRevision% |
|
|
|
|
|
|
|
; Make compose-definitions globally available |
|
|
|
LoadDefaultCompose() { |
|
|
|
global |
|
|
|
|
|
|
|
), % configs["outputFile"], UTF-8 |
|
|
|
|
|
|
|
; Write compose-definitions |
|
|
|
onParseErrorFun := Func("onParseError") |
|
|
|
onProgressFun := Func("onProgress") |
|
|
|
keySym2KeyIdMap := makekeySym2KeyIdMap() |
|
|
|
totalErrorCount := 0 |
|
|
|
Loop, % numComposeFiles |
|
|
|
{ |
|
|
|
file := configs["composeFilesFull"][A_Index] |
|
|
|
logEntry("Parse compose-definitions from '" . file . "'...") |
|
|
|
if not FileExist(file) |
|
|
|
{ |
|
|
|
logError("Compose-file not found at '" . file . "'") |
|
|
|
continue ; try to complete output with footer etc. |
|
|
|
} |
|
|
|
try |
|
|
|
totalErrorCount += parseComposeFile(file, keySym2KeyIdMap, configs["outputFile"], onParseErrorFun, onProgressFun) |
|
|
|
catch e |
|
|
|
{ |
|
|
|
logError("Unhandled exception '" . e.What . (e.Message == "" ? "" : "' reports '" . e.Message) . "' while processing '" . file . "'") |
|
|
|
continue ; try to complete output with footer etc. |
|
|
|
} |
|
|
|
} |
|
|
|
logEntry(Format("Parsing finished with {:i} parsing-error{:s} in {:i} file{:s}.", totalErrorCount, util_pluralS(totalErrorCount), numComposeFiles, util_pluralS(numComposeFiles))) |
|
|
|
|
|
|
|
; Write footer |
|
|
|
logEntry("Write footer information.") |
|
|
|
FileAppend, }, % configs["outputFile"], UTF-8 |
|
|
|
|
|
|
|
; Exit |
|
|
|
logEntry("Compose-update completed.") |
|
|
|
logFinal() |
|
|
|
|
|
|
|
|
|
|
|
; ------------------------------------- |
|
|
|
; Functions |
|
|
|
; ------------------------------------- |
|
|
|
|
|
|
|
; Compose-Parser progress handler |
|
|
|
onParseError(e, errorCount) |
|
|
|
{ |
|
|
|
if (e.type == "key_sym") |
|
|
|
logWarning(Format("Ignored unknown <{:s}> on line {:u} '{:s}'", e.keySym, e.lineNo, e.line)) |
|
|
|
else if (e.type == "result") |
|
|
|
logWarning(Format("Ignored empty result on line {:u} '{:s}'", e.lineNo, e.line)) |
|
|
|
else |
|
|
|
logWarning(Format("Ignored bad line {:u} '{:s}'", e.lineNo, e.line)) |
|
|
|
} |
|
|
|
|
|
|
|
; Compose-Parser error handler |
|
|
|
onProgress(count, total, errorCount) |
|
|
|
{ |
|
|
|
progress := 100 * count / total |
|
|
|
msgStr := Format("complete - {:u} line{:s} with {:u} parsing-error{:s}", count, util_pluralS(count), errorCount, util_pluralS(errorCount)) |
|
|
|
logProgressUpdate(progress, msgStr) |
|
|
|
if (count == total) |
|
|
|
logProgressStop() |
|
|
|
} |