neo-layout/windows/neo-vars/tools/compose-update.ahk

208 lines
6.5 KiB
AutoHotkey

; 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()
}