Files
omarchy-pkgs/pkgbuilds/rime-ice-installer/omarchy-offline.patch
T

377 lines
12 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
diff --git a/internal/app/app.go b/internal/app/app.go
index 17e01bf..fd73578 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -84,7 +84,8 @@ func Run(ctx context.Context, cfg config.InstallConfig) error {
return err
}
- if os.Geteuid() != 0 {
+ missingPackages := fcitx.MissingPackages()
+ if os.Geteuid() != 0 && len(missingPackages) > 0 {
if dialogUI != nil {
if err := validateSudoWithDialog(ctx, runner, dialogUI, detectedEnv.FingerprintAvailable); err != nil {
if errors.Is(err, ui.ErrCancelled) {
@@ -140,7 +141,7 @@ func Run(ctx context.Context, cfg config.InstallConfig) error {
}
if err := step("安装 Fcitx5 与依赖", func() error {
- return fcitx.InstallPackages(ctx, runner, cfg, detectedEnv)
+ return fcitx.InstallPackages(ctx, runner, missingPackages, detectedEnv)
}); err != nil {
closeGauge(gauge)
return fail(dialogUI, logger, err)
@@ -181,7 +182,7 @@ func Run(ctx context.Context, cfg config.InstallConfig) error {
if err := step("下载并补丁雾凇拼音", func() error {
var err error
- rimeWorkspace, err = rime.PrepareWorkspace(ctx, downloader, cfg.WorkspaceDir)
+ rimeWorkspace, err = rime.PrepareWorkspace(ctx, downloader, cfg.WorkspaceDir, cfg.AssetDir)
return err
}); err != nil {
closeGauge(gauge)
@@ -191,7 +192,7 @@ func Run(ctx context.Context, cfg config.InstallConfig) error {
if cfg.EnableWanxiang {
if err := step("下载并启用万象语法模型", func() error {
var err error
- modelRelease, err = wanxiang.Integrate(ctx, downloader, cfg.WorkspaceDir, rimeWorkspace.SourceDir)
+ modelRelease, err = wanxiang.Integrate(ctx, downloader, cfg.WorkspaceDir, rimeWorkspace.SourceDir, cfg.AssetDir)
return err
}); err != nil {
closeGauge(gauge)
diff --git a/internal/config/config.go b/internal/config/config.go
index e3554e8..2efc8c2 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -18,6 +18,7 @@ type InstallConfig struct {
DryRun bool
Verbose bool
WorkspaceDir string
+ AssetDir string
}
type DetectedEnv struct {
diff --git a/internal/fcitx/fcitx.go b/internal/fcitx/fcitx.go
index fba202a..9710fe3 100644
--- a/internal/fcitx/fcitx.go
+++ b/internal/fcitx/fcitx.go
@@ -25,6 +25,11 @@ var pacmanPackages = []string{
"opencc",
}
+func init() {
+ // fcitx5 reads "key = value" as the key "key ".
+ ini.PrettyFormat = false
+}
+
const (
managedBlockStart = "# >>> rime-ice-installer ime begin >>>"
managedBlockEnd = "# <<< rime-ice-installer ime end <<<"
@@ -34,10 +39,23 @@ const (
defaultGroupName = "Default"
)
-func InstallPackages(ctx context.Context, runner *system.Runner, cfg config.InstallConfig, env config.DetectedEnv) error {
- args := append([]string{"-S", "--needed", "--noconfirm"}, pacmanPackages...)
- if err := runner.RunPrivileged(ctx, "pacman", args...); err != nil {
- return err
+// MissingPackages 返回尚未安装的 pacman 包。
+func MissingPackages() []string {
+ var missing []string
+ for _, pkg := range pacmanPackages {
+ if exec.Command("pacman", "-Q", pkg).Run() != nil {
+ missing = append(missing, pkg)
+ }
+ }
+ return missing
+}
+
+func InstallPackages(ctx context.Context, runner *system.Runner, missing []string, env config.DetectedEnv) error {
+ if len(missing) > 0 {
+ args := append([]string{"-S", "--needed", "--noconfirm"}, missing...)
+ if err := runner.RunPrivileged(ctx, "pacman", args...); err != nil {
+ return err
+ }
}
if !env.HasOctagramPlugin {
diff --git a/internal/fcitx/ini_format_test.go b/internal/fcitx/ini_format_test.go
new file mode 100644
index 0000000..3081d96
--- /dev/null
+++ b/internal/fcitx/ini_format_test.go
@@ -0,0 +1,38 @@
+package fcitx
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestConfigFilesUseFcitxKeyValueFormat(t *testing.T) {
+ dir := t.TempDir()
+ config := filepath.Join(dir, "config")
+ profile := filepath.Join(dir, "profile")
+ if err := ensureGlobalConfig(config); err != nil {
+ t.Fatal(err)
+ }
+ if err := ensureProfile(profile); err != nil {
+ t.Fatal(err)
+ }
+
+ for path, lines := range map[string][]string{
+ config: {"0=Control+space", "ModifierOnlyKeyTimeout=-1"},
+ profile: {"Name=" + defaultGroupName, "Default Layout=us", "DefaultIM=rime", "Name=" + keyboardUS, "Name=rime"},
+ } {
+ content, err := os.ReadFile(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if strings.Contains(string(content), " = ") {
+ t.Fatalf("%s pads \"=\" with spaces, which fcitx5 keeps in the key name:\n%s", filepath.Base(path), content)
+ }
+ for _, line := range lines {
+ if !strings.Contains("\n"+string(content)+"\n", "\n"+line+"\n") {
+ t.Fatalf("%s is missing the line %q:\n%s", filepath.Base(path), line, content)
+ }
+ }
+ }
+}
diff --git a/internal/rime/asset_dir_test.go b/internal/rime/asset_dir_test.go
new file mode 100644
index 0000000..9e66346
--- /dev/null
+++ b/internal/rime/asset_dir_test.go
@@ -0,0 +1,49 @@
+package rime
+
+import (
+ "archive/zip"
+ "context"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func TestPrepareWorkspaceReadsFullZipFromAssetDir(t *testing.T) {
+ assetDir := t.TempDir()
+ archive, err := os.Create(filepath.Join(assetDir, rimeIceAsset))
+ if err != nil {
+ t.Fatal(err)
+ }
+ writer := zip.NewWriter(archive)
+ entry, err := writer.Create("rime_ice.schema.yaml")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := entry.Write([]byte("schema:\n schema_id: rime_ice\n")); err != nil {
+ t.Fatal(err)
+ }
+ if err := writer.Close(); err != nil {
+ t.Fatal(err)
+ }
+ archive.Close()
+
+ workspace := t.TempDir()
+ prepared, err := PrepareWorkspace(context.Background(), nil, workspace, assetDir)
+ if err != nil {
+ t.Fatalf("PrepareWorkspace() error = %v", err)
+ }
+ if prepared.Release.Tag != "local" || prepared.Release.AssetName != rimeIceAsset {
+ t.Fatalf("release = %+v, want local %s", prepared.Release, rimeIceAsset)
+ }
+ for _, name := range []string{"rime_ice.schema.yaml", "default.custom.yaml", "user.yaml"} {
+ if _, err := os.Stat(filepath.Join(prepared.SourceDir, name)); err != nil {
+ t.Fatalf("%s missing from prepared workspace: %v", name, err)
+ }
+ }
+}
+
+func TestPrepareWorkspaceFailsWithoutLocalFullZip(t *testing.T) {
+ if _, err := PrepareWorkspace(context.Background(), nil, t.TempDir(), t.TempDir()); err == nil {
+ t.Fatal("PrepareWorkspace() succeeded without full.zip in the asset dir")
+ }
+}
diff --git a/internal/rime/rime.go b/internal/rime/rime.go
index 1db520b..45900be 100644
--- a/internal/rime/rime.go
+++ b/internal/rime/rime.go
@@ -26,20 +26,28 @@ type PreparedWorkspace struct {
Release config.ReleaseInfo
}
-func PrepareWorkspace(ctx context.Context, client *download.Client, workspace string) (*PreparedWorkspace, error) {
- release, err := client.ReleaseByTag(ctx, rimeIceOwner, rimeIceRepo, rimeIceTag)
- if err != nil {
- return nil, err
- }
+func PrepareWorkspace(ctx context.Context, client *download.Client, workspace, assetDir string) (*PreparedWorkspace, error) {
+ info := config.ReleaseInfo{Owner: rimeIceOwner, Repo: rimeIceRepo, Tag: "local", AssetName: rimeIceAsset}
+ downloadPath := filepath.Join(assetDir, rimeIceAsset)
+ if assetDir == "" {
+ release, err := client.ReleaseByTag(ctx, rimeIceOwner, rimeIceRepo, rimeIceTag)
+ if err != nil {
+ return nil, err
+ }
- asset, err := download.FindAsset(release, rimeIceAsset)
- if err != nil {
- return nil, err
- }
+ asset, err := download.FindAsset(release, rimeIceAsset)
+ if err != nil {
+ return nil, err
+ }
- downloadPath := filepath.Join(workspace, "downloads", asset.Name)
- if err := client.DownloadAsset(ctx, asset, downloadPath); err != nil {
- return nil, err
+ downloadPath = filepath.Join(workspace, "downloads", asset.Name)
+ if err := client.DownloadAsset(ctx, asset, downloadPath); err != nil {
+ return nil, err
+ }
+ info.Tag = release.TagName
+ info.AssetName = asset.Name
+ info.Digest = normalizeDigest(asset.Digest)
+ info.URL = asset.BrowserDownloadURL
}
sourceDir := filepath.Join(workspace, "source", "rime-ice")
@@ -54,17 +62,7 @@ func PrepareWorkspace(ctx context.Context, client *download.Client, workspace st
return nil, err
}
- return &PreparedWorkspace{
- SourceDir: sourceDir,
- Release: config.ReleaseInfo{
- Owner: rimeIceOwner,
- Repo: rimeIceRepo,
- Tag: release.TagName,
- AssetName: asset.Name,
- Digest: normalizeDigest(asset.Digest),
- URL: asset.BrowserDownloadURL,
- },
- }, nil
+ return &PreparedWorkspace{SourceDir: sourceDir, Release: info}, nil
}
func MergeDefaultCustomConfig(path string) error {
diff --git a/internal/wanxiang/asset_dir_test.go b/internal/wanxiang/asset_dir_test.go
new file mode 100644
index 0000000..23554b3
--- /dev/null
+++ b/internal/wanxiang/asset_dir_test.go
@@ -0,0 +1,33 @@
+package wanxiang
+
+import (
+ "context"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestIntegrateReadsModelFromAssetDir(t *testing.T) {
+ assetDir := t.TempDir()
+ if err := os.WriteFile(filepath.Join(assetDir, modelAsset), []byte("model"), 0o644); err != nil {
+ t.Fatal(err)
+ }
+
+ sourceDir := t.TempDir()
+ info, err := Integrate(context.Background(), nil, t.TempDir(), sourceDir, assetDir)
+ if err != nil {
+ t.Fatalf("Integrate() error = %v", err)
+ }
+ if info.Tag != "local" || info.AssetName != modelAsset {
+ t.Fatalf("release = %+v, want local %s", info, modelAsset)
+ }
+ model, err := os.ReadFile(filepath.Join(sourceDir, modelAsset))
+ if err != nil || string(model) != "model" {
+ t.Fatalf("model copy = %q, %v", model, err)
+ }
+ custom, err := os.ReadFile(filepath.Join(sourceDir, "rime_ice.custom.yaml"))
+ if err != nil || !strings.Contains(string(custom), "wanxiang-lts-zh-hans") {
+ t.Fatalf("custom yaml = %q, %v", custom, err)
+ }
+}
diff --git a/internal/wanxiang/wanxiang.go b/internal/wanxiang/wanxiang.go
index f629977..a0fd266 100644
--- a/internal/wanxiang/wanxiang.go
+++ b/internal/wanxiang/wanxiang.go
@@ -21,23 +21,31 @@ const (
modelAsset = "wanxiang-lts-zh-hans.gram"
)
-func Integrate(ctx context.Context, client *download.Client, workspace, sourceDir string) (*config.ReleaseInfo, error) {
- release, err := client.ReleaseByTag(ctx, modelOwner, modelRepo, modelTag)
- if err != nil {
- return nil, err
- }
+func Integrate(ctx context.Context, client *download.Client, workspace, sourceDir, assetDir string) (*config.ReleaseInfo, error) {
+ info := &config.ReleaseInfo{Owner: modelOwner, Repo: modelRepo, Tag: "local", AssetName: modelAsset}
+ downloadPath := filepath.Join(assetDir, modelAsset)
+ if assetDir == "" {
+ release, err := client.ReleaseByTag(ctx, modelOwner, modelRepo, modelTag)
+ if err != nil {
+ return nil, err
+ }
- asset, err := download.FindAsset(release, modelAsset)
- if err != nil {
- return nil, err
- }
+ asset, err := download.FindAsset(release, modelAsset)
+ if err != nil {
+ return nil, err
+ }
- downloadPath := filepath.Join(workspace, "downloads", asset.Name)
- if err := client.DownloadAsset(ctx, asset, downloadPath); err != nil {
- return nil, err
+ downloadPath = filepath.Join(workspace, "downloads", asset.Name)
+ if err := client.DownloadAsset(ctx, asset, downloadPath); err != nil {
+ return nil, err
+ }
+ info.Tag = release.TagName
+ info.AssetName = asset.Name
+ info.Digest = strings.TrimPrefix(asset.Digest, "sha256:")
+ info.URL = asset.BrowserDownloadURL
}
- targetModelPath := filepath.Join(sourceDir, asset.Name)
+ targetModelPath := filepath.Join(sourceDir, modelAsset)
modelContent, err := os.ReadFile(downloadPath)
if err != nil {
return nil, fmt.Errorf("读取模型文件失败: %w", err)
@@ -50,14 +58,7 @@ func Integrate(ctx context.Context, client *download.Client, workspace, sourceDi
return nil, err
}
- return &config.ReleaseInfo{
- Owner: modelOwner,
- Repo: modelRepo,
- Tag: release.TagName,
- AssetName: asset.Name,
- Digest: strings.TrimPrefix(asset.Digest, "sha256:"),
- URL: asset.BrowserDownloadURL,
- }, nil
+ return info, nil
}
func MergeCustomConfig(path string) error {
diff --git a/main.go b/main.go
index 50f00f3..15439bd 100644
--- a/main.go
+++ b/main.go
@@ -19,6 +19,7 @@ func main() {
flag.BoolVar(&cfg.EnableWanxiang, "enable-wanxiang", cfg.EnableWanxiang, "下载并启用万象 LTS 语法模型")
flag.IntVar(&cfg.FontSize, "font-size", cfg.FontSize, "Fcitx5 候选框字体大小(8-48)")
flag.StringVar(&cfg.WorkspaceDir, "workspace-dir", cfg.WorkspaceDir, "工作目录,用于下载缓存和日志")
+ flag.StringVar(&cfg.AssetDir, "asset-dir", "", "从此目录读取 full.zip 和万象模型,不访问 GitHub")
flag.Parse()
if err := app.Run(context.Background(), cfg); err != nil {