spiegel-keyman/windows/src/developer/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas

511 lines
13 KiB
ObjectPascal

unit Keyman.Developer.System.KeyboardProjectTemplate;
interface
uses
System.SysUtils,
kpsfile,
UKeymanTargets;
type
EKeyboardProjectTemplate = class(Exception);
TKeyboardProjectTemplate = class
private
FName: string;
FBasePath: string;
FKeyboardID: string;
FTargets: TKeymanTargets;
FIconFilename: string;
FOSKFilename: string;
FTouchLayoutFilename: string;
FAuthor: string;
FCopyright: string;
FVersion: string;
FBCP47Tags: string;
FIncludeIcon: Boolean;
function GetFilename(Base: string): string;
function GetIconFilename: string;
function GetKeyboardFilename: string;
function GetOSKFilename: string;
function GetPackageFilename: string;
function GetProjectFilename: string;
function GetTouchLayoutFilename: string;
function HasIcon: Boolean;
function HasKMX: Boolean;
function HasKVKS: Boolean;
function HasTouchLayout: Boolean;
procedure WriteKMN;
procedure WriteKPS;
procedure WriteKVKS;
procedure WriteTouchLayout;
procedure WriteKPJ;
procedure WriteRepositoryMetadata;
procedure WriteDocumentation;
procedure WriteKeyboardInfo;
procedure Transform(const SourceFile: string; DestFile: string = '');
function DataPath: string;
procedure SetKeyboardLanguages(kps: TkpsFile);
public
constructor Create(const BasePath, KeyboardID: string; Targets: TKeymanTargets);
destructor Destroy; override;
procedure Generate;
property Name: string read FName write FName;
property Copyright: string read FCopyright write FCopyright;
property Author: string read FAuthor write FAuthor;
property Version: string read FVersion write FVersion;
property BCP47Tags: string read FBCP47Tags write FBCP47Tags;
property IncludeIcon: Boolean read FIncludeIcon write FIncludeIcon;
property KeyboardFilename: string read GetKeyboardFilename;
property ProjectFilename: string read GetProjectFilename;
property PackageFilename: string read GetPackageFilename;
property OSKFilename: string read GetOSKFilename;
property IconFilename: string read GetIconFilename;
property TouchLayoutFilename: string read GetTouchLayoutFilename;
end;
implementation
uses
System.Classes,
System.StrUtils,
BCP47Tag,
Keyman.System.LanguageCodeUtils,
KeyboardParser,
KeymanVersion,
kmxfileconsts,
Keyman.Developer.System.Project.kmnProjectFile,
Keyman.Developer.System.Project.kpsProjectFile,
PackageInfo,
Keyman.Developer.System.Project.ProjectFile,
RedistFiles,
TouchLayout,
utilfiletypes,
utilstr,
VisualKeyboard;
{ TKeyboardProjectTemplate }
constructor TKeyboardProjectTemplate.Create(const BasePath, KeyboardID: string; Targets: TKeymanTargets);
begin
inherited Create;
FBasePath := IncludeTrailingPathDelimiter(ExpandFileName(BasePath));
FKeyboardID := LowerCase(KeyboardID);
FTargets := Targets;
FVersion := '1.0';
end;
destructor TKeyboardProjectTemplate.Destroy;
begin
inherited Destroy;
end;
procedure TKeyboardProjectTemplate.Generate;
begin
if not ForceDirectories(FBasePath + FKeyboardID + '\source') then
raise EKeyboardProjectTemplate.Create('Could not create destination path '+FBasePath+FKeyboardID);
WriteDocumentation;
WriteKMN;
WriteKPS;
if HasKVKS then
WriteKVKS;
if HasTouchLayout then
WriteTouchLayout;
WriteKPJ;
WriteRepositoryMetadata;
WriteKeyboardInfo;
end;
function TKeyboardProjectTemplate.GetFilename(Base: string): string;
begin
if Base = '' then
Result := ''
else if ExtractFileExt(Base) = Base then
Result := FBasePath + FKeyboardID + '\source\' + FKeyboardID + Base
else
Result := FBasePath + FKeyboardID + '\source\' + Base;
end;
function TKeyboardProjectTemplate.GetIconFilename: string;
begin
if FIncludeIcon
then Result := GetFilename(FIconFilename)
else Result := '';
end;
function TKeyboardProjectTemplate.GetKeyboardFilename: string;
begin
Result := GetFilename(Ext_KeymanSource);
end;
function TKeyboardProjectTemplate.GetOSKFilename: string;
begin
Result := GetFilename(FOSKFilename);
end;
function TKeyboardProjectTemplate.GetPackageFilename: string;
begin
Result := GetFilename(Ext_PackageSource);
end;
function TKeyboardProjectTemplate.GetProjectFilename: string;
begin
// Project file goes in parent folder
Result := FBasePath + FKeyboardID + '\' + FKeyboardID + Ext_ProjectSource;
end;
function TKeyboardProjectTemplate.GetTouchLayoutFilename: string;
begin
Result := GetFilename(FTouchLayoutFilename);
end;
function TKeyboardProjectTemplate.HasIcon: Boolean;
begin
Result := FIncludeIcon and ((KMXKeymanTargets+[ktAny]) * FTargets <> []);
end;
function TKeyboardProjectTemplate.HasKMX: Boolean;
begin
Result := (KMXKeymanTargets+[ktAny]) * FTargets <> [];
end;
function TKeyboardProjectTemplate.HasKVKS: Boolean;
begin
Result := (KeymanTargetsUsingKVK+[ktAny]) * FTargets <> [];
end;
function TKeyboardProjectTemplate.HasTouchLayout: Boolean;
begin
Result := (TouchKeymanTargets+[ktAny]) * FTargets <> [];
end;
procedure TKeyboardProjectTemplate.WriteDocumentation;
begin
// Write readme.htm and welcome.htm
Transform('source\welcome.htm');
Transform('source\readme.htm');
end;
procedure TKeyboardProjectTemplate.WriteKeyboardInfo;
begin
// Write keyboardid.keyboard_info
Transform('keyboard.keyboard_info', FKeyboardID+'.keyboard_info');
end;
procedure TKeyboardProjectTemplate.WriteKMN;
var
FKeyboardSource: string;
kp: TKeyboardParser;
sl: TStringList;
begin
kp := TKeyboardParser.Create;
try
kp.FileName := KeyboardFilename;
kp.InitialComment := #13#10+
FKeyboardID+' generated from template at '+FormatDateTime('yyyy-mm-dd hh:nn:ss', Now)+#13#10+
'with name "'+FName+'"'#13#10;
kp.SetSystemStoreValue(ssName, FName);
if FCopyright <> '' then
kp.SetSystemStoreValue(ssCopyright, FCopyright);
kp.SetSystemStoreValue(ssVersion, SKeymanKeyboardVersion);
kp.SetSystemStoreValue(ssKeyboardVersion, FVersion);
kp.SetSystemStoreValue(ssTargets, KeymanTargetsToString(FTargets));
if HasIcon then
kp.Features.Add(kfIcon);
if HasKVKS then
kp.Features.Add(kfOSK);
if HasTouchLayout then
kp.Features.Add(kfTouchLayout);
kp.AddRequiredLines;
FKeyboardSource := kp.KeyboardText;
FIconFilename := kp.GetSystemStoreValue(ssBitmap);
FOSKFilename := kp.GetSystemStoreValue(ssVisualKeyboard);
FTouchLayoutFilename := kp.GetSystemStoreValue(ssLayoutFile);
sl := TStringList.Create;
try
sl.Text := FKeyboardSource;
sl.SaveToFile(KeyboardFilename, TEncoding.UTF8);
finally
sl.Free;
end;
finally
kp.Free;
end;
end;
procedure TKeyboardProjectTemplate.WriteKPJ;
var
kpj: TProject;
begin
kpj := TProject.Create(GetProjectFilename);
try
kpj.Options.BuildPath := '$PROJECTPATH\build';
kpj.Options.WarnDeprecatedCode := True;
kpj.Options.CompilerWarningsAsErrors := True;
kpj.Files.Add(TkmnProjectFile.Create(kpj, GetKeyboardFilename, nil));
kpj.Files.Add(TkpsProjectFile.Create(kpj, GetPackageFilename, nil));
kpj.Save;
finally
kpj.Free;
end;
end;
procedure TKeyboardProjectTemplate.WriteKPS;
var
kps: TKPSFile;
f: TPackageContentFile;
pk: TPackageKeyboard;
begin
kps := TKPSFile.Create;
try
// Set kps metadata
kps.Info.Desc[PackageInfo_Name] := FName;
kps.Info.Desc[PackageInfo_Copyright] := FCopyright;
kps.Info.Desc[PackageInfo_Author] := FAuthor;
kps.KPSOptions.FollowKeyboardVersion := True;
kps.FileName := GetPackageFilename;
// Add .kmx
if HasKMX then
begin
f := TPackageContentFile.Create(kps);
f.FileName := FBasePath + FKeyboardID + '\build\' + FKeyboardID + Ext_KeymanFile;
kps.Files.Add(f);
end;
// Add .js
if HasTouchLayout then
begin
f := TPackageContentFile.Create(kps);
f.FileName := FBasePath + FKeyboardID + '\build\' + FKeyboardID + Ext_Javascript;
kps.Files.Add(f);
end;
// Add .kvk
if HasKVKS then
begin
f := TPackageContentFile.Create(kps);
f.FileName := FBasePath + FKeyboardID + '\build\' + FKeyboardID + Ext_VisualKeyboard;
kps.Files.Add(f);
end;
// Add welcome
f := TPackageContentFile.Create(kps);
f.FileName := FBasePath + FKeyboardID + '\source\welcome.htm';
kps.Files.Add(f);
// Add readme
f := TPackageContentFile.Create(kps);
f.FileName := FBasePath + FKeyboardID + '\source\readme.htm';
kps.Files.Add(f);
kps.Options.ReadmeFile := f;
// Add metadata about the keyboard
pk := TPackageKeyboard.Create(kps);
pk.Name := FName;
pk.ID := FKeyboardID;
pk.Version := FVersion;
kps.Keyboards.Add(pk);
SetKeyboardLanguages(kps);
kps.SaveXML;
finally
kps.Free;
end;
end;
procedure TKeyboardProjectTemplate.SetKeyboardLanguages(kps: TkpsFile);
var
tag, tags, s: string;
bcp47tag: TBCP47Tag;
pkl: TPackageKeyboardLanguage;
begin
tags := Trim(FBCP47Tags);
tag := StrToken(tags, ' ');
while tag <> '' do
begin
pkl := TPackageKeyboardLanguage.Create(kps);
bcp47tag := TBCP47Tag.Create(tag);
try
bcp47tag.Canonicalize;
pkl.ID := bcp47tag.Tag;
if not TLanguageCodeUtils.BCP47Languages.TryGetValue(bcp47tag.Language, pkl.Name) then
begin
pkl.Name := bcp47tag.Language;
end;
if bcp47tag.Script <> '' then
begin
if TLanguageCodeUtils.BCP47Languages.TryGetValue(bcp47tag.Script, s) then
pkl.Name := pkl.Name + ' ('+s+')';
end;
finally
bcp47tag.Free;
end;
kps.Keyboards[0].Languages.Add(pkl);
tag := StrToken(tags, ' ');
end;
end;
procedure TKeyboardProjectTemplate.WriteKVKS;
var
vk: TVisualKeyboard;
begin
Assert(HasKVKS);
vk := TVisualKeyboard.Create;
try
vk.Header.AssociatedKeyboard := FKeyboardID;
vk.SaveToFile(OSKFilename, kvksfXML);
finally
vk.Free;
end;
end;
procedure TKeyboardProjectTemplate.WriteRepositoryMetadata;
begin
Transform('HISTORY.md');
Transform('LICENSE.md');
Transform('README.md');
end;
procedure TKeyboardProjectTemplate.WriteTouchLayout;
var
tl: TTouchLayout;
JSON: string;
ss: TStringStream;
sl: TStringList;
const
CTouchLayoutBasicFile = 'template-basic'+Ext_KeymanTouchLayout;
begin
Assert(HasTouchLayout);
sl := TStringList.Create; // We use a stringlist because may have a BOM
try
sl.LoadFromFile(GetLayoutBuilderPath + CTouchLayoutBasicFile, TEncoding.UTF8);
JSON := sl.Text;
finally
sl.Free;
end;
tl := TTouchLayout.Create;
try
// We'll load and save to ensure we get formatting
tl.Load(JSON);
JSON := tl.Save(True);
// We don't want a BOM on output
ss := TStringStream.Create(JSON, TEncoding.UTF8);
try
ss.SaveToFile(TouchLayoutFilename);
finally
ss.Free;
end;
finally
tl.Free;
end;
end;
function TKeyboardProjectTemplate.DataPath: string;
begin
Result := GetRedistProjectTemplatePath;
end;
procedure TKeyboardProjectTemplate.Transform(const SourceFile: string; DestFile: string = '');
var
s: string;
ss: TStringStream;
function GetLanguageTagListForKeyboardInfo: string;
var
tags, tag: string;
bcp47tag: TBCP47Tag;
begin
// We won't worry about line breaks...
tags := Trim(FBCP47Tags);
tag := StrToken(tags, ' ');
Result := '';
while tag <> '' do
begin
if Result <> '' then
Result := Result + ', ';
bcp47tag := TBCP47Tag.Create(tag);
try
bcp47tag.Canonicalize;
Result := Result + '"' + bcp47tag.Tag + '"';
finally
bcp47tag.Free;
end;
tag := StrToken(tags, ' ');
end;
end;
function GetPlatformsDotListForReadme: string;
var
kt: TKeymanTarget;
begin
Result := '';
for kt := Low(TKeymanTarget) to High(TKeymanTarget) do
begin
if kt = ktAny then
Continue;
Result := Result + ' * ' + SKeymanTargetNames[kt] + #13#10;
end;
end;
begin
if DestFile = '' then
DestFile := SourceFile;
ss := TStringStream.Create('', TEncoding.UTF8);
try
ss.LoadFromFile(DataPath + SourceFile);
s := ss.DataString;
finally
ss.Free;
end;
s := ReplaceStr(s, '$NAME', FName);
s := ReplaceStr(s, '$VERSION', FVersion);
s := ReplaceStr(s, '$COPYRIGHT', FCopyright);
s := ReplaceStr(s, '$AUTHOR', FAuthor);
s := ReplaceStr(s, '$DATE', FormatDateTime('yyyy-mm-dd', Now));
if Pos('$LANGUAGES_KEYBOARD_INFO', s) > 0 then
s := ReplaceStr(s, '$LANGUAGES_KEYBOARD_INFO', GetLanguageTagListForKeyboardInfo);
if Pos('$PLATFORMS_DOTLIST_README', s) > 0 then
s := ReplaceStr(s, '$PLATFORMS_DOTLIST_README', GetPlatformsDotListForReadme);
ss := TStringStream.Create(s, TEncoding.UTF8);
try
ss.SaveToFile(FBasePath + FKeyboardID + '\' + DestFile);
finally
ss.Free;
end;
end;
end.