Compare commits
38 Commits
1480f5b617
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ad8f51a05 | |||
| d7ab3e7056 | |||
| fe6a71ff25 | |||
| b2a77b5d93 | |||
| e8b9ad6bdf | |||
| ce4f22ba16 | |||
|
|
06951f92aa | ||
| b9277ec2da | |||
| a1cfb6d7dd | |||
| 429c1678d3 | |||
| ef4036f3c7 | |||
| 0f1b730fde | |||
| 4c5fff5390 | |||
| 34c8f2a185 | |||
|
|
61d6e70fd3 | ||
|
|
e2fc5cdbfe | ||
|
|
5036ffe451 | ||
|
|
0328663185 | ||
|
|
b6b6d20672 | ||
|
|
17224544bb | ||
|
|
457b7a398b | ||
| 9d7f4f7149 | |||
| a1544d6fd5 | |||
| 4838d965da | |||
| a873a9e244 | |||
| 184c5241f3 | |||
| f8b7b69a91 | |||
|
|
643aa6d395 | ||
| 6909def4e2 | |||
| 9cfc02ebc3 | |||
|
|
0ebe3f33df | ||
|
|
0354e1b59d | ||
|
|
55c4593e2f | ||
|
|
005a388e22 | ||
|
|
d638383af3 | ||
|
|
50d92ca663 | ||
| 37d3d926c1 | |||
|
|
ef562a7df4 |
@@ -18,6 +18,14 @@ import * as application$0 from "../../application/models.js";
|
||||
// @ts-ignore: Unused imports
|
||||
import * as $models from "./models.js";
|
||||
|
||||
/**
|
||||
* GetBadge returns the badge label on the application icon.
|
||||
*/
|
||||
export function GetBadge(): Promise<string | null> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1150236961) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* HideAppIcon hides the app icon in the dock/taskbar.
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,79 +0,0 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* BackupService 提供基于Git的备份同步功能
|
||||
* @module
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as models$0 from "../models/models.js";
|
||||
|
||||
/**
|
||||
* HandleConfigChange 处理配置变更
|
||||
*/
|
||||
export function HandleConfigChange(config: models$0.GitBackupConfig | null): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(395287784, config) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize 初始化备份服务
|
||||
*/
|
||||
export function Initialize(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1052437974) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinitialize 重新初始化
|
||||
*/
|
||||
export function Reinitialize(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(301562543) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceShutdown 服务关闭
|
||||
*/
|
||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(422131801) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2900331732, options) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* StartAutoBackup 启动自动备份
|
||||
*/
|
||||
export function StartAutoBackup(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3035755449) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* StopAutoBackup 停止自动备份
|
||||
*/
|
||||
export function StopAutoBackup(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(2641894021) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync 执行完整的同步流程:导出 -> commit -> pull -> 解决冲突 -> push -> 导入
|
||||
*/
|
||||
export function Sync(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(3420383211) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
import * as BackupService from "./backupservice.js";
|
||||
import * as ConfigService from "./configservice.js";
|
||||
import * as DatabaseService from "./databaseservice.js";
|
||||
import * as DialogService from "./dialogservice.js";
|
||||
import * as DocumentService from "./documentservice.js";
|
||||
import * as ExtensionService from "./extensionservice.js";
|
||||
import * as HotkeyService from "./hotkeyservice.js";
|
||||
import * as HttpClientService from "./httpclientservice.js";
|
||||
import * as KeyBindingService from "./keybindingservice.js";
|
||||
import * as MigrationService from "./migrationservice.js";
|
||||
import * as SelfUpdateService from "./selfupdateservice.js";
|
||||
import * as StartupService from "./startupservice.js";
|
||||
import * as SystemService from "./systemservice.js";
|
||||
import * as TestService from "./testservice.js";
|
||||
import * as ThemeService from "./themeservice.js";
|
||||
import * as TranslationService from "./translationservice.js";
|
||||
import * as WindowService from "./windowservice.js";
|
||||
export {
|
||||
BackupService,
|
||||
ConfigService,
|
||||
DatabaseService,
|
||||
DialogService,
|
||||
DocumentService,
|
||||
ExtensionService,
|
||||
HotkeyService,
|
||||
HttpClientService,
|
||||
KeyBindingService,
|
||||
MigrationService,
|
||||
SelfUpdateService,
|
||||
StartupService,
|
||||
SystemService,
|
||||
TestService,
|
||||
ThemeService,
|
||||
TranslationService,
|
||||
WindowService
|
||||
};
|
||||
|
||||
export * from "./models.js";
|
||||
3719
frontend/package-lock.json
generated
3719
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -22,19 +22,19 @@
|
||||
"app:generate": "cd .. && wails3 generate bindings -ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.20.0",
|
||||
"@codemirror/commands": "^6.10.1",
|
||||
"@codemirror/autocomplete": "^6.20.1",
|
||||
"@codemirror/commands": "^6.10.3",
|
||||
"@codemirror/lang-angular": "^0.1.4",
|
||||
"@codemirror/lang-cpp": "^6.0.3",
|
||||
"@codemirror/lang-css": "^6.3.1",
|
||||
"@codemirror/lang-go": "^6.0.1",
|
||||
"@codemirror/lang-html": "^6.4.11",
|
||||
"@codemirror/lang-java": "^6.0.2",
|
||||
"@codemirror/lang-javascript": "^6.2.4",
|
||||
"@codemirror/lang-javascript": "^6.2.5",
|
||||
"@codemirror/lang-json": "^6.0.2",
|
||||
"@codemirror/lang-less": "^6.0.2",
|
||||
"@codemirror/lang-lezer": "^6.0.2",
|
||||
"@codemirror/lang-liquid": "^6.3.1",
|
||||
"@codemirror/lang-liquid": "^6.3.2",
|
||||
"@codemirror/lang-markdown": "^6.5.0",
|
||||
"@codemirror/lang-php": "^6.0.2",
|
||||
"@codemirror/lang-python": "^6.2.1",
|
||||
@@ -43,65 +43,61 @@
|
||||
"@codemirror/lang-sql": "^6.10.0",
|
||||
"@codemirror/lang-vue": "^0.1.3",
|
||||
"@codemirror/lang-wast": "^6.0.2",
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
"@codemirror/language": "^6.12.1",
|
||||
"@codemirror/lang-yaml": "^6.1.3",
|
||||
"@codemirror/language": "^6.12.3",
|
||||
"@codemirror/language-data": "^6.5.2",
|
||||
"@codemirror/legacy-modes": "^6.5.2",
|
||||
"@codemirror/lint": "^6.9.2",
|
||||
"@codemirror/search": "^6.5.11",
|
||||
"@codemirror/state": "^6.5.3",
|
||||
"@codemirror/view": "^6.39.8",
|
||||
"@codemirror/lint": "^6.9.5",
|
||||
"@codemirror/search": "^6.6.0",
|
||||
"@codemirror/state": "^6.6.0",
|
||||
"@codemirror/view": "^6.40.0",
|
||||
"@cospaia/prettier-plugin-clojure": "^0.0.2",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@lezer/lr": "^1.4.5",
|
||||
"@lezer/lr": "^1.4.8",
|
||||
"@prettier/plugin-xml": "^3.4.2",
|
||||
"@replit/codemirror-lang-svelte": "^6.0.0",
|
||||
"@toml-tools/lexer": "^1.0.1",
|
||||
"@toml-tools/parser": "^1.0.1",
|
||||
"@types/katex": "^0.16.7",
|
||||
"@zumer/snapdom": "^2.0.1",
|
||||
"@types/katex": "^0.16.8",
|
||||
"@zumer/snapdom": "^2.7.0",
|
||||
"codemirror": "^6.0.2",
|
||||
"codemirror-lang-elixir": "^4.0.0",
|
||||
"colors-named": "^1.0.4",
|
||||
"colors-named-hex": "^1.0.3",
|
||||
"codemirror-lang-elixir": "^4.0.1",
|
||||
"colors-named": "^1.0.5",
|
||||
"colors-named-hex": "^1.0.4",
|
||||
"groovy-beautify": "^0.0.17",
|
||||
"hsl-matcher": "^1.2.4",
|
||||
"java-parser": "^3.0.1",
|
||||
"katex": "^0.16.27",
|
||||
"linguist-languages": "^9.1.11",
|
||||
"marked": "^17.0.1",
|
||||
"mermaid": "^11.12.2",
|
||||
"php-parser": "^3.2.5",
|
||||
"katex": "^0.16.44",
|
||||
"linguist-languages": "^9.3.1",
|
||||
"marked": "^17.0.5",
|
||||
"mermaid": "^11.13.0",
|
||||
"php-parser": "^3.5.0",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"prettier": "^3.7.4",
|
||||
"sass": "^1.97.1",
|
||||
"vue": "^3.5.26",
|
||||
"vue-i18n": "^11.2.8",
|
||||
"prettier": "^3.8.1",
|
||||
"sass": "^1.98.0",
|
||||
"vue": "^3.5.31",
|
||||
"vue-i18n": "^11.3.0",
|
||||
"vue-pick-colors": "^1.8.0",
|
||||
"vue-router": "^4.6.4"
|
||||
"vue-router": "^5.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@lezer/generator": "^1.8.0",
|
||||
"@types/node": "^25.0.3",
|
||||
"@vitejs/plugin-vue": "^6.0.3",
|
||||
"@wailsio/runtime": "^3.0.0-alpha.77",
|
||||
"@types/node": "^25.5.0",
|
||||
"@vitejs/plugin-vue": "^6.0.5",
|
||||
"@wailsio/runtime": "^3.0.0-alpha.79",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-plugin-vue": "^10.6.2",
|
||||
"globals": "^16.5.0",
|
||||
"eslint": "^10.1.0",
|
||||
"eslint-plugin-vue": "^10.8.0",
|
||||
"globals": "^17.4.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.51.0",
|
||||
"unplugin-vue-components": "^30.0.0",
|
||||
"vite": "npm:rolldown-vite@latest",
|
||||
"vite-plugin-node-polyfills": "^0.24.0",
|
||||
"typescript-eslint": "^8.57.2",
|
||||
"unplugin-vue-components": "^32.0.0",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-node-polyfills": "^0.25.0",
|
||||
"vitepress": "^2.0.0-alpha.12",
|
||||
"vitest": "^4.0.16",
|
||||
"vue-eslint-parser": "^10.2.0",
|
||||
"vue-tsc": "^3.2.1"
|
||||
},
|
||||
"overrides": {
|
||||
"vite": "npm:rolldown-vite@latest"
|
||||
"vitest": "^4.1.2",
|
||||
"vue-eslint-parser": "^10.4.0",
|
||||
"vue-tsc": "^3.2.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
AppConfig,
|
||||
AuthMethod,
|
||||
SyncTarget,
|
||||
KeyBindingType,
|
||||
LanguageType,
|
||||
SystemThemeType,
|
||||
@@ -8,12 +9,8 @@ import {
|
||||
} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {FONT_OPTIONS} from './fonts';
|
||||
|
||||
export type NumberConfigKey = 'fontSize' | 'tabSize' | 'lineHeight';
|
||||
export type ConfigSection = 'general' | 'editing' | 'appearance' | 'updates' | 'backup';
|
||||
|
||||
// 统一配置键映射(平级展开)
|
||||
export const CONFIG_KEY_MAP = {
|
||||
// general
|
||||
alwaysOnTop: 'general.alwaysOnTop',
|
||||
dataPath: 'general.dataPath',
|
||||
enableSystemTray: 'general.enableSystemTray',
|
||||
@@ -24,7 +21,7 @@ export const CONFIG_KEY_MAP = {
|
||||
enableLoadingAnimation: 'general.enableLoadingAnimation',
|
||||
enableTabs: 'general.enableTabs',
|
||||
enableMemoryMonitor: 'general.enableMemoryMonitor',
|
||||
// editing
|
||||
|
||||
fontSize: 'editing.fontSize',
|
||||
fontFamily: 'editing.fontFamily',
|
||||
fontWeight: 'editing.fontWeight',
|
||||
@@ -34,33 +31,35 @@ export const CONFIG_KEY_MAP = {
|
||||
tabType: 'editing.tabType',
|
||||
keymapMode: 'editing.keymapMode',
|
||||
autoSaveDelay: 'editing.autoSaveDelay',
|
||||
// appearance
|
||||
|
||||
language: 'appearance.language',
|
||||
systemTheme: 'appearance.systemTheme',
|
||||
currentTheme: 'appearance.currentTheme',
|
||||
// updates
|
||||
version: 'updates.version',
|
||||
|
||||
autoUpdate: 'updates.autoUpdate',
|
||||
primarySource: 'updates.primarySource',
|
||||
backupSource: 'updates.backupSource',
|
||||
backupBeforeUpdate: 'updates.backupBeforeUpdate',
|
||||
updateTimeout: 'updates.updateTimeout',
|
||||
github: 'updates.github',
|
||||
gitea: 'updates.gitea',
|
||||
// backup
|
||||
enabled: 'backup.enabled',
|
||||
repo_url: 'backup.repo_url',
|
||||
auth_method: 'backup.auth_method',
|
||||
username: 'backup.username',
|
||||
password: 'backup.password',
|
||||
token: 'backup.token',
|
||||
ssh_key_path: 'backup.ssh_key_path',
|
||||
ssh_key_passphrase: 'backup.ssh_key_passphrase',
|
||||
backup_interval: 'backup.backup_interval',
|
||||
auto_backup: 'backup.auto_backup',
|
||||
|
||||
sync_target: 'sync.target',
|
||||
git_enabled: 'sync.git.enabled',
|
||||
git_auto_sync: 'sync.git.auto_sync',
|
||||
git_sync_interval: 'sync.git.sync_interval',
|
||||
git_repo_url: 'sync.git.repo_url',
|
||||
git_auth_method: 'sync.git.auth_method',
|
||||
git_username: 'sync.git.username',
|
||||
git_password: 'sync.git.password',
|
||||
git_token: 'sync.git.token',
|
||||
git_ssh_key_path: 'sync.git.ssh_key_path',
|
||||
git_ssh_key_passphrase: 'sync.git.ssh_key_passphrase',
|
||||
localfs_enabled: 'sync.localfs.enabled',
|
||||
localfs_auto_sync: 'sync.localfs.auto_sync',
|
||||
localfs_sync_interval: 'sync.localfs.sync_interval',
|
||||
localfs_root_path: 'sync.localfs.root_path',
|
||||
} as const;
|
||||
|
||||
export type ConfigKey = keyof typeof CONFIG_KEY_MAP;
|
||||
export type NumberConfigKey = 'fontSize' | 'tabSize' | 'lineHeight';
|
||||
|
||||
// 配置限制
|
||||
export const CONFIG_LIMITS = {
|
||||
@@ -116,17 +115,26 @@ export const DEFAULT_CONFIG: AppConfig = {
|
||||
repo: "voidraft",
|
||||
},
|
||||
},
|
||||
backup: {
|
||||
sync: {
|
||||
target: SyncTarget.SyncTargetGit,
|
||||
git: {
|
||||
enabled: false,
|
||||
repo_url: "",
|
||||
auto_sync: false,
|
||||
sync_interval: 60,
|
||||
repo_url: '',
|
||||
auth_method: AuthMethod.UserPass,
|
||||
username: "",
|
||||
password: "",
|
||||
token: "",
|
||||
ssh_key_path: "",
|
||||
ssh_key_passphrase: "",
|
||||
backup_interval: 60,
|
||||
auto_backup: true,
|
||||
username: '',
|
||||
password: '',
|
||||
token: '',
|
||||
ssh_key_path: '',
|
||||
ssh_key_passphrase: '',
|
||||
},
|
||||
localfs: {
|
||||
enabled: false,
|
||||
auto_sync: false,
|
||||
sync_interval: 60,
|
||||
root_path: '',
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
version: '1.0.0',
|
||||
|
||||
@@ -23,7 +23,7 @@ const languages = [
|
||||
{
|
||||
name: 'C',
|
||||
aliases: ['c'],
|
||||
parsers: ['c'],
|
||||
parsers: [parserName],
|
||||
extensions: ['.c', '.h'],
|
||||
filenames: ['*.c', '*.h'],
|
||||
aceMode: 'c_cpp',
|
||||
@@ -34,7 +34,7 @@ const languages = [
|
||||
{
|
||||
name: 'C++',
|
||||
aliases: ['cpp', 'cxx', 'cc'],
|
||||
parsers: ['cpp'],
|
||||
parsers: [parserName],
|
||||
extensions: ['.cpp', '.cxx', '.cc', '.hpp', '.hxx', '.hh', '.C', '.H'],
|
||||
filenames: ['*.cpp', '*.cxx', '*.cc', '*.hpp', '*.hxx', '*.hh', '*.C', '*.H'],
|
||||
aceMode: 'c_cpp',
|
||||
@@ -45,7 +45,7 @@ const languages = [
|
||||
{
|
||||
name: 'Objective-C',
|
||||
aliases: ['objc', 'objectivec'],
|
||||
parsers: ['objective-c'],
|
||||
parsers: [parserName],
|
||||
extensions: ['.m'],
|
||||
filenames: ['*.m'],
|
||||
aceMode: 'objectivec',
|
||||
@@ -56,7 +56,7 @@ const languages = [
|
||||
{
|
||||
name: 'Objective-C++',
|
||||
aliases: ['objcpp', 'objectivecpp'],
|
||||
parsers: ['objective-cpp'],
|
||||
parsers: [parserName],
|
||||
extensions: ['.mm'],
|
||||
filenames: ['*.mm'],
|
||||
aceMode: 'objectivec',
|
||||
@@ -67,7 +67,7 @@ const languages = [
|
||||
{
|
||||
name: 'C#',
|
||||
aliases: ['csharp', 'cs'],
|
||||
parsers: ['cs'],
|
||||
parsers: [parserName],
|
||||
extensions: ['.cs'],
|
||||
filenames: ['*.cs'],
|
||||
aceMode: 'csharp',
|
||||
@@ -78,7 +78,7 @@ const languages = [
|
||||
{
|
||||
name: 'Java',
|
||||
aliases: ['java'],
|
||||
parsers: ['java'],
|
||||
parsers: [parserName],
|
||||
extensions: ['.java'],
|
||||
filenames: ['*.java'],
|
||||
aceMode: 'java',
|
||||
@@ -89,7 +89,7 @@ const languages = [
|
||||
{
|
||||
name: 'Protocol Buffer',
|
||||
aliases: ['protobuf', 'proto'],
|
||||
parsers: ['proto'],
|
||||
parsers: [parserName],
|
||||
extensions: ['.proto'],
|
||||
filenames: ['*.proto'],
|
||||
aceMode: 'protobuf',
|
||||
@@ -158,17 +158,7 @@ const clangPrinter: Printer<string> = {
|
||||
|
||||
// Helper function to determine clang-format style
|
||||
function getClangStyle(options: any): string {
|
||||
// You can extend this to support more options
|
||||
const style = options.clangStyle || 'LLVM';
|
||||
|
||||
// Support common styles
|
||||
const validStyles = ['LLVM', 'Google', 'Chromium', 'Mozilla', 'WebKit', 'Microsoft', 'GNU'];
|
||||
if (validStyles.includes(style)) {
|
||||
return style;
|
||||
}
|
||||
|
||||
// Default to LLVM style
|
||||
return 'LLVM';
|
||||
return options.clangStyle || 'LLVM';
|
||||
}
|
||||
|
||||
// Plugin options
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
module docker_fmt
|
||||
|
||||
go 1.25.0
|
||||
go 1.25.5
|
||||
|
||||
require (
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/moby/buildkit v0.24.0
|
||||
github.com/moby/buildkit v0.28.1
|
||||
mvdan.cc/sh/v3 v3.12.0
|
||||
)
|
||||
|
||||
@@ -13,5 +13,5 @@ require (
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
)
|
||||
|
||||
@@ -16,22 +16,18 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/moby/buildkit v0.20.2 h1:qIeR47eQ1tzI1rwz0on3Xx2enRw/1CKjFhoONVcTlMA=
|
||||
github.com/moby/buildkit v0.20.2/go.mod h1:DhaF82FjwOElTftl0JUAJpH/SUIUx4UvcFncLeOtlDI=
|
||||
github.com/moby/buildkit v0.24.0 h1:qYfTl7W1SIJzWDIDCcPT8FboHIZCYfi++wvySi3eyFE=
|
||||
github.com/moby/buildkit v0.24.0/go.mod h1:4qovICAdR2H4C7+EGMRva5zgHW1gyhT4/flHI7F5F9k=
|
||||
github.com/moby/buildkit v0.28.1 h1:Tq6H6gOMU2JyEQ5rA0pa7Ey3VGNR3qpw90liSIpMQoo=
|
||||
github.com/moby/buildkit v0.28.1/go.mod h1:xO6wb9VBXszkIBxaGTLXc1rQORVQFIJRt3GSX7KzCFc=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/reteps/dockerfmt v0.3.7 h1:GChhICBoy6oiTuoLTLFtGnfyBi2qY9dvHBhrcWrN8Zk=
|
||||
github.com/reteps/dockerfmt v0.3.7/go.mod h1:5lpbp1KzLWaRhL7qB6IEutHoQK3ZcT2Lb5MWPmFts74=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -59,13 +55,9 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
|
||||
mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
|
||||
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
|
||||
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { IToken } from "java-parser";
|
||||
import { type AstPath } from "prettier";
|
||||
import { type JavaNode, type JavaNonTerminal, type JavaParserOptions } from "./printers/helpers.js";
|
||||
export declare function determineFormatterOffOnRanges(cst: JavaNonTerminal): void;
|
||||
export declare function isFullyBetweenFormatterOffOn(path: AstPath<JavaNode>): boolean;
|
||||
export declare function canAttachComment(node: JavaNode): boolean;
|
||||
export declare function handleLineComment(commentNode: JavaComment, _: string, options: JavaParserOptions): boolean;
|
||||
export declare function handleRemainingComment(commentNode: JavaComment): boolean;
|
||||
export type JavaComment = IToken & {
|
||||
value: string;
|
||||
leading: boolean;
|
||||
trailing: boolean;
|
||||
printed: boolean;
|
||||
enclosingNode?: JavaNonTerminal;
|
||||
precedingNode?: JavaNonTerminal;
|
||||
followingNode?: JavaNonTerminal;
|
||||
};
|
||||
@@ -1,199 +0,0 @@
|
||||
import { util } from "prettier";
|
||||
import parser from "./parser.js";
|
||||
import { isEmptyStatement, isNonTerminal, isTerminal } from "./printers/helpers.js";
|
||||
const formatterOffOnRangesByCst = new WeakMap();
|
||||
export function determineFormatterOffOnRanges(cst) {
|
||||
const { comments } = cst;
|
||||
if (!comments) {
|
||||
return;
|
||||
}
|
||||
const ranges = comments
|
||||
.filter(({ image }) => /^(\/\/\s*@formatter:(off|on)\s*|\/\*\s*@formatter:(off|on)\s*\*\/)$/.test(image))
|
||||
.reduce((ranges, { image, startOffset }) => {
|
||||
const previous = ranges.at(-1);
|
||||
if (image.endsWith("off")) {
|
||||
if ((previous === null || previous === void 0 ? void 0 : previous.on) !== Infinity) {
|
||||
ranges.push({ off: startOffset, on: Infinity });
|
||||
}
|
||||
}
|
||||
else if ((previous === null || previous === void 0 ? void 0 : previous.on) === Infinity) {
|
||||
previous.on = startOffset;
|
||||
}
|
||||
return ranges;
|
||||
}, new Array());
|
||||
formatterOffOnRangesByCst.set(cst, ranges);
|
||||
}
|
||||
export function isFullyBetweenFormatterOffOn(path) {
|
||||
var _a;
|
||||
const { node, root } = path;
|
||||
const start = parser.locStart(node);
|
||||
const end = parser.locEnd(node);
|
||||
return (((_a = formatterOffOnRangesByCst
|
||||
.get(root)) === null || _a === void 0 ? void 0 : _a.some(range => range.off < start && end < range.on)) === true);
|
||||
}
|
||||
export function canAttachComment(node) {
|
||||
var _a, _b, _c;
|
||||
if (isTerminal(node)) {
|
||||
const { name, CATEGORIES } = node.tokenType;
|
||||
return (name === "Identifier" ||
|
||||
(CATEGORIES === null || CATEGORIES === void 0 ? void 0 : CATEGORIES.find(({ name }) => name === "BinaryOperator")) !== undefined);
|
||||
}
|
||||
const { children, name } = node;
|
||||
switch (name) {
|
||||
case "argumentList":
|
||||
case "blockStatements":
|
||||
case "emptyStatement":
|
||||
case "enumBodyDeclarations":
|
||||
return false;
|
||||
case "annotationInterfaceMemberDeclaration":
|
||||
case "classMemberDeclaration":
|
||||
case "interfaceMemberDeclaration":
|
||||
case "methodBody":
|
||||
return !children.Semicolon;
|
||||
case "blockStatement":
|
||||
return !children.statement || !isEmptyStatement(children.statement[0]);
|
||||
case "classBodyDeclaration":
|
||||
return !((_a = children.classMemberDeclaration) === null || _a === void 0 ? void 0 : _a[0].children.Semicolon);
|
||||
case "recordBodyDeclaration":
|
||||
return !((_c = (_b = children.classBodyDeclaration) === null || _b === void 0 ? void 0 : _b[0].children.classMemberDeclaration) === null || _c === void 0 ? void 0 : _c[0].children.Semicolon);
|
||||
case "statement":
|
||||
return !isEmptyStatement(node);
|
||||
case "statementWithoutTrailingSubstatement":
|
||||
return !children.emptyStatement;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
export function handleLineComment(commentNode, _, options) {
|
||||
return [
|
||||
handleBinaryExpressionComments,
|
||||
handleFqnOrRefTypeComments,
|
||||
handleIfStatementComments,
|
||||
handleJumpStatementComments,
|
||||
handleLabeledStatementComments,
|
||||
handleNameComments
|
||||
].some(fn => fn(commentNode, options));
|
||||
}
|
||||
export function handleRemainingComment(commentNode) {
|
||||
return [
|
||||
handleFqnOrRefTypeComments,
|
||||
handleMethodDeclaratorComments,
|
||||
handleNameComments,
|
||||
handleJumpStatementComments
|
||||
].some(fn => fn(commentNode));
|
||||
}
|
||||
function handleBinaryExpressionComments(commentNode, options) {
|
||||
const { enclosingNode, precedingNode, followingNode } = commentNode;
|
||||
if (enclosingNode &&
|
||||
isNonTerminal(enclosingNode) &&
|
||||
enclosingNode.name === "binaryExpression") {
|
||||
if (isBinaryOperator(followingNode)) {
|
||||
if (options.experimentalOperatorPosition === "start") {
|
||||
util.addLeadingComment(followingNode, commentNode);
|
||||
}
|
||||
else {
|
||||
util.addTrailingComment(followingNode, commentNode);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (options.experimentalOperatorPosition === "start" &&
|
||||
isBinaryOperator(precedingNode)) {
|
||||
util.addLeadingComment(precedingNode, commentNode);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function handleFqnOrRefTypeComments(commentNode) {
|
||||
const { enclosingNode, followingNode } = commentNode;
|
||||
if (enclosingNode &&
|
||||
isNonTerminal(enclosingNode) &&
|
||||
enclosingNode.name === "fqnOrRefType" &&
|
||||
followingNode) {
|
||||
util.addLeadingComment(followingNode, commentNode);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function handleIfStatementComments(commentNode) {
|
||||
const { enclosingNode, precedingNode } = commentNode;
|
||||
if (enclosingNode &&
|
||||
isNonTerminal(enclosingNode) &&
|
||||
enclosingNode.name === "ifStatement" &&
|
||||
precedingNode &&
|
||||
isNonTerminal(precedingNode) &&
|
||||
precedingNode.name === "statement") {
|
||||
util.addDanglingComment(enclosingNode, commentNode, undefined);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function handleJumpStatementComments(commentNode) {
|
||||
const { enclosingNode, precedingNode, followingNode } = commentNode;
|
||||
if (enclosingNode &&
|
||||
!precedingNode &&
|
||||
!followingNode &&
|
||||
isNonTerminal(enclosingNode) &&
|
||||
["breakStatement", "continueStatement", "returnStatement"].includes(enclosingNode.name)) {
|
||||
util.addTrailingComment(enclosingNode, commentNode);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function handleLabeledStatementComments(commentNode) {
|
||||
const { enclosingNode, precedingNode } = commentNode;
|
||||
if (enclosingNode &&
|
||||
precedingNode &&
|
||||
isNonTerminal(enclosingNode) &&
|
||||
enclosingNode.name === "labeledStatement" &&
|
||||
isTerminal(precedingNode) &&
|
||||
precedingNode.tokenType.name === "Identifier") {
|
||||
util.addLeadingComment(precedingNode, commentNode);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function handleMethodDeclaratorComments(commentNode) {
|
||||
const { enclosingNode } = commentNode;
|
||||
if (enclosingNode &&
|
||||
isNonTerminal(enclosingNode) &&
|
||||
enclosingNode.name === "methodDeclarator" &&
|
||||
!enclosingNode.children.receiverParameter &&
|
||||
!enclosingNode.children.formalParameterList &&
|
||||
enclosingNode.children.LBrace[0].startOffset < commentNode.startOffset &&
|
||||
commentNode.startOffset < enclosingNode.children.RBrace[0].startOffset) {
|
||||
util.addDanglingComment(enclosingNode, commentNode, undefined);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function handleNameComments(commentNode) {
|
||||
const { enclosingNode, precedingNode } = commentNode;
|
||||
if (enclosingNode &&
|
||||
precedingNode &&
|
||||
isNonTerminal(enclosingNode) &&
|
||||
isTerminal(precedingNode) &&
|
||||
precedingNode.tokenType.name === "Identifier" &&
|
||||
[
|
||||
"ambiguousName",
|
||||
"classOrInterfaceTypeToInstantiate",
|
||||
"expressionName",
|
||||
"moduleDeclaration",
|
||||
"moduleName",
|
||||
"packageDeclaration",
|
||||
"packageName",
|
||||
"packageOrTypeName",
|
||||
"typeName"
|
||||
].includes(enclosingNode.name)) {
|
||||
util.addTrailingComment(precedingNode, commentNode);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function isBinaryOperator(node) {
|
||||
var _a;
|
||||
return (node !== undefined &&
|
||||
(isNonTerminal(node)
|
||||
? node.name === "shiftOperator"
|
||||
: (_a = node.tokenType.CATEGORIES) === null || _a === void 0 ? void 0 : _a.some(({ name }) => name === "BinaryOperator")));
|
||||
}
|
||||
563
frontend/src/common/prettier/plugins/java/index.d.ts
vendored
563
frontend/src/common/prettier/plugins/java/index.d.ts
vendored
@@ -1,563 +0,0 @@
|
||||
import type { JavaNode } from "./printers/helpers.js";
|
||||
declare const _default: {
|
||||
languages: {
|
||||
name: string;
|
||||
parsers: "java"[];
|
||||
group: string;
|
||||
tmScope: string;
|
||||
aceMode: string;
|
||||
codemirrorMode: string;
|
||||
codemirrorMimeType: string;
|
||||
extensions: string[];
|
||||
linguistLanguageId: number;
|
||||
vscodeLanguageIds: string[];
|
||||
}[];
|
||||
parsers: {
|
||||
java: {
|
||||
parse(text: string, options: import("./printers/helpers.js").JavaParserOptions): import("./printers/helpers.js").JavaNonTerminal;
|
||||
astFormat: string;
|
||||
hasPragma(text: string): boolean;
|
||||
locStart(node: JavaNode): number;
|
||||
locEnd(node: JavaNode): number;
|
||||
};
|
||||
};
|
||||
printers: {
|
||||
java: {
|
||||
print(path: import("prettier").AstPath<import("java-parser").ArrayInitializerCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").VariableInitializerListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").BlockCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").BlockStatementsCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").LocalVariableDeclarationStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").LocalVariableDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").LabeledStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ExpressionStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").IfStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").AssertStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").SwitchStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").SwitchBlockCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").SwitchBlockStatementGroupCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").SwitchLabelCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").SwitchRuleCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").WhileStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").DoStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").BasicForStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").StatementExpressionListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").EnhancedForStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").BreakStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ContinueStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ReturnStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ThrowStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").SynchronizedStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TryStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").CatchesCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").CatchClauseCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").CatchFormalParameterCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").CatchTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").FinallyCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TryWithResourcesStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ResourceSpecificationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ResourceListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").YieldStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ForInitCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ForUpdateCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").StatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").StatementWithoutTrailingSubstatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ForStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").BlockStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").CaseConstantCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").CasePatternCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").EmptyStatementCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").StatementExpressionCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").LocalVariableTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ResourceCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").VariableAccessCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ClassDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").NormalClassDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TypeParametersCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TypeParameterListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ClassExtendsCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ClassImplementsCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").InterfaceTypeListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ClassBodyCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ClassMemberDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").FieldDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").VariableDeclaratorListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").VariableDeclaratorCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").VariableDeclaratorIdCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").UnannPrimitiveTypeWithOptionalDimsSuffixCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").UnannReferenceTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").MethodDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").MethodHeaderCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").MethodDeclaratorCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ReceiverParameterCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").FormalParameterListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").VariableParaRegularParameterCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").VariableArityParameterCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ThrowsCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ExceptionTypeListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").StaticInitializerCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ConstructorDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ConstructorDeclaratorCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ConstructorBodyCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").UnqualifiedExplicitConstructorInvocationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").QualifiedExplicitConstructorInvocationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").EnumDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").EnumBodyCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").EnumConstantListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").EnumConstantCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").EnumBodyDeclarationsCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").RecordDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").RecordHeaderCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").RecordComponentListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").RecordComponentCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").VariableArityRecordComponentCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").RecordBodyCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").CompactConstructorDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").UnannTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").VariableInitializerCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").VariableModifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").UnannClassTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ClassBodyDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").InstanceInitializerCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ClassModifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ClassPermitsCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").FieldModifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").MethodModifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").MethodBodyCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ConstructorModifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").SimpleTypeNameCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ExplicitConstructorInvocationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").EnumConstantModifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ExceptionTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").FormalParameterCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ResultCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").RecordBodyDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").RecordComponentModifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").UnannClassOrInterfaceTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").UnannInterfaceTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").UnannPrimitiveTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").UnannTypeVariableCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").LambdaExpressionCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").LambdaParametersCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").LambdaParametersWithBracesCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ConciseLambdaParameterListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").NormalLambdaParameterListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").RegularLambdaParameterCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ConditionalExpressionCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").BinaryExpressionCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").UnaryExpressionCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").UnaryExpressionNotPlusMinusCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").PrimaryCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").PrimarySuffixCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").FqnOrRefTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").FqnOrRefTypePartFirstCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").FqnOrRefTypePartRestCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").FqnOrRefTypePartCommonCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ParenthesisExpressionCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").PrimitiveCastExpressionCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ReferenceTypeCastExpressionCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").UnqualifiedClassInstanceCreationExpressionCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ClassOrInterfaceTypeToInstantiateCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").MethodInvocationSuffixCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ArgumentListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ArrayCreationExpressionCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ArrayCreationExpressionWithoutInitializerSuffixCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ArrayCreationWithInitializerSuffixCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").DimExprsCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").DimExprCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ClassLiteralSuffixCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ArrayAccessSuffixCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").MethodReferenceSuffixCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").StringTemplateCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TextBlockTemplateCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").RecordPatternCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ComponentPatternListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").GuardCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TemplateCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").PatternCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ExpressionCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TypePatternCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").CastExpressionCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TypeArgumentsOrDiamondCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").DiamondCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ComponentPatternCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").MatchAllPatternCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ConciseLambdaParameterCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").EmbeddedExpressionCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").LambdaBodyCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").LambdaParameterListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").NormalLambdaParameterCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").LambdaParameterTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").NewExpressionCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").PrimaryPrefixCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TemplateArgumentCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").InterfaceDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").NormalInterfaceDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").InterfaceExtendsCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").InterfaceBodyCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").InterfaceMemberDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ConstantDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").InterfaceMethodDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").AnnotationInterfaceDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").AnnotationInterfaceBodyCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").AnnotationInterfaceMemberDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").AnnotationInterfaceElementDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").DefaultValueCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").AnnotationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ElementValuePairListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ElementValuePairCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ElementValueArrayInitializerCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ElementValueListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ElementValueCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").AnnotationInterfaceElementModifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ConstantModifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").InterfaceModifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").InterfacePermitsCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").InterfaceMethodModifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").LiteralCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ShiftOperatorCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").BooleanLiteralCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").FloatingPointLiteralCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").IntegerLiteralCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").MethodNameCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").AmbiguousNameCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TypeNameCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TypeIdentifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ExpressionNameCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").PackageNameCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ModuleNameCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").PackageOrTypeNameCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").CompilationUnitCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").OrdinaryCompilationUnitCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ModularCompilationUnitCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").PackageDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ImportDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TypeDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ModuleDeclarationCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").RequiresModuleDirectiveCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ExportsModuleDirectiveCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").OpensModuleDirectiveCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").UsesModuleDirectiveCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ProvidesModuleDirectiveCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ModuleDirectiveCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").RequiresModifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").PackageModifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").PrimitiveTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ReferenceTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TypeVariableCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").DimsCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TypeParameterCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TypeBoundCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").AdditionalBoundCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TypeArgumentsCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TypeArgumentListCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").WildcardCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").WildcardBoundsCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").InterfaceTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ClassTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").NumericTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").ClassOrInterfaceTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").FloatingPointTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").IntegralTypeCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TypeArgumentCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").TypeParameterModifierCstNode & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}> | import("prettier").AstPath<import("java-parser").IToken & {
|
||||
comments?: import("./comments.js").JavaComment[];
|
||||
}>, options: import("prettier").ParserOptions<JavaNode>, print: (path: import("prettier").AstPath<JavaNode>) => import("prettier").Doc, args: unknown): import("prettier/doc.js").builders.Doc;
|
||||
hasPrettierIgnore(path: import("prettier").AstPath<JavaNode>): boolean;
|
||||
canAttachComment: typeof import("./comments.js").canAttachComment;
|
||||
isBlockComment(node: JavaNode): boolean;
|
||||
printComment(commentPath: import("prettier").AstPath<JavaNode>): string | import("prettier/doc.js").builders.Doc[];
|
||||
getCommentChildNodes(node: JavaNode): any[];
|
||||
handleComments: {
|
||||
ownLine: typeof import("./comments.js").handleLineComment;
|
||||
endOfLine: typeof import("./comments.js").handleLineComment;
|
||||
remaining: typeof import("./comments.js").handleRemainingComment;
|
||||
};
|
||||
};
|
||||
};
|
||||
options: {
|
||||
entrypoint: {
|
||||
type: "choice";
|
||||
category: string;
|
||||
default: string;
|
||||
choices: {
|
||||
value: string;
|
||||
description: string;
|
||||
}[];
|
||||
description: string;
|
||||
};
|
||||
arrowParens: {
|
||||
type: "choice";
|
||||
category: string;
|
||||
default: string;
|
||||
choices: {
|
||||
value: string;
|
||||
description: string;
|
||||
}[];
|
||||
description: string;
|
||||
};
|
||||
trailingComma: {
|
||||
type: "choice";
|
||||
category: string;
|
||||
default: string;
|
||||
choices: {
|
||||
value: string;
|
||||
description: string;
|
||||
}[];
|
||||
description: string;
|
||||
};
|
||||
experimentalOperatorPosition: {
|
||||
type: "choice";
|
||||
category: string;
|
||||
default: string;
|
||||
choices: {
|
||||
value: string;
|
||||
description: string;
|
||||
}[];
|
||||
description: string;
|
||||
};
|
||||
};
|
||||
defaultOptions: {
|
||||
arrowParens: "avoid";
|
||||
};
|
||||
};
|
||||
export default _default;
|
||||
@@ -1,29 +0,0 @@
|
||||
import options from "./options.js";
|
||||
import parser from "./parser.js";
|
||||
import printer from "./printer.js";
|
||||
export default {
|
||||
languages: [
|
||||
{
|
||||
name: "Java",
|
||||
parsers: ["java"],
|
||||
group: "Java",
|
||||
tmScope: "source.java",
|
||||
aceMode: "java",
|
||||
codemirrorMode: "clike",
|
||||
codemirrorMimeType: "text/x-java",
|
||||
extensions: [".java"],
|
||||
linguistLanguageId: 181,
|
||||
vscodeLanguageIds: ["java"]
|
||||
}
|
||||
],
|
||||
parsers: {
|
||||
java: parser
|
||||
},
|
||||
printers: {
|
||||
java: printer
|
||||
},
|
||||
options,
|
||||
defaultOptions: {
|
||||
arrowParens: "avoid"
|
||||
}
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
declare const _default: {
|
||||
entrypoint: {
|
||||
type: "choice";
|
||||
category: string;
|
||||
default: string;
|
||||
choices: {
|
||||
value: string;
|
||||
description: string;
|
||||
}[];
|
||||
description: string;
|
||||
};
|
||||
arrowParens: {
|
||||
type: "choice";
|
||||
category: string;
|
||||
default: string;
|
||||
choices: {
|
||||
value: string;
|
||||
description: string;
|
||||
}[];
|
||||
description: string;
|
||||
};
|
||||
trailingComma: {
|
||||
type: "choice";
|
||||
category: string;
|
||||
default: string;
|
||||
choices: {
|
||||
value: string;
|
||||
description: string;
|
||||
}[];
|
||||
description: string;
|
||||
};
|
||||
experimentalOperatorPosition: {
|
||||
type: "choice";
|
||||
category: string;
|
||||
default: string;
|
||||
choices: {
|
||||
value: string;
|
||||
description: string;
|
||||
}[];
|
||||
description: string;
|
||||
};
|
||||
};
|
||||
export default _default;
|
||||
@@ -1,284 +0,0 @@
|
||||
export default {
|
||||
entrypoint: {
|
||||
type: "choice",
|
||||
category: "Global",
|
||||
default: "compilationUnit",
|
||||
// sed -nr 's/.*\.RULE\(([^,]+),.*/\1/p' $(ls path/to/java-parser/rules/folder/*)
|
||||
choices: [
|
||||
{ value: "arrayInitializer", description: "" },
|
||||
{ value: "variableInitializerList", description: "" },
|
||||
{ value: "block", description: "" },
|
||||
{ value: "blockStatements", description: "" },
|
||||
{ value: "blockStatement", description: "" },
|
||||
{ value: "localVariableDeclarationStatement", description: "" },
|
||||
{ value: "localVariableDeclaration", description: "" },
|
||||
{ value: "localVariableType", description: "" },
|
||||
{ value: "statement", description: "" },
|
||||
{ value: "statementWithoutTrailingSubstatement", description: "" },
|
||||
{ value: "emptyStatement", description: "" },
|
||||
{ value: "labeledStatement", description: "" },
|
||||
{ value: "expressionStatement", description: "" },
|
||||
{ value: "statementExpression", description: "" },
|
||||
{ value: "ifStatement", description: "" },
|
||||
{ value: "assertStatement", description: "" },
|
||||
{ value: "switchStatement", description: "" },
|
||||
{ value: "switchBlock", description: "" },
|
||||
{ value: "switchBlockStatementGroup", description: "" },
|
||||
{ value: "switchLabel", description: "" },
|
||||
{ value: "switchRule", description: "" },
|
||||
{ value: "caseConstant", description: "" },
|
||||
{ value: "casePattern", description: "" },
|
||||
{ value: "whileStatement", description: "" },
|
||||
{ value: "doStatement", description: "" },
|
||||
{ value: "forStatement", description: "" },
|
||||
{ value: "basicForStatement", description: "" },
|
||||
{ value: "forInit", description: "" },
|
||||
{ value: "forUpdate", description: "" },
|
||||
{ value: "statementExpressionList", description: "" },
|
||||
{ value: "enhancedForStatement", description: "" },
|
||||
{ value: "breakStatement", description: "" },
|
||||
{ value: "continueStatement", description: "" },
|
||||
{ value: "returnStatement", description: "" },
|
||||
{ value: "throwStatement", description: "" },
|
||||
{ value: "synchronizedStatement", description: "" },
|
||||
{ value: "tryStatement", description: "" },
|
||||
{ value: "catches", description: "" },
|
||||
{ value: "catchClause", description: "" },
|
||||
{ value: "catchFormalParameter", description: "" },
|
||||
{ value: "catchType", description: "" },
|
||||
{ value: "finally", description: "" },
|
||||
{ value: "tryWithResourcesStatement", description: "" },
|
||||
{ value: "resourceSpecification", description: "" },
|
||||
{ value: "resourceList", description: "" },
|
||||
{ value: "resource", description: "" },
|
||||
{ value: "yieldStatement", description: "" },
|
||||
{ value: "variableAccess", description: "" },
|
||||
{ value: "classDeclaration", description: "" },
|
||||
{ value: "normalClassDeclaration", description: "" },
|
||||
{ value: "classModifier", description: "" },
|
||||
{ value: "typeParameters", description: "" },
|
||||
{ value: "typeParameterList", description: "" },
|
||||
{ value: "classExtends", description: "" },
|
||||
{ value: "classImplements", description: "" },
|
||||
{ value: "interfaceTypeList", description: "" },
|
||||
{ value: "classPermits", description: "" },
|
||||
{ value: "classBody", description: "" },
|
||||
{ value: "classBodyDeclaration", description: "" },
|
||||
{ value: "classMemberDeclaration", description: "" },
|
||||
{ value: "fieldDeclaration", description: "" },
|
||||
{ value: "fieldModifier", description: "" },
|
||||
{ value: "variableDeclaratorList", description: "" },
|
||||
{ value: "variableDeclarator", description: "" },
|
||||
{ value: "variableDeclaratorId", description: "" },
|
||||
{ value: "variableInitializer", description: "" },
|
||||
{ value: "unannType", description: "" },
|
||||
{ value: "unannPrimitiveTypeWithOptionalDimsSuffix", description: "" },
|
||||
{ value: "unannPrimitiveType", description: "" },
|
||||
{ value: "unannReferenceType", description: "" },
|
||||
{ value: "unannClassOrInterfaceType", description: "" },
|
||||
{ value: "unannClassType", description: "" },
|
||||
{ value: "unannInterfaceType", description: "" },
|
||||
{ value: "unannTypeVariable", description: "" },
|
||||
{ value: "methodDeclaration", description: "" },
|
||||
{ value: "methodModifier", description: "" },
|
||||
{ value: "methodHeader", description: "" },
|
||||
{ value: "result", description: "" },
|
||||
{ value: "methodDeclarator", description: "" },
|
||||
{ value: "receiverParameter", description: "" },
|
||||
{ value: "formalParameterList", description: "" },
|
||||
{ value: "formalParameter", description: "" },
|
||||
{ value: "variableParaRegularParameter", description: "" },
|
||||
{ value: "variableArityParameter", description: "" },
|
||||
{ value: "variableModifier", description: "" },
|
||||
{ value: "throws", description: "" },
|
||||
{ value: "exceptionTypeList", description: "" },
|
||||
{ value: "exceptionType", description: "" },
|
||||
{ value: "methodBody", description: "" },
|
||||
{ value: "instanceInitializer", description: "" },
|
||||
{ value: "staticInitializer", description: "" },
|
||||
{ value: "constructorDeclaration", description: "" },
|
||||
{ value: "constructorModifier", description: "" },
|
||||
{ value: "constructorDeclarator", description: "" },
|
||||
{ value: "simpleTypeName", description: "" },
|
||||
{ value: "constructorBody", description: "" },
|
||||
{ value: "explicitConstructorInvocation", description: "" },
|
||||
{ value: "unqualifiedExplicitConstructorInvocation", description: "" },
|
||||
{ value: "qualifiedExplicitConstructorInvocation", description: "" },
|
||||
{ value: "enumDeclaration", description: "" },
|
||||
{ value: "enumBody", description: "" },
|
||||
{ value: "enumConstantList", description: "" },
|
||||
{ value: "enumConstant", description: "" },
|
||||
{ value: "enumConstantModifier", description: "" },
|
||||
{ value: "enumBodyDeclarations", description: "" },
|
||||
{ value: "recordDeclaration", description: "" },
|
||||
{ value: "recordHeader", description: "" },
|
||||
{ value: "recordComponentList", description: "" },
|
||||
{ value: "recordComponent", description: "" },
|
||||
{ value: "variableArityRecordComponent", description: "" },
|
||||
{ value: "recordComponentModifier", description: "" },
|
||||
{ value: "recordBody", description: "" },
|
||||
{ value: "recordBodyDeclaration", description: "" },
|
||||
{ value: "compactConstructorDeclaration", description: "" },
|
||||
{ value: "isDims", description: "" },
|
||||
{ value: "expression", description: "" },
|
||||
{ value: "lambdaExpression", description: "" },
|
||||
{ value: "lambdaParameters", description: "" },
|
||||
{ value: "lambdaParametersWithBraces", description: "" },
|
||||
{ value: "lambdaParameterList", description: "" },
|
||||
{ value: "conciseLambdaParameterList", description: "" },
|
||||
{ value: "normalLambdaParameterList", description: "" },
|
||||
{ value: "normalLambdaParameter", description: "" },
|
||||
{ value: "regularLambdaParameter", description: "" },
|
||||
{ value: "lambdaParameterType", description: "" },
|
||||
{ value: "conciseLambdaParameter", description: "" },
|
||||
{ value: "lambdaBody", description: "" },
|
||||
{ value: "conditionalExpression", description: "" },
|
||||
{ value: "binaryExpression", description: "" },
|
||||
{ value: "unaryExpression", description: "" },
|
||||
{ value: "unaryExpressionNotPlusMinus", description: "" },
|
||||
{ value: "primary", description: "" },
|
||||
{ value: "primaryPrefix", description: "" },
|
||||
{ value: "primarySuffix", description: "" },
|
||||
{ value: "fqnOrRefType", description: "" },
|
||||
{ value: "fqnOrRefTypePartRest", description: "" },
|
||||
{ value: "fqnOrRefTypePartCommon", description: "" },
|
||||
{ value: "fqnOrRefTypePartFirst", description: "" },
|
||||
{ value: "parenthesisExpression", description: "" },
|
||||
{ value: "castExpression", description: "" },
|
||||
{ value: "primitiveCastExpression", description: "" },
|
||||
{ value: "referenceTypeCastExpression", description: "" },
|
||||
{ value: "newExpression", description: "" },
|
||||
{ value: "unqualifiedClassInstanceCreationExpression", description: "" },
|
||||
{ value: "classOrInterfaceTypeToInstantiate", description: "" },
|
||||
{ value: "typeArgumentsOrDiamond", description: "" },
|
||||
{ value: "diamond", description: "" },
|
||||
{ value: "methodInvocationSuffix", description: "" },
|
||||
{ value: "argumentList", description: "" },
|
||||
{ value: "arrayCreationExpression", description: "" },
|
||||
{
|
||||
value: "arrayCreationExpressionWithoutInitializerSuffix",
|
||||
description: ""
|
||||
},
|
||||
{ value: "arrayCreationWithInitializerSuffix", description: "" },
|
||||
{ value: "dimExprs", description: "" },
|
||||
{ value: "dimExpr", description: "" },
|
||||
{ value: "classLiteralSuffix", description: "" },
|
||||
{ value: "arrayAccessSuffix", description: "" },
|
||||
{ value: "methodReferenceSuffix", description: "" },
|
||||
{ value: "templateArgument", description: "" },
|
||||
{ value: "template", description: "" },
|
||||
{ value: "stringTemplate", description: "" },
|
||||
{ value: "textBlockTemplate", description: "" },
|
||||
{ value: "embeddedExpression", description: "" },
|
||||
{ value: "pattern", description: "" },
|
||||
{ value: "typePattern", description: "" },
|
||||
{ value: "recordPattern", description: "" },
|
||||
{ value: "componentPatternList", description: "" },
|
||||
{ value: "componentPattern", description: "" },
|
||||
{ value: "matchAllPattern", description: "" },
|
||||
{ value: "guard", description: "" },
|
||||
{ value: "isRefTypeInMethodRef", description: "" },
|
||||
{ value: "interfaceDeclaration", description: "" },
|
||||
{ value: "normalInterfaceDeclaration", description: "" },
|
||||
{ value: "interfaceModifier", description: "" },
|
||||
{ value: "interfaceExtends", description: "" },
|
||||
{ value: "interfacePermits", description: "" },
|
||||
{ value: "interfaceBody", description: "" },
|
||||
{ value: "interfaceMemberDeclaration", description: "" },
|
||||
{ value: "constantDeclaration", description: "" },
|
||||
{ value: "constantModifier", description: "" },
|
||||
{ value: "interfaceMethodDeclaration", description: "" },
|
||||
{ value: "interfaceMethodModifier", description: "" },
|
||||
{ value: "annotationInterfaceDeclaration", description: "" },
|
||||
{ value: "annotationInterfaceBody", description: "" },
|
||||
{ value: "annotationInterfaceMemberDeclaration", description: "" },
|
||||
{ value: "annotationInterfaceElementDeclaration", description: "" },
|
||||
{ value: "annotationInterfaceElementModifier", description: "" },
|
||||
{ value: "defaultValue", description: "" },
|
||||
{ value: "annotation", description: "" },
|
||||
{ value: "elementValuePairList", description: "" },
|
||||
{ value: "elementValuePair", description: "" },
|
||||
{ value: "elementValue", description: "" },
|
||||
{ value: "elementValueArrayInitializer", description: "" },
|
||||
{ value: "elementValueList", description: "" },
|
||||
{ value: "literal", description: "" },
|
||||
{ value: "integerLiteral", description: "" },
|
||||
{ value: "floatingPointLiteral", description: "" },
|
||||
{ value: "booleanLiteral", description: "" },
|
||||
{ value: "shiftOperator", description: "" },
|
||||
{ value: "moduleName", description: "" },
|
||||
{ value: "packageName", description: "" },
|
||||
{ value: "typeName", description: "" },
|
||||
{ value: "expressionName", description: "" },
|
||||
{ value: "methodName", description: "" },
|
||||
{ value: "packageOrTypeName", description: "" },
|
||||
{ value: "ambiguousName", description: "" },
|
||||
{ value: "compilationUnit", description: "" },
|
||||
{ value: "ordinaryCompilationUnit", description: "" },
|
||||
{ value: "modularCompilationUnit", description: "" },
|
||||
{ value: "packageDeclaration", description: "" },
|
||||
{ value: "packageModifier", description: "" },
|
||||
{ value: "importDeclaration", description: "" },
|
||||
{ value: "typeDeclaration", description: "" },
|
||||
{ value: "moduleDeclaration", description: "" },
|
||||
{ value: "moduleDirective", description: "" },
|
||||
{ value: "requiresModuleDirective", description: "" },
|
||||
{ value: "exportsModuleDirective", description: "" },
|
||||
{ value: "opensModuleDirective", description: "" },
|
||||
{ value: "usesModuleDirective", description: "" },
|
||||
{ value: "providesModuleDirective", description: "" },
|
||||
{ value: "requiresModifier", description: "" },
|
||||
{ value: "primitiveType", description: "" },
|
||||
{ value: "numericType", description: "" },
|
||||
{ value: "integralType", description: "" },
|
||||
{ value: "floatingPointType", description: "" },
|
||||
{ value: "referenceType", description: "" },
|
||||
{ value: "classOrInterfaceType", description: "" },
|
||||
{ value: "classType", description: "" },
|
||||
{ value: "interfaceType", description: "" },
|
||||
{ value: "typeVariable", description: "" },
|
||||
{ value: "dims", description: "" },
|
||||
{ value: "typeParameter", description: "" },
|
||||
{ value: "typeParameterModifier", description: "" },
|
||||
{ value: "typeBound", description: "" },
|
||||
{ value: "additionalBound", description: "" },
|
||||
{ value: "typeArguments", description: "" },
|
||||
{ value: "typeArgumentList", description: "" },
|
||||
{ value: "typeArgument", description: "" },
|
||||
{ value: "wildcard", description: "" },
|
||||
{ value: "wildcardBounds", description: "" }
|
||||
],
|
||||
description: "Prettify from the entrypoint, allowing to use prettier on snippet."
|
||||
},
|
||||
arrowParens: {
|
||||
type: "choice",
|
||||
category: "Java",
|
||||
default: "always",
|
||||
choices: [
|
||||
{ value: "always", description: "" },
|
||||
{ value: "avoid", description: "" }
|
||||
],
|
||||
description: "Include parentheses around a sole arrow function parameter."
|
||||
},
|
||||
trailingComma: {
|
||||
type: "choice",
|
||||
category: "Java",
|
||||
default: "all",
|
||||
choices: [
|
||||
{ value: "all", description: "" },
|
||||
{ value: "es5", description: "" },
|
||||
{ value: "none", description: "" }
|
||||
],
|
||||
description: "Print trailing commas wherever possible when multi-line."
|
||||
},
|
||||
experimentalOperatorPosition: {
|
||||
type: "choice",
|
||||
category: "Java",
|
||||
default: "end",
|
||||
choices: [
|
||||
{ value: "start", description: "" },
|
||||
{ value: "end", description: "" }
|
||||
],
|
||||
description: "Where to print operators when binary expressions wrap lines."
|
||||
}
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
import { type JavaNode, type JavaNonTerminal, type JavaParserOptions } from "./printers/helpers.js";
|
||||
declare const _default: {
|
||||
parse(text: string, options: JavaParserOptions): JavaNonTerminal;
|
||||
astFormat: string;
|
||||
hasPragma(text: string): boolean;
|
||||
locStart(node: JavaNode): number;
|
||||
locEnd(node: JavaNode): number;
|
||||
};
|
||||
export default _default;
|
||||
@@ -1,24 +0,0 @@
|
||||
import { parse } from "java-parser";
|
||||
import { determineFormatterOffOnRanges } from "./comments.js";
|
||||
import { isTerminal } from "./printers/helpers.js";
|
||||
export default {
|
||||
parse(text, options) {
|
||||
var _a;
|
||||
const cst = parse(text, options.entrypoint);
|
||||
(_a = cst.comments) === null || _a === void 0 ? void 0 : _a.forEach(comment => {
|
||||
comment.value = comment.image;
|
||||
});
|
||||
determineFormatterOffOnRanges(cst);
|
||||
return cst;
|
||||
},
|
||||
astFormat: "java",
|
||||
hasPragma(text) {
|
||||
return /^\/\*\*\n\s+\*\s@(format|prettier)\n\s+\*\//.test(text);
|
||||
},
|
||||
locStart(node) {
|
||||
return isTerminal(node) ? node.startOffset : node.location.startOffset;
|
||||
},
|
||||
locEnd(node) {
|
||||
return (isTerminal(node) ? node.endOffset : node.location.endOffset) + 1;
|
||||
}
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { AstPath } from "prettier";
|
||||
import { canAttachComment, handleLineComment, handleRemainingComment } from "./comments.js";
|
||||
import { type JavaNode } from "./printers/helpers.js";
|
||||
declare const _default: {
|
||||
print(path: DistributedAstPath<JavaNode>, options: import("prettier").ParserOptions<JavaNode>, print: (path: AstPath<JavaNode>) => import("prettier").Doc, args: unknown): import("prettier/doc.js").builders.Doc;
|
||||
hasPrettierIgnore(path: AstPath<JavaNode>): boolean;
|
||||
canAttachComment: typeof canAttachComment;
|
||||
isBlockComment(node: JavaNode): boolean;
|
||||
printComment(commentPath: AstPath<JavaNode>): string | import("prettier/doc.js").builders.Doc[];
|
||||
getCommentChildNodes(node: JavaNode): any[];
|
||||
handleComments: {
|
||||
ownLine: typeof handleLineComment;
|
||||
endOfLine: typeof handleLineComment;
|
||||
remaining: typeof handleRemainingComment;
|
||||
};
|
||||
};
|
||||
export default _default;
|
||||
type DistributedAstPath<T> = T extends any ? AstPath<T> : never;
|
||||
@@ -1,40 +0,0 @@
|
||||
import { canAttachComment, handleLineComment, handleRemainingComment, isFullyBetweenFormatterOffOn } from "./comments.js";
|
||||
import { isNonTerminal, isTerminal, printComment } from "./printers/helpers.js";
|
||||
import { printerForNodeType } from "./printers/index.js";
|
||||
export default {
|
||||
print(path, options, print, args) {
|
||||
return hasTerminal(path)
|
||||
? path.node.image
|
||||
: printerForNodeType(path.node.name)(path, print, options, args);
|
||||
},
|
||||
hasPrettierIgnore(path) {
|
||||
var _a;
|
||||
const { node } = path;
|
||||
return (((_a = node.comments) === null || _a === void 0 ? void 0 : _a.some(({ image }) => /^(\/\/\s*prettier-ignore|\/\*\s*prettier-ignore\s*\*\/)$/.test(image))) === true ||
|
||||
(canAttachComment(node) && isFullyBetweenFormatterOffOn(path)));
|
||||
},
|
||||
canAttachComment,
|
||||
isBlockComment(node) {
|
||||
return isTerminal(node) && node.tokenType.name === "TraditionalComment";
|
||||
},
|
||||
printComment(commentPath) {
|
||||
const { node } = commentPath;
|
||||
if (isNonTerminal(node) || node.tokenType.GROUP !== "comments") {
|
||||
throw new Error(`Not a comment: ${JSON.stringify(node)}`);
|
||||
}
|
||||
return printComment(node);
|
||||
},
|
||||
getCommentChildNodes(node) {
|
||||
return isNonTerminal(node)
|
||||
? Object.values(node.children).flatMap(child => child)
|
||||
: [];
|
||||
},
|
||||
handleComments: {
|
||||
ownLine: handleLineComment,
|
||||
endOfLine: handleLineComment,
|
||||
remaining: handleRemainingComment
|
||||
}
|
||||
};
|
||||
function hasTerminal(path) {
|
||||
return isTerminal(path.node);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
declare const _default: {
|
||||
arrayInitializer(path: import("prettier").AstPath<import("java-parser").ArrayInitializerCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn, options: import("./helpers.js").JavaParserOptions): import("prettier/doc.js").builders.Group | "{}";
|
||||
variableInitializerList(path: import("prettier").AstPath<import("java-parser").VariableInitializerListCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): import("prettier/doc.js").builders.Doc[];
|
||||
};
|
||||
export default _default;
|
||||
@@ -1,9 +0,0 @@
|
||||
import { printArrayInitializer, printList } from "./helpers.js";
|
||||
export default {
|
||||
arrayInitializer(path, print, options) {
|
||||
return printArrayInitializer(path, print, options, "variableInitializerList");
|
||||
},
|
||||
variableInitializerList(path, print) {
|
||||
return printList(path, print, "variableInitializer");
|
||||
}
|
||||
};
|
||||
@@ -1,117 +0,0 @@
|
||||
import { builders } from "prettier/doc";
|
||||
import { printSingle } from "./helpers.js";
|
||||
declare const _default: {
|
||||
block(path: import("prettier").AstPath<import("java-parser").BlockCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
|
||||
blockStatements(path: import("prettier").AstPath<import("java-parser").BlockStatementsCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
blockStatement: typeof printSingle;
|
||||
localVariableDeclarationStatement(path: import("prettier").AstPath<import("java-parser").LocalVariableDeclarationStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
localVariableDeclaration(path: import("prettier").AstPath<import("java-parser").LocalVariableDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
localVariableType: typeof printSingle;
|
||||
statement: typeof printSingle;
|
||||
statementWithoutTrailingSubstatement: typeof printSingle;
|
||||
emptyStatement(): string;
|
||||
labeledStatement(path: import("prettier").AstPath<import("java-parser").LabeledStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
expressionStatement(path: import("prettier").AstPath<import("java-parser").ExpressionStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
statementExpression: typeof printSingle;
|
||||
ifStatement(path: import("prettier").AstPath<import("java-parser").IfStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
assertStatement(path: import("prettier").AstPath<import("java-parser").AssertStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
switchStatement(path: import("prettier").AstPath<import("java-parser").SwitchStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
switchBlock(path: import("prettier").AstPath<import("java-parser").SwitchBlockCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
|
||||
switchBlockStatementGroup(path: import("prettier").AstPath<import("java-parser").SwitchBlockStatementGroupCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
switchLabel(path: import("prettier").AstPath<import("java-parser").SwitchLabelCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): "default" | builders.Group | builders.Doc[];
|
||||
switchRule(path: import("prettier").AstPath<import("java-parser").SwitchRuleCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
caseConstant: typeof printSingle;
|
||||
casePattern: typeof printSingle;
|
||||
whileStatement(path: import("prettier").AstPath<import("java-parser").WhileStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
doStatement(path: import("prettier").AstPath<import("java-parser").DoStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): (string | builders.Group | builders.Doc[])[];
|
||||
forStatement: typeof printSingle;
|
||||
basicForStatement(path: import("prettier").AstPath<import("java-parser").BasicForStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
forInit: typeof printSingle;
|
||||
forUpdate: typeof printSingle;
|
||||
statementExpressionList(path: import("prettier").AstPath<import("java-parser").StatementExpressionListCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Group;
|
||||
enhancedForStatement(path: import("prettier").AstPath<import("java-parser").EnhancedForStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Group;
|
||||
breakStatement(path: import("prettier").AstPath<import("java-parser").BreakStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[] | "break;";
|
||||
continueStatement(path: import("prettier").AstPath<import("java-parser").ContinueStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[] | "continue;";
|
||||
returnStatement(path: import("prettier").AstPath<import("java-parser").ReturnStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
throwStatement(path: import("prettier").AstPath<import("java-parser").ThrowStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
synchronizedStatement(path: import("prettier").AstPath<import("java-parser").SynchronizedStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
tryStatement(path: import("prettier").AstPath<import("java-parser").TryStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc;
|
||||
catches(path: import("prettier").AstPath<import("java-parser").CatchesCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
catchClause(path: import("prettier").AstPath<import("java-parser").CatchClauseCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
catchFormalParameter(path: import("prettier").AstPath<import("java-parser").CatchFormalParameterCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
catchType(path: import("prettier").AstPath<import("java-parser").CatchTypeCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
finally(path: import("prettier").AstPath<import("java-parser").FinallyCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
tryWithResourcesStatement(path: import("prettier").AstPath<import("java-parser").TryWithResourcesStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
resourceSpecification(path: import("prettier").AstPath<import("java-parser").ResourceSpecificationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Group | "()";
|
||||
resourceList(path: import("prettier").AstPath<import("java-parser").ResourceListCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
resource: typeof printSingle;
|
||||
yieldStatement(path: import("prettier").AstPath<import("java-parser").YieldStatementCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
variableAccess: typeof printSingle;
|
||||
};
|
||||
export default _default;
|
||||
@@ -1,337 +0,0 @@
|
||||
import { builders } from "prettier/doc";
|
||||
import { call, definedKeys, indentInParentheses, isBinaryExpression, isEmptyStatement, lineEndWithComments, lineStartWithComments, map, onlyDefinedKey, printBlock, printDanglingComments, printSingle, printWithModifiers } from "./helpers.js";
|
||||
const { group, hardline, ifBreak, indent, join, line, softline } = builders;
|
||||
export default {
|
||||
block(path, print) {
|
||||
const statements = path.node.children.blockStatements
|
||||
? call(path, print, "blockStatements")
|
||||
: [];
|
||||
return printBlock(path, statements.length ? [statements] : []);
|
||||
},
|
||||
blockStatements(path, print) {
|
||||
return join(hardline, map(path, statementPath => {
|
||||
const { node, previous } = statementPath;
|
||||
const statement = print(statementPath);
|
||||
return previous &&
|
||||
lineStartWithComments(node) > lineEndWithComments(previous) + 1
|
||||
? [hardline, statement]
|
||||
: statement;
|
||||
}, "blockStatement").filter(doc => doc !== ""));
|
||||
},
|
||||
blockStatement: printSingle,
|
||||
localVariableDeclarationStatement(path, print) {
|
||||
return [call(path, print, "localVariableDeclaration"), ";"];
|
||||
},
|
||||
localVariableDeclaration(path, print) {
|
||||
const declaration = join(" ", [
|
||||
call(path, print, "localVariableType"),
|
||||
call(path, print, "variableDeclaratorList")
|
||||
]);
|
||||
return printWithModifiers(path, print, "variableModifier", declaration);
|
||||
},
|
||||
localVariableType: printSingle,
|
||||
statement: printSingle,
|
||||
statementWithoutTrailingSubstatement: printSingle,
|
||||
emptyStatement() {
|
||||
return "";
|
||||
},
|
||||
labeledStatement(path, print) {
|
||||
return [
|
||||
call(path, print, "Identifier"),
|
||||
": ",
|
||||
call(path, print, "statement")
|
||||
];
|
||||
},
|
||||
expressionStatement(path, print) {
|
||||
return [call(path, print, "statementExpression"), ";"];
|
||||
},
|
||||
statementExpression: printSingle,
|
||||
ifStatement(path, print) {
|
||||
var _a;
|
||||
const { children } = path.node;
|
||||
const hasEmptyStatement = isEmptyStatement(children.statement[0]);
|
||||
const statements = map(path, print, "statement");
|
||||
const statement = [
|
||||
"if ",
|
||||
indentInParentheses(call(path, print, "expression")),
|
||||
hasEmptyStatement ? ";" : [" ", statements[0]]
|
||||
];
|
||||
if (children.Else) {
|
||||
const danglingComments = printDanglingComments(path);
|
||||
if (danglingComments.length) {
|
||||
statement.push(hardline, ...danglingComments, hardline);
|
||||
}
|
||||
else {
|
||||
const elseHasBlock = ((_a = children.statement[0].children
|
||||
.statementWithoutTrailingSubstatement) === null || _a === void 0 ? void 0 : _a[0].children.block) !==
|
||||
undefined;
|
||||
statement.push(elseHasBlock ? " " : hardline);
|
||||
}
|
||||
const elseHasEmptyStatement = isEmptyStatement(children.statement[1]);
|
||||
statement.push("else", elseHasEmptyStatement ? ";" : [" ", statements[1]]);
|
||||
}
|
||||
return statement;
|
||||
},
|
||||
assertStatement(path, print) {
|
||||
return ["assert ", ...join([" : "], map(path, print, "expression")), ";"];
|
||||
},
|
||||
switchStatement(path, print) {
|
||||
return join(" ", [
|
||||
"switch",
|
||||
indentInParentheses(call(path, print, "expression")),
|
||||
call(path, print, "switchBlock")
|
||||
]);
|
||||
},
|
||||
switchBlock(path, print) {
|
||||
const { children } = path.node;
|
||||
const caseKeys = definedKeys(children, [
|
||||
"switchBlockStatementGroup",
|
||||
"switchRule"
|
||||
]);
|
||||
const cases = caseKeys.length === 1 ? map(path, print, caseKeys[0]) : [];
|
||||
return printBlock(path, cases);
|
||||
},
|
||||
switchBlockStatementGroup(path, print) {
|
||||
var _a, _b;
|
||||
const { children } = path.node;
|
||||
const switchLabel = call(path, print, "switchLabel");
|
||||
if (!children.blockStatements) {
|
||||
return [switchLabel, ":"];
|
||||
}
|
||||
const blockStatements = call(path, print, "blockStatements");
|
||||
const statements = children.blockStatements[0].children.blockStatement;
|
||||
const onlyStatementIsBlock = statements.length === 1 &&
|
||||
((_b = (_a = statements[0].children.statement) === null || _a === void 0 ? void 0 : _a[0].children.statementWithoutTrailingSubstatement) === null || _b === void 0 ? void 0 : _b[0].children.block) !== undefined;
|
||||
return [
|
||||
switchLabel,
|
||||
":",
|
||||
onlyStatementIsBlock
|
||||
? [" ", blockStatements]
|
||||
: indent([hardline, blockStatements])
|
||||
];
|
||||
},
|
||||
switchLabel(path, print) {
|
||||
var _a, _b;
|
||||
const { children } = path.node;
|
||||
if (!((_b = (_a = children.caseConstant) !== null && _a !== void 0 ? _a : children.casePattern) !== null && _b !== void 0 ? _b : children.Null)) {
|
||||
return "default";
|
||||
}
|
||||
const values = [];
|
||||
if (children.Null) {
|
||||
values.push("null");
|
||||
if (children.Default) {
|
||||
values.push("default");
|
||||
}
|
||||
}
|
||||
else {
|
||||
const valuesKey = onlyDefinedKey(children, [
|
||||
"caseConstant",
|
||||
"casePattern"
|
||||
]);
|
||||
values.push(...map(path, print, valuesKey));
|
||||
}
|
||||
const hasMultipleValues = values.length > 1;
|
||||
const label = hasMultipleValues
|
||||
? ["case", indent([line, ...join([",", line], values)])]
|
||||
: ["case ", values[0]];
|
||||
return children.guard
|
||||
? [
|
||||
group([...label, hasMultipleValues ? line : " "]),
|
||||
call(path, print, "guard")
|
||||
]
|
||||
: group(label);
|
||||
},
|
||||
switchRule(path, print) {
|
||||
const { children } = path.node;
|
||||
const bodyKey = onlyDefinedKey(children, [
|
||||
"block",
|
||||
"expression",
|
||||
"throwStatement"
|
||||
]);
|
||||
const parts = [
|
||||
call(path, print, "switchLabel"),
|
||||
" -> ",
|
||||
call(path, print, bodyKey)
|
||||
];
|
||||
if (children.Semicolon) {
|
||||
parts.push(";");
|
||||
}
|
||||
return parts;
|
||||
},
|
||||
caseConstant: printSingle,
|
||||
casePattern: printSingle,
|
||||
whileStatement(path, print) {
|
||||
const statement = call(path, print, "statement");
|
||||
const hasEmptyStatement = isEmptyStatement(path.node.children.statement[0]);
|
||||
return [
|
||||
"while ",
|
||||
indentInParentheses(call(path, print, "expression")),
|
||||
...[hasEmptyStatement ? ";" : " ", statement]
|
||||
];
|
||||
},
|
||||
doStatement(path, print) {
|
||||
const hasEmptyStatement = isEmptyStatement(path.node.children.statement[0]);
|
||||
return [
|
||||
"do",
|
||||
hasEmptyStatement ? ";" : [" ", call(path, print, "statement")],
|
||||
" while ",
|
||||
indentInParentheses(call(path, print, "expression")),
|
||||
";"
|
||||
];
|
||||
},
|
||||
forStatement: printSingle,
|
||||
basicForStatement(path, print) {
|
||||
const { children } = path.node;
|
||||
const danglingComments = printDanglingComments(path);
|
||||
if (danglingComments.length) {
|
||||
danglingComments.push(hardline);
|
||||
}
|
||||
const expressions = ["forInit", "expression", "forUpdate"].map(expressionKey => expressionKey in children ? call(path, print, expressionKey) : "");
|
||||
const hasEmptyStatement = isEmptyStatement(children.statement[0]);
|
||||
return [
|
||||
...danglingComments,
|
||||
"for ",
|
||||
expressions.some(expression => expression !== "")
|
||||
? indentInParentheses(join([";", line], expressions))
|
||||
: "(;;)",
|
||||
hasEmptyStatement ? ";" : [" ", call(path, print, "statement")]
|
||||
];
|
||||
},
|
||||
forInit: printSingle,
|
||||
forUpdate: printSingle,
|
||||
statementExpressionList(path, print) {
|
||||
return group(map(path, print, "statementExpression").map((expression, index) => index === 0 ? expression : [",", indent([line, expression])]));
|
||||
},
|
||||
enhancedForStatement(path, print) {
|
||||
var _a;
|
||||
const statementNode = path.node.children.statement[0];
|
||||
const forStatement = [
|
||||
printDanglingComments(path),
|
||||
"for ",
|
||||
"(",
|
||||
call(path, print, "localVariableDeclaration"),
|
||||
" : ",
|
||||
call(path, print, "expression"),
|
||||
")"
|
||||
];
|
||||
if (isEmptyStatement(statementNode)) {
|
||||
forStatement.push(";");
|
||||
}
|
||||
else {
|
||||
const hasStatementBlock = ((_a = statementNode.children.statementWithoutTrailingSubstatement) === null || _a === void 0 ? void 0 : _a[0].children.block) !== undefined;
|
||||
const statement = call(path, print, "statement");
|
||||
forStatement.push(hasStatementBlock ? [" ", statement] : indent([line, statement]));
|
||||
}
|
||||
return group(forStatement);
|
||||
},
|
||||
breakStatement(path, print) {
|
||||
return path.node.children.Identifier
|
||||
? ["break ", call(path, print, "Identifier"), ";"]
|
||||
: "break;";
|
||||
},
|
||||
continueStatement(path, print) {
|
||||
return path.node.children.Identifier
|
||||
? ["continue ", call(path, print, "Identifier"), ";"]
|
||||
: "continue;";
|
||||
},
|
||||
returnStatement(path, print) {
|
||||
const { children } = path.node;
|
||||
const statement = ["return"];
|
||||
if (children.expression) {
|
||||
statement.push(" ");
|
||||
const expression = call(path, print, "expression");
|
||||
if (isBinaryExpression(children.expression[0])) {
|
||||
statement.push(group([
|
||||
ifBreak("("),
|
||||
indent([softline, expression]),
|
||||
softline,
|
||||
ifBreak(")")
|
||||
]));
|
||||
}
|
||||
else {
|
||||
statement.push(expression);
|
||||
}
|
||||
}
|
||||
statement.push(";");
|
||||
return statement;
|
||||
},
|
||||
throwStatement(path, print) {
|
||||
return ["throw ", call(path, print, "expression"), ";"];
|
||||
},
|
||||
synchronizedStatement(path, print) {
|
||||
return [
|
||||
"synchronized ",
|
||||
indentInParentheses(call(path, print, "expression")),
|
||||
" ",
|
||||
call(path, print, "block")
|
||||
];
|
||||
},
|
||||
tryStatement(path, print) {
|
||||
const { children } = path.node;
|
||||
if (children.tryWithResourcesStatement) {
|
||||
return call(path, print, "tryWithResourcesStatement");
|
||||
}
|
||||
const blocks = ["try", call(path, print, "block")];
|
||||
if (children.catches) {
|
||||
blocks.push(call(path, print, "catches"));
|
||||
}
|
||||
if (children.finally) {
|
||||
blocks.push(call(path, print, "finally"));
|
||||
}
|
||||
return join(" ", blocks);
|
||||
},
|
||||
catches(path, print) {
|
||||
return join(" ", map(path, print, "catchClause"));
|
||||
},
|
||||
catchClause(path, print) {
|
||||
return [
|
||||
"catch ",
|
||||
indentInParentheses(call(path, print, "catchFormalParameter")),
|
||||
" ",
|
||||
call(path, print, "block")
|
||||
];
|
||||
},
|
||||
catchFormalParameter(path, print) {
|
||||
return join(" ", [
|
||||
...map(path, print, "variableModifier"),
|
||||
call(path, print, "catchType"),
|
||||
call(path, print, "variableDeclaratorId")
|
||||
]);
|
||||
},
|
||||
catchType(path, print) {
|
||||
return join([line, "| "], [call(path, print, "unannClassType"), ...map(path, print, "classType")]);
|
||||
},
|
||||
finally(path, print) {
|
||||
return ["finally ", call(path, print, "block")];
|
||||
},
|
||||
tryWithResourcesStatement(path, print) {
|
||||
const { children } = path.node;
|
||||
const blocks = [
|
||||
"try",
|
||||
call(path, print, "resourceSpecification"),
|
||||
call(path, print, "block")
|
||||
];
|
||||
if (children.catches) {
|
||||
blocks.push(call(path, print, "catches"));
|
||||
}
|
||||
if (children.finally) {
|
||||
blocks.push(call(path, print, "finally"));
|
||||
}
|
||||
return join(" ", blocks);
|
||||
},
|
||||
resourceSpecification(path, print) {
|
||||
const resources = [call(path, print, "resourceList")];
|
||||
if (path.node.children.Semicolon) {
|
||||
resources.push(ifBreak(";"));
|
||||
}
|
||||
return indentInParentheses(resources);
|
||||
},
|
||||
resourceList(path, print) {
|
||||
return join([";", line], map(path, print, "resource"));
|
||||
},
|
||||
resource: printSingle,
|
||||
yieldStatement(path, print) {
|
||||
return ["yield ", call(path, print, "expression"), ";"];
|
||||
},
|
||||
variableAccess: printSingle
|
||||
};
|
||||
@@ -1,157 +0,0 @@
|
||||
import type { ClassBodyCstNode, EnumBodyDeclarationsCstNode } from "java-parser";
|
||||
import type { AstPath } from "prettier";
|
||||
import { builders } from "prettier/doc";
|
||||
import { printClassPermits, printClassType, printSingle, type JavaPrintFn } from "./helpers.js";
|
||||
declare const _default: {
|
||||
classDeclaration(path: AstPath<import("java-parser").ClassDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
normalClassDeclaration(path: AstPath<import("java-parser").NormalClassDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
classModifier: typeof printSingle;
|
||||
typeParameters(path: AstPath<import("java-parser").TypeParametersCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Group;
|
||||
typeParameterList(path: AstPath<import("java-parser").TypeParameterListCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
classExtends(path: AstPath<import("java-parser").ClassExtendsCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
classImplements(path: AstPath<import("java-parser").ClassImplementsCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Group;
|
||||
classPermits: typeof printClassPermits;
|
||||
interfaceTypeList(path: AstPath<import("java-parser").InterfaceTypeListCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Group;
|
||||
classBody(path: AstPath<ClassBodyCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
|
||||
classBodyDeclaration: typeof printSingle;
|
||||
classMemberDeclaration(path: AstPath<import("java-parser").ClassMemberDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc;
|
||||
fieldDeclaration(path: AstPath<import("java-parser").FieldDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
fieldModifier: typeof printSingle;
|
||||
variableDeclaratorList(path: AstPath<import("java-parser").VariableDeclaratorListCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Group | builders.Doc[];
|
||||
variableDeclarator(path: AstPath<import("java-parser").VariableDeclaratorCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc;
|
||||
variableDeclaratorId(path: AstPath<import("java-parser").VariableDeclaratorIdCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc;
|
||||
variableInitializer: typeof printSingle;
|
||||
unannType: typeof printSingle;
|
||||
unannPrimitiveTypeWithOptionalDimsSuffix(path: AstPath<import("java-parser").UnannPrimitiveTypeWithOptionalDimsSuffixCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc;
|
||||
unannPrimitiveType: typeof printSingle;
|
||||
unannReferenceType(path: AstPath<import("java-parser").UnannReferenceTypeCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc;
|
||||
unannClassOrInterfaceType: typeof printSingle;
|
||||
unannClassType: typeof printClassType;
|
||||
unannInterfaceType: typeof printSingle;
|
||||
unannTypeVariable: typeof printSingle;
|
||||
methodDeclaration(path: AstPath<import("java-parser").MethodDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
methodModifier: typeof printSingle;
|
||||
methodHeader(path: AstPath<import("java-parser").MethodHeaderCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Group;
|
||||
result: typeof printSingle;
|
||||
methodDeclarator(path: AstPath<import("java-parser").MethodDeclaratorCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
receiverParameter(path: AstPath<import("java-parser").ReceiverParameterCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
formalParameterList(path: AstPath<import("java-parser").FormalParameterListCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
formalParameter: typeof printSingle;
|
||||
variableParaRegularParameter(path: AstPath<import("java-parser").VariableParaRegularParameterCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
variableArityParameter(path: AstPath<import("java-parser").VariableArityParameterCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
variableModifier: typeof printSingle;
|
||||
throws(path: AstPath<import("java-parser").ThrowsCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
exceptionTypeList(path: AstPath<import("java-parser").ExceptionTypeListCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
exceptionType: typeof printSingle;
|
||||
methodBody: typeof printSingle;
|
||||
instanceInitializer: typeof printSingle;
|
||||
staticInitializer(path: AstPath<import("java-parser").StaticInitializerCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
constructorDeclaration(path: AstPath<import("java-parser").ConstructorDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
constructorModifier: typeof printSingle;
|
||||
constructorDeclarator(path: AstPath<import("java-parser").ConstructorDeclaratorCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
simpleTypeName: typeof printSingle;
|
||||
constructorBody(path: AstPath<import("java-parser").ConstructorBodyCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
|
||||
explicitConstructorInvocation: typeof printSingle;
|
||||
unqualifiedExplicitConstructorInvocation(path: AstPath<import("java-parser").UnqualifiedExplicitConstructorInvocationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
qualifiedExplicitConstructorInvocation(path: AstPath<import("java-parser").QualifiedExplicitConstructorInvocationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
enumDeclaration(path: AstPath<import("java-parser").EnumDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
enumBody(path: AstPath<import("java-parser").EnumBodyCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn, options: import("./helpers.js").JavaParserOptions): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
|
||||
enumConstantList(path: AstPath<import("java-parser").EnumConstantListCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
enumConstant(path: AstPath<import("java-parser").EnumConstantCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
enumConstantModifier: typeof printSingle;
|
||||
enumBodyDeclarations(path: AstPath<EnumBodyDeclarationsCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
recordDeclaration(path: AstPath<import("java-parser").RecordDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
recordHeader(path: AstPath<import("java-parser").RecordHeaderCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Group | "()";
|
||||
recordComponentList(path: AstPath<import("java-parser").RecordComponentListCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
recordComponent(path: AstPath<import("java-parser").RecordComponentCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Group;
|
||||
variableArityRecordComponent(path: AstPath<import("java-parser").VariableArityRecordComponentCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
recordComponentModifier: typeof printSingle;
|
||||
recordBody(path: AstPath<import("java-parser").RecordBodyCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
|
||||
recordBodyDeclaration: typeof printSingle;
|
||||
compactConstructorDeclaration(path: AstPath<import("java-parser").CompactConstructorDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
};
|
||||
export default _default;
|
||||
@@ -1,446 +0,0 @@
|
||||
import { builders } from "prettier/doc";
|
||||
import { call, each, hasDeclarationAnnotations, hasLeadingComments, indentInParentheses, isBinaryExpression, lineEndWithComments, lineStartWithComments, map, onlyDefinedKey, printBlock, printClassPermits, printClassType, printDanglingComments, printList, printSingle, printWithModifiers } from "./helpers.js";
|
||||
const { group, hardline, indent, indentIfBreak, join, line, softline } = builders;
|
||||
export default {
|
||||
classDeclaration(path, print) {
|
||||
const declarationKey = onlyDefinedKey(path.node.children, [
|
||||
"enumDeclaration",
|
||||
"normalClassDeclaration",
|
||||
"recordDeclaration"
|
||||
]);
|
||||
const declaration = call(path, print, declarationKey);
|
||||
return printWithModifiers(path, print, "classModifier", declaration, true);
|
||||
},
|
||||
normalClassDeclaration(path, print) {
|
||||
const { classExtends, classImplements, classPermits, typeParameters } = path.node.children;
|
||||
const header = ["class ", call(path, print, "typeIdentifier")];
|
||||
if (typeParameters) {
|
||||
header.push(call(path, print, "typeParameters"));
|
||||
}
|
||||
if (classExtends) {
|
||||
header.push(indent([line, call(path, print, "classExtends")]));
|
||||
}
|
||||
if (classImplements) {
|
||||
header.push(indent([line, call(path, print, "classImplements")]));
|
||||
}
|
||||
if (classPermits) {
|
||||
header.push(indent([line, call(path, print, "classPermits")]));
|
||||
}
|
||||
return [group(header), " ", call(path, print, "classBody")];
|
||||
},
|
||||
classModifier: printSingle,
|
||||
typeParameters(path, print) {
|
||||
return group([
|
||||
"<",
|
||||
indent([softline, call(path, print, "typeParameterList")]),
|
||||
softline,
|
||||
">"
|
||||
]);
|
||||
},
|
||||
typeParameterList(path, print) {
|
||||
return printList(path, print, "typeParameter");
|
||||
},
|
||||
classExtends(path, print) {
|
||||
return ["extends ", call(path, print, "classType")];
|
||||
},
|
||||
classImplements(path, print) {
|
||||
return group([
|
||||
"implements",
|
||||
indent([line, call(path, print, "interfaceTypeList")])
|
||||
]);
|
||||
},
|
||||
classPermits: printClassPermits,
|
||||
interfaceTypeList(path, print) {
|
||||
return group(printList(path, print, "interfaceType"));
|
||||
},
|
||||
classBody(path, print) {
|
||||
return printBlock(path, printClassBodyDeclarations(path, print));
|
||||
},
|
||||
classBodyDeclaration: printSingle,
|
||||
classMemberDeclaration(path, print) {
|
||||
const { children } = path.node;
|
||||
return children.Semicolon
|
||||
? ""
|
||||
: call(path, print, onlyDefinedKey(children));
|
||||
},
|
||||
fieldDeclaration(path, print) {
|
||||
const declaration = [
|
||||
call(path, print, "unannType"),
|
||||
" ",
|
||||
call(path, print, "variableDeclaratorList"),
|
||||
";"
|
||||
];
|
||||
return printWithModifiers(path, print, "fieldModifier", declaration);
|
||||
},
|
||||
fieldModifier: printSingle,
|
||||
variableDeclaratorList(path, print) {
|
||||
var _a;
|
||||
const declarators = map(path, print, "variableDeclarator");
|
||||
return declarators.length > 1 &&
|
||||
path.node.children.variableDeclarator.some(({ children }) => children.Equals)
|
||||
? group(indent(join([",", line], declarators)), {
|
||||
shouldBreak: ((_a = path.getNode(4)) === null || _a === void 0 ? void 0 : _a.name) !== "forInit"
|
||||
})
|
||||
: join(", ", declarators);
|
||||
},
|
||||
variableDeclarator(path, print) {
|
||||
var _a, _b;
|
||||
const { children } = path.node;
|
||||
const variableInitializer = (_a = children.variableInitializer) === null || _a === void 0 ? void 0 : _a[0];
|
||||
const declaratorId = call(path, print, "variableDeclaratorId");
|
||||
if (!variableInitializer) {
|
||||
return declaratorId;
|
||||
}
|
||||
const expression = (_b = variableInitializer.children.expression) === null || _b === void 0 ? void 0 : _b[0];
|
||||
const declarator = [declaratorId, " ", call(path, print, "Equals")];
|
||||
const initializer = call(path, print, "variableInitializer");
|
||||
if (hasLeadingComments(variableInitializer) ||
|
||||
(expression && isBinaryExpression(expression))) {
|
||||
declarator.push(group(indent([line, initializer])));
|
||||
}
|
||||
else {
|
||||
const groupId = Symbol("assignment");
|
||||
declarator.push(group(indent(line), { id: groupId }), indentIfBreak(initializer, { groupId }));
|
||||
}
|
||||
return group(declarator);
|
||||
},
|
||||
variableDeclaratorId(path, print) {
|
||||
const { dims, Underscore } = path.node.children;
|
||||
if (Underscore) {
|
||||
return "_";
|
||||
}
|
||||
const identifier = call(path, print, "Identifier");
|
||||
return dims ? [identifier, call(path, print, "dims")] : identifier;
|
||||
},
|
||||
variableInitializer: printSingle,
|
||||
unannType: printSingle,
|
||||
unannPrimitiveTypeWithOptionalDimsSuffix(path, print) {
|
||||
const type = call(path, print, "unannPrimitiveType");
|
||||
return path.node.children.dims ? [type, call(path, print, "dims")] : type;
|
||||
},
|
||||
unannPrimitiveType: printSingle,
|
||||
unannReferenceType(path, print) {
|
||||
const type = call(path, print, "unannClassOrInterfaceType");
|
||||
return path.node.children.dims ? [type, call(path, print, "dims")] : type;
|
||||
},
|
||||
unannClassOrInterfaceType: printSingle,
|
||||
unannClassType: printClassType,
|
||||
unannInterfaceType: printSingle,
|
||||
unannTypeVariable: printSingle,
|
||||
methodDeclaration(path, print) {
|
||||
const declaration = [
|
||||
call(path, print, "methodHeader"),
|
||||
path.node.children.methodBody[0].children.Semicolon ? "" : " ",
|
||||
call(path, print, "methodBody")
|
||||
];
|
||||
return printWithModifiers(path, print, "methodModifier", declaration);
|
||||
},
|
||||
methodModifier: printSingle,
|
||||
methodHeader(path, print) {
|
||||
const { typeParameters, annotation, throws } = path.node.children;
|
||||
const header = [];
|
||||
if (typeParameters) {
|
||||
header.push(call(path, print, "typeParameters"));
|
||||
}
|
||||
if (annotation) {
|
||||
header.push(join(line, map(path, print, "annotation")));
|
||||
}
|
||||
header.push(call(path, print, "result"), call(path, print, "methodDeclarator"));
|
||||
return throws
|
||||
? group([
|
||||
...join(" ", header),
|
||||
group(indent([line, call(path, print, "throws")]))
|
||||
])
|
||||
: group(join(" ", header));
|
||||
},
|
||||
result: printSingle,
|
||||
methodDeclarator(path, print) {
|
||||
const { dims, formalParameterList, receiverParameter } = path.node.children;
|
||||
const declarator = [call(path, print, "Identifier")];
|
||||
const parameters = [];
|
||||
if (receiverParameter) {
|
||||
parameters.push(call(path, print, "receiverParameter"));
|
||||
}
|
||||
if (formalParameterList) {
|
||||
parameters.push(call(path, print, "formalParameterList"));
|
||||
}
|
||||
const items = parameters.length
|
||||
? join([",", line], parameters)
|
||||
: printDanglingComments(path);
|
||||
declarator.push(items.length ? indentInParentheses(items) : "()");
|
||||
if (dims) {
|
||||
declarator.push(call(path, print, "dims"));
|
||||
}
|
||||
return declarator;
|
||||
},
|
||||
receiverParameter(path, print) {
|
||||
return join(" ", [
|
||||
...map(path, print, "annotation"),
|
||||
call(path, print, "unannType"),
|
||||
path.node.children.Identifier
|
||||
? [call(path, print, "Identifier"), ".this"]
|
||||
: "this"
|
||||
]);
|
||||
},
|
||||
formalParameterList(path, print) {
|
||||
return printList(path, print, "formalParameter");
|
||||
},
|
||||
formalParameter: printSingle,
|
||||
variableParaRegularParameter(path, print) {
|
||||
return join(" ", [
|
||||
...map(path, print, "variableModifier"),
|
||||
call(path, print, "unannType"),
|
||||
call(path, print, "variableDeclaratorId")
|
||||
]);
|
||||
},
|
||||
variableArityParameter(path, print) {
|
||||
const type = join(" ", [
|
||||
...map(path, print, "variableModifier"),
|
||||
call(path, print, "unannType"),
|
||||
...map(path, print, "annotation")
|
||||
]);
|
||||
return [type, "... ", call(path, print, "Identifier")];
|
||||
},
|
||||
variableModifier: printSingle,
|
||||
throws(path, print) {
|
||||
return ["throws ", call(path, print, "exceptionTypeList")];
|
||||
},
|
||||
exceptionTypeList(path, print) {
|
||||
return join(", ", map(path, print, "exceptionType"));
|
||||
},
|
||||
exceptionType: printSingle,
|
||||
methodBody: printSingle,
|
||||
instanceInitializer: printSingle,
|
||||
staticInitializer(path, print) {
|
||||
return ["static ", call(path, print, "block")];
|
||||
},
|
||||
constructorDeclaration(path, print) {
|
||||
const declaration = [call(path, print, "constructorDeclarator")];
|
||||
if (path.node.children.throws) {
|
||||
declaration.push(group(indent([line, call(path, print, "throws")])));
|
||||
}
|
||||
declaration.push(" ", call(path, print, "constructorBody"));
|
||||
return printWithModifiers(path, print, "constructorModifier", declaration, true);
|
||||
},
|
||||
constructorModifier: printSingle,
|
||||
constructorDeclarator(path, print) {
|
||||
const { children } = path.node;
|
||||
const parameters = [];
|
||||
if (children.receiverParameter) {
|
||||
parameters.push(call(path, print, "receiverParameter"));
|
||||
}
|
||||
if (children.formalParameterList) {
|
||||
parameters.push(call(path, print, "formalParameterList"));
|
||||
}
|
||||
const header = [call(path, print, "simpleTypeName")];
|
||||
header.push(parameters.length
|
||||
? indentInParentheses(join([",", line], parameters))
|
||||
: "()");
|
||||
return children.typeParameters
|
||||
? [call(path, print, "typeParameters"), " ", ...header]
|
||||
: header;
|
||||
},
|
||||
simpleTypeName: printSingle,
|
||||
constructorBody(path, print) {
|
||||
const { children } = path.node;
|
||||
const statements = [];
|
||||
if (children.explicitConstructorInvocation) {
|
||||
statements.push(call(path, print, "explicitConstructorInvocation"));
|
||||
}
|
||||
if (children.blockStatements) {
|
||||
statements.push(call(path, print, "blockStatements"));
|
||||
}
|
||||
return printBlock(path, statements);
|
||||
},
|
||||
explicitConstructorInvocation: printSingle,
|
||||
unqualifiedExplicitConstructorInvocation(path, print) {
|
||||
const { children } = path.node;
|
||||
const invocation = [];
|
||||
if (children.typeArguments) {
|
||||
invocation.push(call(path, print, "typeArguments"));
|
||||
}
|
||||
invocation.push(children.Super ? "super" : "this");
|
||||
if (children.argumentList) {
|
||||
invocation.push(group(["(", call(path, print, "argumentList"), ")"]));
|
||||
}
|
||||
else {
|
||||
invocation.push(indentInParentheses(printDanglingComments(path), { shouldBreak: true }));
|
||||
}
|
||||
invocation.push(";");
|
||||
return invocation;
|
||||
},
|
||||
qualifiedExplicitConstructorInvocation(path, print) {
|
||||
const { children } = path.node;
|
||||
const invocation = [call(path, print, "expressionName"), "."];
|
||||
if (children.typeArguments) {
|
||||
invocation.push(call(path, print, "typeArguments"));
|
||||
}
|
||||
invocation.push("super");
|
||||
if (children.argumentList) {
|
||||
invocation.push(group(["(", call(path, print, "argumentList"), ")"]));
|
||||
}
|
||||
else {
|
||||
invocation.push(indentInParentheses(printDanglingComments(path), { shouldBreak: true }));
|
||||
}
|
||||
invocation.push(";");
|
||||
return invocation;
|
||||
},
|
||||
enumDeclaration(path, print) {
|
||||
const header = ["enum", call(path, print, "typeIdentifier")];
|
||||
if (path.node.children.classImplements) {
|
||||
header.push(call(path, print, "classImplements"));
|
||||
}
|
||||
return join(" ", [...header, call(path, print, "enumBody")]);
|
||||
},
|
||||
enumBody(path, print, options) {
|
||||
var _a;
|
||||
const { children } = path.node;
|
||||
const contents = [];
|
||||
const hasNonEmptyDeclaration = ((_a = children.enumBodyDeclarations) !== null && _a !== void 0 ? _a : [])
|
||||
.flatMap(({ children }) => { var _a; return (_a = children.classBodyDeclaration) !== null && _a !== void 0 ? _a : []; })
|
||||
.some(({ children }) => { var _a; return !((_a = children.classMemberDeclaration) === null || _a === void 0 ? void 0 : _a[0].children.Semicolon); });
|
||||
if (children.enumConstantList) {
|
||||
contents.push(call(path, print, "enumConstantList"));
|
||||
if (!hasNonEmptyDeclaration && options.trailingComma !== "none") {
|
||||
contents.push(",");
|
||||
}
|
||||
}
|
||||
if (hasNonEmptyDeclaration) {
|
||||
contents.push(";", hardline, call(path, print, "enumBodyDeclarations"));
|
||||
}
|
||||
return printBlock(path, contents.length ? [contents] : []);
|
||||
},
|
||||
enumConstantList(path, print) {
|
||||
return join([",", hardline], map(path, constantPath => {
|
||||
const constant = print(constantPath);
|
||||
const { node, previous } = constantPath;
|
||||
return !previous ||
|
||||
lineStartWithComments(node) <= lineEndWithComments(previous) + 1
|
||||
? constant
|
||||
: [hardline, constant];
|
||||
}, "enumConstant"));
|
||||
},
|
||||
enumConstant(path, print) {
|
||||
const { argumentList, classBody } = path.node.children;
|
||||
const initializer = [call(path, print, "Identifier")];
|
||||
if (argumentList) {
|
||||
initializer.push(group(["(", call(path, print, "argumentList"), ")"]));
|
||||
}
|
||||
if (classBody) {
|
||||
initializer.push(" ", call(path, print, "classBody"));
|
||||
}
|
||||
return printWithModifiers(path, print, "enumConstantModifier", initializer);
|
||||
},
|
||||
enumConstantModifier: printSingle,
|
||||
enumBodyDeclarations(path, print) {
|
||||
return join(hardline, printClassBodyDeclarations(path, print));
|
||||
},
|
||||
recordDeclaration(path, print) {
|
||||
const { children } = path.node;
|
||||
const header = ["record ", call(path, print, "typeIdentifier")];
|
||||
if (children.typeParameters) {
|
||||
header.push(call(path, print, "typeParameters"));
|
||||
}
|
||||
header.push(call(path, print, "recordHeader"));
|
||||
if (children.classImplements) {
|
||||
header.push(" ", call(path, print, "classImplements"));
|
||||
}
|
||||
return [group(header), " ", call(path, print, "recordBody")];
|
||||
},
|
||||
recordHeader(path, print) {
|
||||
return path.node.children.recordComponentList
|
||||
? indentInParentheses(call(path, print, "recordComponentList"))
|
||||
: indentInParentheses(printDanglingComments(path), { shouldBreak: true });
|
||||
},
|
||||
recordComponentList(path, print) {
|
||||
return join([",", line], map(path, componentPath => {
|
||||
const { node, previous } = componentPath;
|
||||
const blankLine = previous &&
|
||||
lineStartWithComments(node) > lineEndWithComments(previous) + 1;
|
||||
const component = print(componentPath);
|
||||
return blankLine ? [softline, component] : component;
|
||||
}, "recordComponent"));
|
||||
},
|
||||
recordComponent(path, print) {
|
||||
const { children } = path.node;
|
||||
const component = [call(path, print, "unannType")];
|
||||
if (children.Identifier ||
|
||||
children.variableArityRecordComponent[0].children.annotation) {
|
||||
component.push(" ");
|
||||
}
|
||||
const suffixKey = onlyDefinedKey(children, [
|
||||
"Identifier",
|
||||
"variableArityRecordComponent"
|
||||
]);
|
||||
component.push(call(path, print, suffixKey));
|
||||
return group(join(line, [...map(path, print, "recordComponentModifier"), component]));
|
||||
},
|
||||
variableArityRecordComponent(path, print) {
|
||||
return [
|
||||
...join(" ", map(path, print, "annotation")),
|
||||
"... ",
|
||||
call(path, print, "Identifier")
|
||||
];
|
||||
},
|
||||
recordComponentModifier: printSingle,
|
||||
recordBody(path, print) {
|
||||
const declarations = [];
|
||||
let previousRequiresPadding = false;
|
||||
each(path, declarationPath => {
|
||||
var _a, _b, _c, _d;
|
||||
const declaration = print(declarationPath);
|
||||
if (declaration === "") {
|
||||
return;
|
||||
}
|
||||
const { node, previous } = declarationPath;
|
||||
const fieldDeclaration = (_c = (_b = (_a = node.children.classBodyDeclaration) === null || _a === void 0 ? void 0 : _a[0].children.classMemberDeclaration) === null || _b === void 0 ? void 0 : _b[0].children.fieldDeclaration) === null || _c === void 0 ? void 0 : _c[0].children;
|
||||
const currentRequiresPadding = !fieldDeclaration ||
|
||||
hasDeclarationAnnotations((_d = fieldDeclaration.fieldModifier) !== null && _d !== void 0 ? _d : []);
|
||||
const blankLine = declarations.length > 0 &&
|
||||
(previousRequiresPadding ||
|
||||
currentRequiresPadding ||
|
||||
lineStartWithComments(node) > lineEndWithComments(previous) + 1);
|
||||
declarations.push(blankLine ? [hardline, declaration] : declaration);
|
||||
previousRequiresPadding = currentRequiresPadding;
|
||||
}, "recordBodyDeclaration");
|
||||
return printBlock(path, declarations);
|
||||
},
|
||||
recordBodyDeclaration: printSingle,
|
||||
compactConstructorDeclaration(path, print) {
|
||||
const declaration = [
|
||||
call(path, print, "simpleTypeName"),
|
||||
" ",
|
||||
call(path, print, "constructorBody")
|
||||
];
|
||||
return printWithModifiers(path, print, "constructorModifier", declaration, true);
|
||||
}
|
||||
};
|
||||
function printClassBodyDeclarations(path, print) {
|
||||
var _a;
|
||||
if (!path.node.children.classBodyDeclaration) {
|
||||
return [];
|
||||
}
|
||||
const declarations = [];
|
||||
let previousRequiresPadding = path.node.name === "enumBodyDeclarations" ||
|
||||
((_a = path.grandparent) === null || _a === void 0 ? void 0 : _a.name) ===
|
||||
"normalClassDeclaration";
|
||||
each(path, declarationPath => {
|
||||
var _a, _b, _c;
|
||||
const declaration = print(declarationPath);
|
||||
if (declaration === "") {
|
||||
return;
|
||||
}
|
||||
const { node, previous } = declarationPath;
|
||||
const fieldDeclaration = (_b = (_a = node.children.classMemberDeclaration) === null || _a === void 0 ? void 0 : _a[0].children.fieldDeclaration) === null || _b === void 0 ? void 0 : _b[0].children;
|
||||
const currentRequiresPadding = fieldDeclaration
|
||||
? hasDeclarationAnnotations((_c = fieldDeclaration.fieldModifier) !== null && _c !== void 0 ? _c : [])
|
||||
: true;
|
||||
const blankLine = previousRequiresPadding ||
|
||||
(declarations.length > 0 &&
|
||||
(currentRequiresPadding ||
|
||||
lineStartWithComments(node) > lineEndWithComments(previous) + 1));
|
||||
declarations.push(blankLine ? [hardline, declaration] : declaration);
|
||||
previousRequiresPadding = currentRequiresPadding;
|
||||
}, "classBodyDeclaration");
|
||||
return declarations;
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
import type { StringTemplateCstNode, TextBlockTemplateCstNode } from "java-parser";
|
||||
import type { AstPath } from "prettier";
|
||||
import { builders } from "prettier/doc";
|
||||
import type { JavaComment } from "../comments.js";
|
||||
import { printSingle, type JavaPrintFn } from "./helpers.js";
|
||||
declare const _default: {
|
||||
expression: typeof printSingle;
|
||||
lambdaExpression(path: AstPath<import("java-parser").LambdaExpressionCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn, _: import("./helpers.js").JavaParserOptions, args?: unknown): builders.Doc[];
|
||||
lambdaParameters(path: AstPath<import("java-parser").LambdaParametersCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn, options: import("./helpers.js").JavaParserOptions): builders.Doc;
|
||||
lambdaParametersWithBraces(path: AstPath<import("java-parser").LambdaParametersWithBracesCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn, options: import("./helpers.js").JavaParserOptions): builders.Doc;
|
||||
lambdaParameterList: typeof printSingle;
|
||||
conciseLambdaParameterList(path: AstPath<import("java-parser").ConciseLambdaParameterListCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
normalLambdaParameterList(path: AstPath<import("java-parser").NormalLambdaParameterListCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
normalLambdaParameter: typeof printSingle;
|
||||
regularLambdaParameter(path: AstPath<import("java-parser").RegularLambdaParameterCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
lambdaParameterType: typeof printSingle;
|
||||
conciseLambdaParameter: typeof printSingle;
|
||||
lambdaBody: typeof printSingle;
|
||||
conditionalExpression(path: AstPath<import("java-parser").ConditionalExpressionCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc;
|
||||
binaryExpression(path: AstPath<import("java-parser").BinaryExpressionCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn, options: import("./helpers.js").JavaParserOptions): builders.Doc;
|
||||
unaryExpression(path: AstPath<import("java-parser").UnaryExpressionCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
unaryExpressionNotPlusMinus(path: AstPath<import("java-parser").UnaryExpressionNotPlusMinusCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
primary(path: AstPath<import("java-parser").PrimaryCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc;
|
||||
primaryPrefix: typeof printSingle;
|
||||
primarySuffix(path: AstPath<import("java-parser").PrimarySuffixCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc;
|
||||
fqnOrRefType(path: AstPath<import("java-parser").FqnOrRefTypeCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn, _: import("./helpers.js").JavaParserOptions, args: unknown): builders.Doc[];
|
||||
fqnOrRefTypePartFirst(path: AstPath<import("java-parser").FqnOrRefTypePartFirstCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
fqnOrRefTypePartRest(path: AstPath<import("java-parser").FqnOrRefTypePartRestCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
fqnOrRefTypePartCommon(path: AstPath<import("java-parser").FqnOrRefTypePartCommonCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc;
|
||||
parenthesisExpression(path: AstPath<import("java-parser").ParenthesisExpressionCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Group | "()" | (string | builders.Indent)[];
|
||||
castExpression: typeof printSingle;
|
||||
primitiveCastExpression(path: AstPath<import("java-parser").PrimitiveCastExpressionCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
referenceTypeCastExpression(path: AstPath<import("java-parser").ReferenceTypeCastExpressionCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
newExpression: typeof printSingle;
|
||||
unqualifiedClassInstanceCreationExpression(path: AstPath<import("java-parser").UnqualifiedClassInstanceCreationExpressionCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
classOrInterfaceTypeToInstantiate(path: AstPath<import("java-parser").ClassOrInterfaceTypeToInstantiateCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
typeArgumentsOrDiamond: typeof printSingle;
|
||||
diamond(): string;
|
||||
methodInvocationSuffix(path: AstPath<import("java-parser").MethodInvocationSuffixCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Group | "()";
|
||||
argumentList(path: AstPath<import("java-parser").ArgumentListCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Group | (builders.Indent | builders.Softline)[] | (builders.BreakParent | builders.Group)[];
|
||||
arrayCreationExpression(path: AstPath<import("java-parser").ArrayCreationExpressionCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
arrayCreationExpressionWithoutInitializerSuffix(path: AstPath<import("java-parser").ArrayCreationExpressionWithoutInitializerSuffixCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc;
|
||||
arrayCreationWithInitializerSuffix(path: AstPath<import("java-parser").ArrayCreationWithInitializerSuffixCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
dimExprs(path: AstPath<import("java-parser").DimExprsCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
dimExpr(path: AstPath<import("java-parser").DimExprCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
classLiteralSuffix(path: AstPath<import("java-parser").ClassLiteralSuffixCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
arrayAccessSuffix(path: AstPath<import("java-parser").ArrayAccessSuffixCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
methodReferenceSuffix(path: AstPath<import("java-parser").MethodReferenceSuffixCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
templateArgument: typeof printSingle;
|
||||
template: typeof printSingle;
|
||||
stringTemplate(path: AstPath<StringTemplateCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Indent;
|
||||
textBlockTemplate(path: AstPath<TextBlockTemplateCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Indent;
|
||||
embeddedExpression: typeof printSingle;
|
||||
pattern: typeof printSingle;
|
||||
typePattern: typeof printSingle;
|
||||
recordPattern(path: AstPath<import("java-parser").RecordPatternCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
componentPatternList(path: AstPath<import("java-parser").ComponentPatternListCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
componentPattern: typeof printSingle;
|
||||
matchAllPattern: typeof printSingle;
|
||||
guard(path: AstPath<import("java-parser").GuardCstNode & {
|
||||
comments?: JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
};
|
||||
export default _default;
|
||||
@@ -1,598 +0,0 @@
|
||||
import { builders, utils } from "prettier/doc";
|
||||
import { call, definedKeys, each, findBaseIndent, flatMap, hasLeadingComments, indentInParentheses, isBinaryExpression, isNonTerminal, isTerminal, map, onlyDefinedKey, printDanglingComments, printList, printName, printSingle } from "./helpers.js";
|
||||
const { breakParent, conditionalGroup, group, hardline, ifBreak, indent, indentIfBreak, join, line, lineSuffixBoundary, softline } = builders;
|
||||
const { removeLines, willBreak } = utils;
|
||||
export default {
|
||||
expression: printSingle,
|
||||
lambdaExpression(path, print, _, args = {}) {
|
||||
var _a;
|
||||
const hug = (_a = args.hug) !== null && _a !== void 0 ? _a : false;
|
||||
const parameters = call(path, print, "lambdaParameters");
|
||||
const expression = [hug ? removeLines(parameters) : parameters, " ->"];
|
||||
const lambdaExpression = path.node.children.lambdaBody[0].children.expression;
|
||||
const body = call(path, print, "lambdaBody");
|
||||
if (lambdaExpression) {
|
||||
const suffix = indent([line, body]);
|
||||
expression.push(group(hug ? [suffix, softline] : suffix));
|
||||
}
|
||||
else {
|
||||
expression.push(" ", body);
|
||||
}
|
||||
return expression;
|
||||
},
|
||||
lambdaParameters(path, print, options) {
|
||||
const parameters = printSingle(path, print);
|
||||
return !path.node.children.lambdaParametersWithBraces &&
|
||||
options.arrowParens === "always"
|
||||
? ["(", parameters, ")"]
|
||||
: parameters;
|
||||
},
|
||||
lambdaParametersWithBraces(path, print, options) {
|
||||
var _a;
|
||||
const { lambdaParameterList } = path.node.children;
|
||||
if (!lambdaParameterList) {
|
||||
return "()";
|
||||
}
|
||||
const { conciseLambdaParameterList, normalLambdaParameterList } = lambdaParameterList[0].children;
|
||||
const parameterCount = ((_a = conciseLambdaParameterList === null || conciseLambdaParameterList === void 0 ? void 0 : conciseLambdaParameterList[0].children.conciseLambdaParameter) !== null && _a !== void 0 ? _a : normalLambdaParameterList === null || normalLambdaParameterList === void 0 ? void 0 : normalLambdaParameterList[0].children.normalLambdaParameter).length;
|
||||
const parameters = call(path, print, "lambdaParameterList");
|
||||
if (parameterCount > 1) {
|
||||
return indentInParentheses(parameters);
|
||||
}
|
||||
return conciseLambdaParameterList && options.arrowParens === "avoid"
|
||||
? parameters
|
||||
: ["(", parameters, ")"];
|
||||
},
|
||||
lambdaParameterList: printSingle,
|
||||
conciseLambdaParameterList(path, print) {
|
||||
return printList(path, print, "conciseLambdaParameter");
|
||||
},
|
||||
normalLambdaParameterList(path, print) {
|
||||
return printList(path, print, "normalLambdaParameter");
|
||||
},
|
||||
normalLambdaParameter: printSingle,
|
||||
regularLambdaParameter(path, print) {
|
||||
return join(" ", [
|
||||
...map(path, print, "variableModifier"),
|
||||
call(path, print, "lambdaParameterType"),
|
||||
call(path, print, "variableDeclaratorId")
|
||||
]);
|
||||
},
|
||||
lambdaParameterType: printSingle,
|
||||
conciseLambdaParameter: printSingle,
|
||||
lambdaBody: printSingle,
|
||||
conditionalExpression(path, print) {
|
||||
var _a;
|
||||
const binaryExpression = call(path, print, "binaryExpression");
|
||||
if (!path.node.children.QuestionMark) {
|
||||
return binaryExpression;
|
||||
}
|
||||
const expressions = map(path, print, "expression");
|
||||
const contents = indent(join(line, [
|
||||
binaryExpression,
|
||||
["? ", expressions[0]],
|
||||
[": ", expressions[1]]
|
||||
]));
|
||||
const isNestedTernary = ((_a = path.getNode(4)) === null || _a === void 0 ? void 0 : _a.name) ===
|
||||
"conditionalExpression";
|
||||
return isNestedTernary ? contents : group(contents);
|
||||
},
|
||||
binaryExpression(path, print, options) {
|
||||
var _a, _b;
|
||||
const { children } = path.node;
|
||||
const operands = flatMap(path, print, definedKeys(children, [
|
||||
"expression",
|
||||
"pattern",
|
||||
"referenceType",
|
||||
"unaryExpression"
|
||||
]));
|
||||
const operators = flatMap(path, operatorPath => {
|
||||
const { node } = operatorPath;
|
||||
let image;
|
||||
if (isTerminal(node)) {
|
||||
image = node.image;
|
||||
}
|
||||
else if (node.children.Less) {
|
||||
image = "<<";
|
||||
}
|
||||
else {
|
||||
image = node.children.Greater.length === 2 ? ">>" : ">>>";
|
||||
}
|
||||
return { image, doc: print(operatorPath) };
|
||||
}, definedKeys(children, [
|
||||
"AssignmentOperator",
|
||||
"BinaryOperator",
|
||||
"Instanceof",
|
||||
"shiftOperator"
|
||||
]));
|
||||
const hasNonAssignmentOperators = (operators.length > 0 && !children.AssignmentOperator) ||
|
||||
(children.expression !== undefined &&
|
||||
isBinaryExpression(children.expression[0]));
|
||||
const isInList = ((_a = path.getNode(4)) === null || _a === void 0 ? void 0 : _a.name) === "elementValue" ||
|
||||
((_b = path.getNode(6)) === null || _b === void 0 ? void 0 : _b.name) === "argumentList";
|
||||
return binary(operands, operators, {
|
||||
hasNonAssignmentOperators,
|
||||
isInList,
|
||||
isRoot: true,
|
||||
operatorPosition: options.experimentalOperatorPosition
|
||||
});
|
||||
},
|
||||
unaryExpression(path, print) {
|
||||
return [
|
||||
...map(path, print, "UnaryPrefixOperator"),
|
||||
call(path, print, "primary"),
|
||||
...map(path, print, "UnarySuffixOperator")
|
||||
];
|
||||
},
|
||||
unaryExpressionNotPlusMinus(path, print) {
|
||||
const { children } = path.node;
|
||||
const expression = [];
|
||||
if (children.UnaryPrefixOperatorNotPlusMinus) {
|
||||
expression.push(...map(path, print, "UnaryPrefixOperatorNotPlusMinus"));
|
||||
}
|
||||
expression.push(call(path, print, "primary"));
|
||||
if (children.UnarySuffixOperator) {
|
||||
expression.push(...map(path, print, "UnarySuffixOperator"));
|
||||
}
|
||||
return join(" ", expression);
|
||||
},
|
||||
primary(path, print) {
|
||||
var _a, _b;
|
||||
const { children } = path.node;
|
||||
if (!children.primarySuffix) {
|
||||
return call(path, print, "primaryPrefix");
|
||||
}
|
||||
const methodInvocations = children.primarySuffix
|
||||
.filter(({ children }) => children.methodInvocationSuffix)
|
||||
.map(({ children }) => children.methodInvocationSuffix[0].children);
|
||||
const hasLambdaMethodParameter = methodInvocations.some(({ argumentList }) => argumentList === null || argumentList === void 0 ? void 0 : argumentList[0].children.expression.some(({ children }) => children.lambdaExpression));
|
||||
const prefixIsCallExpression = children.primaryPrefix[0].children.newExpression;
|
||||
const callExpressionCount = methodInvocations.length +
|
||||
(prefixIsCallExpression ? 1 : 0) +
|
||||
children.primarySuffix.filter(({ children }) => children.unqualifiedClassInstanceCreationExpression).length;
|
||||
const fqnOrRefType = (_a = children.primaryPrefix[0].children.fqnOrRefType) === null || _a === void 0 ? void 0 : _a[0].children;
|
||||
const prefixIsMethodInvocation = (fqnOrRefType === null || fqnOrRefType === void 0 ? void 0 : fqnOrRefType.fqnOrRefTypePartRest) !== undefined &&
|
||||
((_b = children.primarySuffix) === null || _b === void 0 ? void 0 : _b[0].children.methodInvocationSuffix) !== undefined;
|
||||
const prefixIsStaticMethodInvocation = prefixIsMethodInvocation && isCapitalizedIdentifier(fqnOrRefType);
|
||||
const prefixIsInstanceMethodInvocation = prefixIsMethodInvocation && !prefixIsStaticMethodInvocation;
|
||||
const mustBreakForCallExpressions = methodInvocations.length > 2 && hasLambdaMethodParameter;
|
||||
const separator = mustBreakForCallExpressions ? hardline : softline;
|
||||
const prefix = [
|
||||
call(path, prefixPath => print(prefixPath, {
|
||||
lastSeparator: prefixIsStaticMethodInvocation ||
|
||||
(prefixIsInstanceMethodInvocation && callExpressionCount === 1)
|
||||
? ""
|
||||
: separator
|
||||
}), "primaryPrefix")
|
||||
];
|
||||
const canBreakForCallExpressions = callExpressionCount > 2 ||
|
||||
(callExpressionCount === 2 && prefixIsInstanceMethodInvocation) ||
|
||||
willBreak(prefix);
|
||||
const suffixes = [];
|
||||
each(path, suffixPath => {
|
||||
const { node, previous } = suffixPath;
|
||||
const suffix = print(suffixPath);
|
||||
if (node.children.Dot) {
|
||||
if ((canBreakForCallExpressions &&
|
||||
((!previous && prefixIsCallExpression) ||
|
||||
(previous === null || previous === void 0 ? void 0 : previous.children.methodInvocationSuffix) ||
|
||||
(previous === null || previous === void 0 ? void 0 : previous.children.unqualifiedClassInstanceCreationExpression))) ||
|
||||
(!node.children.templateArgument && willBreak(suffix))) {
|
||||
suffixes.push(separator);
|
||||
}
|
||||
suffixes.push(suffix);
|
||||
}
|
||||
else if (previous) {
|
||||
suffixes.push(suffix);
|
||||
}
|
||||
else {
|
||||
prefix.push(prefixIsInstanceMethodInvocation && callExpressionCount >= 2
|
||||
? indent(suffix)
|
||||
: suffix);
|
||||
}
|
||||
}, "primarySuffix");
|
||||
const hasSuffixComments = children.primarySuffix.some(suffix => hasLeadingComments(suffix));
|
||||
return group(canBreakForCallExpressions || hasSuffixComments
|
||||
? [prefix, indent(suffixes)]
|
||||
: [prefix, ...suffixes]);
|
||||
},
|
||||
primaryPrefix: printSingle,
|
||||
primarySuffix(path, print) {
|
||||
const { children } = path.node;
|
||||
if (!children.Dot) {
|
||||
return printSingle(path, print);
|
||||
}
|
||||
const suffix = ["."];
|
||||
if (children.This) {
|
||||
suffix.push("this");
|
||||
}
|
||||
else if (children.Identifier) {
|
||||
if (children.typeArguments) {
|
||||
suffix.push(call(path, print, "typeArguments"));
|
||||
}
|
||||
suffix.push(call(path, print, "Identifier"));
|
||||
}
|
||||
else {
|
||||
const suffixKey = onlyDefinedKey(children, [
|
||||
"templateArgument",
|
||||
"unqualifiedClassInstanceCreationExpression"
|
||||
]);
|
||||
suffix.push(call(path, print, suffixKey));
|
||||
}
|
||||
return suffix;
|
||||
},
|
||||
fqnOrRefType(path, print, _, args) {
|
||||
var _a;
|
||||
const lastSeparator = (_a = args.lastSeparator) !== null && _a !== void 0 ? _a : "";
|
||||
const fqnOrRefType = [
|
||||
call(path, print, "fqnOrRefTypePartFirst"),
|
||||
...map(path, partPath => {
|
||||
const part = print(partPath);
|
||||
return partPath.isLast
|
||||
? [willBreak(part) ? hardline : lastSeparator, part]
|
||||
: part;
|
||||
}, "fqnOrRefTypePartRest")
|
||||
];
|
||||
fqnOrRefType.push(indent(fqnOrRefType.pop()));
|
||||
return path.node.children.dims
|
||||
? [fqnOrRefType, call(path, print, "dims")]
|
||||
: fqnOrRefType;
|
||||
},
|
||||
fqnOrRefTypePartFirst(path, print) {
|
||||
return join(" ", [
|
||||
...map(path, print, "annotation"),
|
||||
call(path, print, "fqnOrRefTypePartCommon")
|
||||
]);
|
||||
},
|
||||
fqnOrRefTypePartRest(path, print) {
|
||||
const common = call(path, print, "fqnOrRefTypePartCommon");
|
||||
const type = path.node.children.typeArguments
|
||||
? [call(path, print, "typeArguments"), common]
|
||||
: common;
|
||||
return [".", ...join(" ", [...map(path, print, "annotation"), type])];
|
||||
},
|
||||
fqnOrRefTypePartCommon(path, print) {
|
||||
const { children } = path.node;
|
||||
const keywordKey = onlyDefinedKey(children, ["Identifier", "Super"]);
|
||||
const keyword = call(path, print, keywordKey);
|
||||
return children.typeArguments
|
||||
? [keyword, call(path, print, "typeArguments")]
|
||||
: keyword;
|
||||
},
|
||||
parenthesisExpression(path, print) {
|
||||
var _a;
|
||||
const expression = call(path, print, "expression");
|
||||
const ancestorName = (_a = path.getNode(14)) === null || _a === void 0 ? void 0 : _a.name;
|
||||
const binaryExpression = path.getNode(8);
|
||||
return ancestorName &&
|
||||
["guard", "returnStatement"].includes(ancestorName) &&
|
||||
binaryExpression &&
|
||||
binaryExpression.name === "binaryExpression" &&
|
||||
Object.keys(binaryExpression.children).length === 1
|
||||
? indentInParentheses(expression)
|
||||
: ["(", indent(expression), ")"];
|
||||
},
|
||||
castExpression: printSingle,
|
||||
primitiveCastExpression(path, print) {
|
||||
return [
|
||||
"(",
|
||||
call(path, print, "primitiveType"),
|
||||
") ",
|
||||
call(path, print, "unaryExpression")
|
||||
];
|
||||
},
|
||||
referenceTypeCastExpression(path, print) {
|
||||
const { children } = path.node;
|
||||
const type = call(path, print, "referenceType");
|
||||
const cast = children.additionalBound
|
||||
? indentInParentheses(join(line, [type, ...map(path, print, "additionalBound")]))
|
||||
: ["(", type, ")"];
|
||||
const expressionKey = onlyDefinedKey(children, [
|
||||
"lambdaExpression",
|
||||
"unaryExpressionNotPlusMinus"
|
||||
]);
|
||||
return [cast, " ", call(path, print, expressionKey)];
|
||||
},
|
||||
newExpression: printSingle,
|
||||
unqualifiedClassInstanceCreationExpression(path, print) {
|
||||
const { children } = path.node;
|
||||
const expression = ["new "];
|
||||
if (children.typeArguments) {
|
||||
expression.push(call(path, print, "typeArguments"));
|
||||
}
|
||||
expression.push(call(path, print, "classOrInterfaceTypeToInstantiate"), children.argumentList
|
||||
? group(["(", call(path, print, "argumentList"), ")"])
|
||||
: "()");
|
||||
if (children.classBody) {
|
||||
expression.push(" ", call(path, print, "classBody"));
|
||||
}
|
||||
return expression;
|
||||
},
|
||||
classOrInterfaceTypeToInstantiate(path, print) {
|
||||
const { children } = path.node;
|
||||
const type = children.annotation
|
||||
? flatMap(path, childPath => [
|
||||
print(childPath),
|
||||
isNonTerminal(childPath.node) ? " " : "."
|
||||
], ["annotation", "Identifier"])
|
||||
: printName(path, print);
|
||||
if (children.typeArgumentsOrDiamond) {
|
||||
type.push(call(path, print, "typeArgumentsOrDiamond"));
|
||||
}
|
||||
return type;
|
||||
},
|
||||
typeArgumentsOrDiamond: printSingle,
|
||||
diamond() {
|
||||
return "<>";
|
||||
},
|
||||
methodInvocationSuffix(path, print) {
|
||||
return path.node.children.argumentList
|
||||
? group(["(", call(path, print, "argumentList"), ")"])
|
||||
: indentInParentheses(printDanglingComments(path), { shouldBreak: true });
|
||||
},
|
||||
argumentList(path, print) {
|
||||
var _a, _b, _c, _d;
|
||||
const expressions = path.node.children.expression;
|
||||
const lastExpression = expressions.at(-1);
|
||||
const lastExpressionLambdaBodyExpression = (_b = (_a = lastExpression.children.lambdaExpression) === null || _a === void 0 ? void 0 : _a[0].children.lambdaBody[0].children.expression) === null || _b === void 0 ? void 0 : _b[0].children;
|
||||
const lastExpressionLambdaBodyTernaryExpression = (_c = lastExpressionLambdaBodyExpression === null || lastExpressionLambdaBodyExpression === void 0 ? void 0 : lastExpressionLambdaBodyExpression.conditionalExpression) === null || _c === void 0 ? void 0 : _c[0].children;
|
||||
const isHuggable = !lastExpression.comments &&
|
||||
(!lastExpressionLambdaBodyExpression ||
|
||||
(lastExpressionLambdaBodyTernaryExpression === null || lastExpressionLambdaBodyTernaryExpression === void 0 ? void 0 : lastExpressionLambdaBodyTernaryExpression.QuestionMark) !== undefined ||
|
||||
((_d = lastExpressionLambdaBodyTernaryExpression === null || lastExpressionLambdaBodyTernaryExpression === void 0 ? void 0 : lastExpressionLambdaBodyTernaryExpression.binaryExpression) === null || _d === void 0 ? void 0 : _d[0].children.unaryExpression.length) === 1) &&
|
||||
expressions.findIndex(({ children }) => children.lambdaExpression) ===
|
||||
expressions.length - 1;
|
||||
const args = map(path, print, "expression");
|
||||
const allArgsExpandable = [
|
||||
indent([softline, ...join([",", line], args)]),
|
||||
softline
|
||||
];
|
||||
if (!isHuggable || willBreak(args.at(-1)[0])) {
|
||||
return allArgsExpandable;
|
||||
}
|
||||
const headArgs = args.slice(0, -1);
|
||||
const huggedLastArg = path.call(argPath => print(argPath, { hug: true }), "children", "expression", args.length - 1);
|
||||
const lastArgExpanded = join(", ", [
|
||||
...headArgs,
|
||||
group(huggedLastArg, { shouldBreak: true })
|
||||
]);
|
||||
if (willBreak(huggedLastArg)) {
|
||||
return [
|
||||
breakParent,
|
||||
conditionalGroup([lastArgExpanded, allArgsExpandable])
|
||||
];
|
||||
}
|
||||
return conditionalGroup([
|
||||
join(", ", [...headArgs, huggedLastArg]),
|
||||
lastArgExpanded,
|
||||
allArgsExpandable
|
||||
]);
|
||||
},
|
||||
arrayCreationExpression(path, print) {
|
||||
const { children } = path.node;
|
||||
const typeKey = onlyDefinedKey(children, [
|
||||
"classOrInterfaceType",
|
||||
"primitiveType"
|
||||
]);
|
||||
const suffixKey = onlyDefinedKey(children, [
|
||||
"arrayCreationExpressionWithoutInitializerSuffix",
|
||||
"arrayCreationWithInitializerSuffix"
|
||||
]);
|
||||
return ["new ", call(path, print, typeKey), call(path, print, suffixKey)];
|
||||
},
|
||||
arrayCreationExpressionWithoutInitializerSuffix(path, print) {
|
||||
const expressions = call(path, print, "dimExprs");
|
||||
return path.node.children.dims
|
||||
? [expressions, call(path, print, "dims")]
|
||||
: expressions;
|
||||
},
|
||||
arrayCreationWithInitializerSuffix(path, print) {
|
||||
return [
|
||||
call(path, print, "dims"),
|
||||
" ",
|
||||
call(path, print, "arrayInitializer")
|
||||
];
|
||||
},
|
||||
dimExprs(path, print) {
|
||||
return map(path, print, "dimExpr");
|
||||
},
|
||||
dimExpr(path, print) {
|
||||
return join(" ", [
|
||||
...map(path, print, "annotation"),
|
||||
["[", call(path, print, "expression"), "]"]
|
||||
]);
|
||||
},
|
||||
classLiteralSuffix(path, print) {
|
||||
const lSquares = map(path, print, "LSquare");
|
||||
const rSquares = map(path, print, "RSquare");
|
||||
return [
|
||||
...lSquares.flatMap((lSquare, index) => [lSquare, rSquares[index]]),
|
||||
".class"
|
||||
];
|
||||
},
|
||||
arrayAccessSuffix(path, print) {
|
||||
return ["[", call(path, print, "expression"), "]"];
|
||||
},
|
||||
methodReferenceSuffix(path, print) {
|
||||
const { children } = path.node;
|
||||
const reference = ["::"];
|
||||
if (children.typeArguments) {
|
||||
reference.push(call(path, print, "typeArguments"));
|
||||
}
|
||||
reference.push(call(path, print, onlyDefinedKey(children, ["Identifier", "New"])));
|
||||
return reference;
|
||||
},
|
||||
templateArgument: printSingle,
|
||||
template: printSingle,
|
||||
stringTemplate(path, print) {
|
||||
return printTemplate(path, print, "StringTemplateBegin", "StringTemplateMid", "StringTemplateEnd");
|
||||
},
|
||||
textBlockTemplate(path, print) {
|
||||
return printTemplate(path, print, "TextBlockTemplateBegin", "TextBlockTemplateMid", "TextBlockTemplateEnd");
|
||||
},
|
||||
embeddedExpression: printSingle,
|
||||
pattern: printSingle,
|
||||
typePattern: printSingle,
|
||||
recordPattern(path, print) {
|
||||
const patterns = path.node.children.componentPatternList
|
||||
? indentInParentheses(call(path, print, "componentPatternList"))
|
||||
: "()";
|
||||
return [call(path, print, "referenceType"), patterns];
|
||||
},
|
||||
componentPatternList(path, print) {
|
||||
return printList(path, print, "componentPattern");
|
||||
},
|
||||
componentPattern: printSingle,
|
||||
matchAllPattern: printSingle,
|
||||
guard(path, print) {
|
||||
var _a;
|
||||
const expression = call(path, print, "expression");
|
||||
const hasParentheses = ((_a = path.node.children.expression[0].children.conditionalExpression) === null || _a === void 0 ? void 0 : _a[0].children.binaryExpression[0].children.unaryExpression[0].children.primary[0].children.primaryPrefix[0].children.parenthesisExpression) !==
|
||||
undefined;
|
||||
return [
|
||||
"when ",
|
||||
hasParentheses
|
||||
? expression
|
||||
: group([
|
||||
ifBreak("("),
|
||||
indent([softline, expression]),
|
||||
softline,
|
||||
ifBreak(")")
|
||||
])
|
||||
];
|
||||
}
|
||||
};
|
||||
function binary(operands, operators, { hasNonAssignmentOperators = false, isInList = false, isRoot = false, operatorPosition }) {
|
||||
let levelOperator;
|
||||
let levelPrecedence;
|
||||
let level = [];
|
||||
while (operators.length) {
|
||||
const nextOperator = operators[0].image;
|
||||
const nextPrecedence = getOperatorPrecedence(nextOperator);
|
||||
if (levelPrecedence === undefined || nextPrecedence === levelPrecedence) {
|
||||
const { image: operator, doc: operatorDoc } = operators.shift();
|
||||
level.push(operands.shift());
|
||||
if (levelOperator !== undefined &&
|
||||
needsParentheses(levelOperator, operator)) {
|
||||
level = [["(", group(indent(level)), ")"]];
|
||||
}
|
||||
const parts = [" ", operatorDoc, line];
|
||||
if (operatorPosition === "start" && !isAssignmentOperator(operator)) {
|
||||
parts.reverse();
|
||||
}
|
||||
level.push(parts);
|
||||
levelOperator = operator;
|
||||
levelPrecedence = nextPrecedence;
|
||||
}
|
||||
else if (nextPrecedence < levelPrecedence) {
|
||||
if (!isRoot) {
|
||||
break;
|
||||
}
|
||||
level.push(operands.shift());
|
||||
const content = group(indent(level));
|
||||
operands.unshift(levelOperator !== undefined &&
|
||||
needsParentheses(levelOperator, nextOperator)
|
||||
? ["(", content, ")"]
|
||||
: content);
|
||||
level = [];
|
||||
levelOperator = undefined;
|
||||
levelPrecedence = undefined;
|
||||
}
|
||||
else {
|
||||
const content = binary(operands, operators, { operatorPosition });
|
||||
operands.unshift(levelOperator !== undefined &&
|
||||
needsParentheses(nextOperator, levelOperator)
|
||||
? ["(", indent(content), ")"]
|
||||
: content);
|
||||
}
|
||||
}
|
||||
level.push(operands.shift());
|
||||
if (!levelOperator ||
|
||||
(!isInList &&
|
||||
!isAssignmentOperator(levelOperator) &&
|
||||
levelOperator !== "instanceof")) {
|
||||
return group(level);
|
||||
}
|
||||
if (!isRoot || hasNonAssignmentOperators) {
|
||||
return group(indent(level));
|
||||
}
|
||||
const groupId = Symbol("assignment");
|
||||
return group([
|
||||
level[0],
|
||||
group(indent(level[1]), { id: groupId }),
|
||||
indentIfBreak(level[2], { groupId })
|
||||
]);
|
||||
}
|
||||
const precedencesByOperator = new Map([
|
||||
["||"],
|
||||
["&&"],
|
||||
["|"],
|
||||
["^"],
|
||||
["&"],
|
||||
["==", "!="],
|
||||
["<", ">", "<=", ">=", "instanceof"],
|
||||
["<<", ">>", ">>>"],
|
||||
["+", "-"],
|
||||
["*", "/", "%"]
|
||||
].flatMap((operators, index) => operators.map(operator => [operator, index])));
|
||||
function getOperatorPrecedence(operator) {
|
||||
var _a;
|
||||
return (_a = precedencesByOperator.get(operator)) !== null && _a !== void 0 ? _a : -1;
|
||||
}
|
||||
function needsParentheses(operator, parentOperator) {
|
||||
return ((operator === "&&" && parentOperator === "||") ||
|
||||
(["|", "^", "&", "<<", ">>", ">>>"].includes(parentOperator) &&
|
||||
getOperatorPrecedence(operator) >
|
||||
getOperatorPrecedence(parentOperator)) ||
|
||||
[operator, parentOperator].every(o => ["==", "!="].includes(o)) ||
|
||||
[operator, parentOperator].every(o => ["<<", ">>", ">>>"].includes(o)) ||
|
||||
(operator === "*" && parentOperator === "/") ||
|
||||
(operator === "/" && parentOperator === "*") ||
|
||||
(operator === "%" && ["+", "-", "*", "/"].includes(parentOperator)) ||
|
||||
(["*", "/"].includes(operator) && parentOperator === "%"));
|
||||
}
|
||||
const assignmentOperators = new Set([
|
||||
"=",
|
||||
"*=",
|
||||
"/=",
|
||||
"%=",
|
||||
"+=",
|
||||
"-=",
|
||||
"<<=",
|
||||
">>=",
|
||||
">>>=",
|
||||
"&=",
|
||||
"^=",
|
||||
"|="
|
||||
]);
|
||||
function isAssignmentOperator(operator) {
|
||||
return assignmentOperators.has(operator);
|
||||
}
|
||||
function isCapitalizedIdentifier(fqnOrRefType) {
|
||||
var _a, _b, _c;
|
||||
const nextToLastIdentifier = (_c = (_b = [
|
||||
fqnOrRefType.fqnOrRefTypePartFirst[0],
|
||||
...((_a = fqnOrRefType.fqnOrRefTypePartRest) !== null && _a !== void 0 ? _a : [])
|
||||
].at(-2)) === null || _b === void 0 ? void 0 : _b.children.fqnOrRefTypePartCommon[0].children.Identifier) === null || _c === void 0 ? void 0 : _c[0].image;
|
||||
return /^\p{Uppercase_Letter}/u.test(nextToLastIdentifier !== null && nextToLastIdentifier !== void 0 ? nextToLastIdentifier : "");
|
||||
}
|
||||
function printTemplate(path, print, beginKey, midKey, endKey) {
|
||||
const begin = call(path, ({ node }) => node.image, beginKey);
|
||||
const mids = map(path, ({ node }) => node.image, midKey);
|
||||
const end = call(path, ({ node }) => node.image, endKey);
|
||||
const lines = [begin, ...mids, end].join("").split("\n").slice(1);
|
||||
const baseIndent = findBaseIndent(lines);
|
||||
const prefix = "\n" + " ".repeat(baseIndent);
|
||||
const parts = [begin, ...mids, end].map(image => join(hardline, image.split(prefix)));
|
||||
return indent([
|
||||
parts[0],
|
||||
...map(path, (expressionPath, index) => {
|
||||
const expression = group([
|
||||
indent([softline, print(expressionPath), lineSuffixBoundary]),
|
||||
softline
|
||||
]);
|
||||
return index === 0 ? expression : [parts[index], expression];
|
||||
}, "embeddedExpression"),
|
||||
parts.at(-1)
|
||||
]);
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import type { AnnotationCstNode, ClassPermitsCstNode, ClassTypeCtx, CstElement, CstNode, ExpressionCstNode, InterfacePermitsCstNode, IToken, StatementCstNode } from "java-parser";
|
||||
import type { AstPath, Doc, ParserOptions } from "prettier";
|
||||
import { builders } from "prettier/doc";
|
||||
import type { JavaComment } from "../comments.js";
|
||||
export declare function onlyDefinedKey<T extends Record<string, any>, K extends Key<T> & string>(obj: T, options?: K[]): K;
|
||||
export declare function definedKeys<T extends Record<string, any>, K extends Key<T> & string>(obj: T, options?: K[]): K[];
|
||||
export declare function printWithModifiers<T extends CstNode, P extends IterProperties<T["children"]>>(path: AstPath<T>, print: JavaPrintFn, modifierChild: P, contents: Doc, noTypeAnnotations?: boolean): builders.Doc[];
|
||||
export declare function hasDeclarationAnnotations(modifiers: ModifierNode[]): boolean;
|
||||
export declare function call<T extends CstNode, U, P extends IterProperties<T["children"]>>(path: AstPath<T>, callback: MapCallback<IndexValue<IndexValue<T, "children">, P>, U>, child: P): U;
|
||||
export declare function each<T extends CstNode, P extends IterProperties<T["children"]>>(path: AstPath<T>, callback: MapCallback<IndexValue<IndexValue<T, "children">, P>, void>, child: P): void;
|
||||
export declare function map<T extends CstNode, U, P extends IterProperties<T["children"]>>(path: AstPath<T>, callback: MapCallback<IndexValue<IndexValue<T, "children">, P>, U>, child: P): U[];
|
||||
export declare function flatMap<T extends CstNode, U, P extends IterProperties<T["children"]>>(path: AstPath<T>, callback: MapCallback<IndexValue<IndexValue<T, "children">, P>, U>, children: P[]): U[];
|
||||
export declare function printSingle(path: AstPath<JavaNonTerminal>, print: JavaPrintFn, _?: JavaParserOptions, args?: unknown): builders.Doc;
|
||||
export declare function lineStartWithComments(node: JavaNonTerminal): number;
|
||||
export declare function lineEndWithComments(node: JavaNonTerminal): number;
|
||||
export declare function printDanglingComments(path: AstPath<JavaNonTerminal>): builders.Doc[];
|
||||
export declare function printComment(node: JavaTerminal): string | builders.Doc[];
|
||||
export declare function hasLeadingComments(node: JavaNode): boolean | undefined;
|
||||
export declare function indentInParentheses(contents: Doc, opts?: {
|
||||
shouldBreak?: boolean;
|
||||
}): builders.Group | "()";
|
||||
export declare function printArrayInitializer<T extends JavaNonTerminal, P extends IterProperties<T["children"]>>(path: AstPath<T>, print: JavaPrintFn, options: JavaParserOptions, child: P): builders.Group | "{}";
|
||||
export declare function printBlock(path: AstPath<JavaNonTerminal>, contents: Doc[]): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
|
||||
export declare function printName(path: AstPath<JavaNonTerminal & {
|
||||
children: {
|
||||
Identifier: IToken[];
|
||||
};
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
export declare function printList<T extends JavaNonTerminal, P extends IterProperties<T["children"]>>(path: AstPath<T>, print: JavaPrintFn, child: P): builders.Doc[];
|
||||
export declare function printClassPermits(path: AstPath<ClassPermitsCstNode | InterfacePermitsCstNode>, print: JavaPrintFn): builders.Group;
|
||||
export declare function printClassType(path: AstPath<JavaNonTerminal & {
|
||||
children: ClassTypeCtx;
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
export declare function isBinaryExpression(expression: ExpressionCstNode): boolean;
|
||||
export declare function findBaseIndent(lines: string[]): number;
|
||||
export declare function isEmptyStatement(statement: StatementCstNode): boolean;
|
||||
export declare function isNonTerminal(node: CstElement): node is JavaNonTerminal;
|
||||
export declare function isTerminal(node: CstElement): node is IToken;
|
||||
export type JavaNode = CstElement & {
|
||||
comments?: JavaComment[];
|
||||
};
|
||||
export type JavaNonTerminal = Exclude<JavaNode, IToken>;
|
||||
export type JavaTerminal = Exclude<JavaNode, CstNode>;
|
||||
export type JavaNodePrinters = {
|
||||
[T in JavaNonTerminal["name"]]: JavaNodePrinter<T>;
|
||||
};
|
||||
export type JavaNodePrinter<T> = (path: AstPath<Extract<JavaNonTerminal, {
|
||||
name: T;
|
||||
}>>, print: JavaPrintFn, options: JavaParserOptions, args?: unknown) => Doc;
|
||||
export type JavaPrintFn = (path: AstPath<JavaNode>, args?: unknown) => Doc;
|
||||
export type JavaParserOptions = ParserOptions<JavaNode> & {
|
||||
entrypoint?: string;
|
||||
};
|
||||
export type IterProperties<T> = T extends any[] ? IndexProperties<T> : ArrayProperties<T>;
|
||||
type Key<T> = T extends T ? keyof T : never;
|
||||
type ModifierNode = JavaNonTerminal & {
|
||||
children: {
|
||||
annotation?: AnnotationCstNode[];
|
||||
};
|
||||
};
|
||||
type IsTuple<T> = T extends [] ? true : T extends [infer _First, ...infer Remain] ? IsTuple<Remain> : false;
|
||||
type IndexProperties<T extends {
|
||||
length: number;
|
||||
}> = IsTuple<T> extends true ? Exclude<Partial<T>["length"], T["length"]> : number;
|
||||
type ArrayProperties<T> = {
|
||||
[K in keyof T]: NonNullable<T[K]> extends readonly any[] ? K : never;
|
||||
}[keyof T];
|
||||
type ArrayElement<T> = T extends Array<infer E> ? E : never;
|
||||
type MapCallback<T, U> = (path: AstPath<ArrayElement<T>>, index: number, value: any) => U;
|
||||
type IndexValue<T, P> = T extends any[] ? P extends number ? T[P] : never : P extends keyof T ? T[P] : never;
|
||||
export {};
|
||||
@@ -1,239 +0,0 @@
|
||||
import { builders } from "prettier/doc";
|
||||
import parser from "../parser.js";
|
||||
const { group, hardline, ifBreak, indent, join, line, softline } = builders;
|
||||
export function onlyDefinedKey(obj, options) {
|
||||
const keys = definedKeys(obj, options);
|
||||
if (keys.length === 1) {
|
||||
return keys[0];
|
||||
}
|
||||
throw new Error(keys.length > 1
|
||||
? `More than one defined key found: ${keys}`
|
||||
: "No defined keys found");
|
||||
}
|
||||
export function definedKeys(obj, options) {
|
||||
return (options !== null && options !== void 0 ? options : Object.keys(obj)).filter(key => obj[key] !== undefined);
|
||||
}
|
||||
const indexByModifier = [
|
||||
"public",
|
||||
"protected",
|
||||
"private",
|
||||
"abstract",
|
||||
"default",
|
||||
"static",
|
||||
"final",
|
||||
"transient",
|
||||
"volatile",
|
||||
"synchronized",
|
||||
"native",
|
||||
"sealed",
|
||||
"non-sealed",
|
||||
"strictfp"
|
||||
].reduce((map, name, index) => map.set(name, index), new Map());
|
||||
export function printWithModifiers(path, print, modifierChild, contents, noTypeAnnotations = false) {
|
||||
const declarationAnnotations = [];
|
||||
const otherModifiers = [];
|
||||
const typeAnnotations = [];
|
||||
each(path, modifierPath => {
|
||||
const { children } = modifierPath.node;
|
||||
const modifier = print(modifierPath);
|
||||
if (children.annotation) {
|
||||
(otherModifiers.length ? typeAnnotations : declarationAnnotations).push(modifier);
|
||||
}
|
||||
else {
|
||||
otherModifiers.push(modifier);
|
||||
declarationAnnotations.push(...typeAnnotations);
|
||||
typeAnnotations.length = 0;
|
||||
}
|
||||
}, modifierChild);
|
||||
if (noTypeAnnotations) {
|
||||
declarationAnnotations.push(...typeAnnotations);
|
||||
typeAnnotations.length = 0;
|
||||
}
|
||||
otherModifiers.sort((a, b) => indexByModifier.get(a) - indexByModifier.get(b));
|
||||
return join(hardline, [
|
||||
...declarationAnnotations,
|
||||
join(" ", [...otherModifiers, ...typeAnnotations, contents])
|
||||
]);
|
||||
}
|
||||
export function hasDeclarationAnnotations(modifiers) {
|
||||
let hasAnnotation = false;
|
||||
let hasNonAnnotation = false;
|
||||
for (const modifier of modifiers) {
|
||||
if (modifier.children.annotation) {
|
||||
hasAnnotation = true;
|
||||
}
|
||||
else if (hasAnnotation) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
hasNonAnnotation = true;
|
||||
}
|
||||
}
|
||||
return hasAnnotation && !hasNonAnnotation;
|
||||
}
|
||||
export function call(path, callback, child) {
|
||||
return path.map(callback, "children", child)[0];
|
||||
}
|
||||
export function each(path, callback, child) {
|
||||
if (path.node.children[child]) {
|
||||
path.each(callback, "children", child);
|
||||
}
|
||||
}
|
||||
export function map(path, callback, child) {
|
||||
return path.node.children[child] ? path.map(callback, "children", child) : [];
|
||||
}
|
||||
export function flatMap(path, callback, children) {
|
||||
return children
|
||||
.flatMap(child => map(path, callback, child).map((doc, index) => {
|
||||
const node = path.node.children[child][index];
|
||||
return {
|
||||
doc,
|
||||
startOffset: parser.locStart(node)
|
||||
};
|
||||
}))
|
||||
.sort((a, b) => a.startOffset - b.startOffset)
|
||||
.map(({ doc }) => doc);
|
||||
}
|
||||
export function printSingle(path, print, _, args) {
|
||||
return call(path, childPath => print(childPath, args), onlyDefinedKey(path.node.children));
|
||||
}
|
||||
export function lineStartWithComments(node) {
|
||||
const { comments, location } = node;
|
||||
return comments
|
||||
? Math.min(location.startLine, comments[0].startLine)
|
||||
: location.startLine;
|
||||
}
|
||||
export function lineEndWithComments(node) {
|
||||
const { comments, location } = node;
|
||||
return comments
|
||||
? Math.max(location.endLine, comments.at(-1).endLine)
|
||||
: location.endLine;
|
||||
}
|
||||
export function printDanglingComments(path) {
|
||||
if (!path.node.comments) {
|
||||
return [];
|
||||
}
|
||||
const comments = [];
|
||||
path.each(commentPath => {
|
||||
const comment = commentPath.node;
|
||||
if (comment.leading || comment.trailing) {
|
||||
return;
|
||||
}
|
||||
comment.printed = true;
|
||||
comments.push(printComment(comment));
|
||||
}, "comments");
|
||||
return join(hardline, comments);
|
||||
}
|
||||
export function printComment(node) {
|
||||
const { image } = node;
|
||||
const lines = image.split("\n").map(line => line.trim());
|
||||
return lines.length > 1 &&
|
||||
lines[0].startsWith("/*") &&
|
||||
lines.slice(1).every(line => line.startsWith("*")) &&
|
||||
lines.at(-1).endsWith("*/")
|
||||
? join(hardline, lines.map((line, index) => (index === 0 ? line : ` ${line}`)))
|
||||
: image;
|
||||
}
|
||||
export function hasLeadingComments(node) {
|
||||
var _a;
|
||||
return (_a = node.comments) === null || _a === void 0 ? void 0 : _a.some(({ leading }) => leading);
|
||||
}
|
||||
export function indentInParentheses(contents, opts) {
|
||||
return !Array.isArray(contents) || contents.length
|
||||
? group(["(", indent([softline, contents]), softline, ")"], opts)
|
||||
: "()";
|
||||
}
|
||||
export function printArrayInitializer(path, print, options, child) {
|
||||
const list = [];
|
||||
if (child && child in path.node.children) {
|
||||
list.push(call(path, print, child));
|
||||
if (options.trailingComma !== "none") {
|
||||
list.push(ifBreak(","));
|
||||
}
|
||||
}
|
||||
list.push(...printDanglingComments(path));
|
||||
return list.length ? group(["{", indent([line, ...list]), line, "}"]) : "{}";
|
||||
}
|
||||
export function printBlock(path, contents) {
|
||||
if (!contents.length) {
|
||||
const danglingComments = printDanglingComments(path);
|
||||
return danglingComments.length
|
||||
? ["{", indent([hardline, ...danglingComments]), hardline, "}"]
|
||||
: "{}";
|
||||
}
|
||||
return group([
|
||||
"{",
|
||||
indent([hardline, ...join(hardline, contents)]),
|
||||
hardline,
|
||||
"}"
|
||||
]);
|
||||
}
|
||||
export function printName(path, print) {
|
||||
return join(".", map(path, print, "Identifier"));
|
||||
}
|
||||
export function printList(path, print, child) {
|
||||
return join([",", line], map(path, print, child));
|
||||
}
|
||||
export function printClassPermits(path, print) {
|
||||
return group([
|
||||
"permits",
|
||||
indent([line, group(printList(path, print, "typeName"))])
|
||||
]);
|
||||
}
|
||||
export function printClassType(path, print) {
|
||||
const { children } = path.node;
|
||||
return definedKeys(children, ["annotation", "Identifier", "typeArguments"])
|
||||
.flatMap(child => children[child].map((node, index) => ({
|
||||
child,
|
||||
index,
|
||||
startOffset: parser.locStart(node)
|
||||
})))
|
||||
.sort((a, b) => a.startOffset - b.startOffset)
|
||||
.flatMap(({ child, index: childIndex }, index, array) => {
|
||||
const node = children[child][childIndex];
|
||||
const next = array.at(index + 1);
|
||||
const nextNode = next && children[next.child][next.index];
|
||||
const docs = [path.call(print, "children", child, childIndex)];
|
||||
if (nextNode) {
|
||||
if (isNonTerminal(node)) {
|
||||
docs.push(node.name === "annotation" ? " " : ".");
|
||||
}
|
||||
else if (isTerminal(nextNode) || nextNode.name === "annotation") {
|
||||
docs.push(".");
|
||||
}
|
||||
}
|
||||
return docs;
|
||||
});
|
||||
}
|
||||
export function isBinaryExpression(expression) {
|
||||
var _a;
|
||||
const conditionalExpression = (_a = expression.children.conditionalExpression) === null || _a === void 0 ? void 0 : _a[0].children;
|
||||
if (!conditionalExpression) {
|
||||
return false;
|
||||
}
|
||||
const isTernary = conditionalExpression.QuestionMark !== undefined;
|
||||
if (isTernary) {
|
||||
return false;
|
||||
}
|
||||
const hasNonAssignmentOperators = Object.values(conditionalExpression.binaryExpression[0].children).some(child => {
|
||||
var _a;
|
||||
return isTerminal(child[0]) &&
|
||||
!((_a = child[0].tokenType.CATEGORIES) === null || _a === void 0 ? void 0 : _a.some(category => category.name === "AssignmentOperator"));
|
||||
});
|
||||
return hasNonAssignmentOperators;
|
||||
}
|
||||
export function findBaseIndent(lines) {
|
||||
return lines.length
|
||||
? Math.min(...lines.map(line => line.search(/\S/)).filter(indent => indent >= 0))
|
||||
: 0;
|
||||
}
|
||||
export function isEmptyStatement(statement) {
|
||||
var _a;
|
||||
return (((_a = statement.children.statementWithoutTrailingSubstatement) === null || _a === void 0 ? void 0 : _a[0].children.emptyStatement) !== undefined);
|
||||
}
|
||||
export function isNonTerminal(node) {
|
||||
return !isTerminal(node);
|
||||
}
|
||||
export function isTerminal(node) {
|
||||
return "tokenType" in node;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
import type { JavaNodePrinter, JavaNodePrinters } from "./helpers.js";
|
||||
export declare function printerForNodeType<T extends keyof JavaNodePrinters>(type: T): JavaNodePrinter<T>;
|
||||
@@ -1,13 +0,0 @@
|
||||
import arrays from "./arrays.js";
|
||||
import blocksAndStatements from "./blocks-and-statements.js";
|
||||
import classes from "./classes.js";
|
||||
import expressions from "./expressions.js";
|
||||
import interfaces from "./interfaces.js";
|
||||
import lexicalStructure from "./lexical-structure.js";
|
||||
import names from "./names.js";
|
||||
import packagesAndModules from "./packages-and-modules.js";
|
||||
import typesValuesAndVariables from "./types-values-and-variables.js";
|
||||
const printersByNodeType = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, arrays), blocksAndStatements), classes), expressions), interfaces), lexicalStructure), names), packagesAndModules), typesValuesAndVariables);
|
||||
export function printerForNodeType(type) {
|
||||
return printersByNodeType[type];
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { builders } from "prettier/doc";
|
||||
import { printClassPermits, printSingle } from "./helpers.js";
|
||||
declare const _default: {
|
||||
interfaceDeclaration(path: import("prettier").AstPath<import("java-parser").InterfaceDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
normalInterfaceDeclaration(path: import("prettier").AstPath<import("java-parser").NormalInterfaceDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
interfaceModifier: typeof printSingle;
|
||||
interfaceExtends(path: import("prettier").AstPath<import("java-parser").InterfaceExtendsCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Group;
|
||||
interfacePermits: typeof printClassPermits;
|
||||
interfaceBody(path: import("prettier").AstPath<import("java-parser").InterfaceBodyCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
|
||||
interfaceMemberDeclaration(path: import("prettier").AstPath<import("java-parser").InterfaceMemberDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc;
|
||||
constantDeclaration(path: import("prettier").AstPath<import("java-parser").ConstantDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
constantModifier: typeof printSingle;
|
||||
interfaceMethodDeclaration(path: import("prettier").AstPath<import("java-parser").InterfaceMethodDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
interfaceMethodModifier: typeof printSingle;
|
||||
annotationInterfaceDeclaration(path: import("prettier").AstPath<import("java-parser").AnnotationInterfaceDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
annotationInterfaceBody(path: import("prettier").AstPath<import("java-parser").AnnotationInterfaceBodyCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Group | "{}" | (string | builders.Indent | builders.Hardline)[];
|
||||
annotationInterfaceMemberDeclaration(path: import("prettier").AstPath<import("java-parser").AnnotationInterfaceMemberDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc;
|
||||
annotationInterfaceElementDeclaration(path: import("prettier").AstPath<import("java-parser").AnnotationInterfaceElementDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
annotationInterfaceElementModifier: typeof printSingle;
|
||||
defaultValue(path: import("prettier").AstPath<import("java-parser").DefaultValueCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
annotation(path: import("prettier").AstPath<import("java-parser").AnnotationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
elementValuePairList(path: import("prettier").AstPath<import("java-parser").ElementValuePairListCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
elementValuePair(path: import("prettier").AstPath<import("java-parser").ElementValuePairCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
elementValue: typeof printSingle;
|
||||
elementValueArrayInitializer(path: import("prettier").AstPath<import("java-parser").ElementValueArrayInitializerCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn, options: import("./helpers.js").JavaParserOptions): builders.Group | "{}";
|
||||
elementValueList(path: import("prettier").AstPath<import("java-parser").ElementValueListCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Group;
|
||||
};
|
||||
export default _default;
|
||||
@@ -1,157 +0,0 @@
|
||||
import { builders } from "prettier/doc";
|
||||
import { call, each, hasDeclarationAnnotations, indentInParentheses, lineEndWithComments, lineStartWithComments, onlyDefinedKey, printArrayInitializer, printBlock, printClassPermits, printList, printSingle, printWithModifiers } from "./helpers.js";
|
||||
const { group, hardline, indent, join, line } = builders;
|
||||
export default {
|
||||
interfaceDeclaration(path, print) {
|
||||
const declarationKey = onlyDefinedKey(path.node.children, [
|
||||
"annotationInterfaceDeclaration",
|
||||
"normalInterfaceDeclaration"
|
||||
]);
|
||||
return printWithModifiers(path, print, "interfaceModifier", call(path, print, declarationKey), true);
|
||||
},
|
||||
normalInterfaceDeclaration(path, print) {
|
||||
const { interfaceExtends, interfacePermits, typeParameters } = path.node.children;
|
||||
const header = ["interface ", call(path, print, "typeIdentifier")];
|
||||
if (typeParameters) {
|
||||
header.push(call(path, print, "typeParameters"));
|
||||
}
|
||||
if (interfaceExtends) {
|
||||
header.push(indent([line, call(path, print, "interfaceExtends")]));
|
||||
}
|
||||
if (interfacePermits) {
|
||||
header.push(indent([line, call(path, print, "interfacePermits")]));
|
||||
}
|
||||
return [group(header), " ", call(path, print, "interfaceBody")];
|
||||
},
|
||||
interfaceModifier: printSingle,
|
||||
interfaceExtends(path, print) {
|
||||
return group([
|
||||
"extends",
|
||||
indent([line, call(path, print, "interfaceTypeList")])
|
||||
]);
|
||||
},
|
||||
interfacePermits: printClassPermits,
|
||||
interfaceBody(path, print) {
|
||||
const declarations = [];
|
||||
let previousRequiresPadding = false;
|
||||
each(path, declarationPath => {
|
||||
var _a, _b, _c, _d;
|
||||
const declaration = print(declarationPath);
|
||||
if (declaration === "") {
|
||||
return;
|
||||
}
|
||||
const { node, previous } = declarationPath;
|
||||
const constantDeclaration = (_a = node.children.constantDeclaration) === null || _a === void 0 ? void 0 : _a[0].children;
|
||||
const methodDeclaration = (_b = node.children.interfaceMethodDeclaration) === null || _b === void 0 ? void 0 : _b[0].children;
|
||||
const currentRequiresPadding = (!constantDeclaration && !methodDeclaration) ||
|
||||
(methodDeclaration === null || methodDeclaration === void 0 ? void 0 : methodDeclaration.methodBody[0].children.block) !== undefined ||
|
||||
hasDeclarationAnnotations((_d = (_c = constantDeclaration === null || constantDeclaration === void 0 ? void 0 : constantDeclaration.constantModifier) !== null && _c !== void 0 ? _c : methodDeclaration === null || methodDeclaration === void 0 ? void 0 : methodDeclaration.interfaceMethodModifier) !== null && _d !== void 0 ? _d : []);
|
||||
const blankLine = declarations.length > 0 &&
|
||||
(previousRequiresPadding ||
|
||||
currentRequiresPadding ||
|
||||
lineStartWithComments(node) > lineEndWithComments(previous) + 1);
|
||||
declarations.push(blankLine ? [hardline, declaration] : declaration);
|
||||
previousRequiresPadding = currentRequiresPadding;
|
||||
}, "interfaceMemberDeclaration");
|
||||
return printBlock(path, declarations);
|
||||
},
|
||||
interfaceMemberDeclaration(path, print) {
|
||||
const { children } = path.node;
|
||||
return children.Semicolon
|
||||
? ""
|
||||
: call(path, print, onlyDefinedKey(children));
|
||||
},
|
||||
constantDeclaration(path, print) {
|
||||
const declaration = [
|
||||
call(path, print, "unannType"),
|
||||
" ",
|
||||
call(path, print, "variableDeclaratorList"),
|
||||
";"
|
||||
];
|
||||
return printWithModifiers(path, print, "constantModifier", declaration);
|
||||
},
|
||||
constantModifier: printSingle,
|
||||
interfaceMethodDeclaration(path, print) {
|
||||
const declaration = [
|
||||
call(path, print, "methodHeader"),
|
||||
path.node.children.methodBody[0].children.Semicolon ? "" : " ",
|
||||
call(path, print, "methodBody")
|
||||
];
|
||||
return printWithModifiers(path, print, "interfaceMethodModifier", declaration);
|
||||
},
|
||||
interfaceMethodModifier: printSingle,
|
||||
annotationInterfaceDeclaration(path, print) {
|
||||
return join(" ", [
|
||||
"@interface",
|
||||
call(path, print, "typeIdentifier"),
|
||||
call(path, print, "annotationInterfaceBody")
|
||||
]);
|
||||
},
|
||||
annotationInterfaceBody(path, print) {
|
||||
const declarations = [];
|
||||
each(path, declarationPath => {
|
||||
const declaration = print(declarationPath);
|
||||
if (declaration === "") {
|
||||
return;
|
||||
}
|
||||
declarations.push(declarationPath.isFirst ? declaration : [hardline, declaration]);
|
||||
}, "annotationInterfaceMemberDeclaration");
|
||||
return printBlock(path, declarations);
|
||||
},
|
||||
annotationInterfaceMemberDeclaration(path, print) {
|
||||
const { children } = path.node;
|
||||
return children.Semicolon
|
||||
? ""
|
||||
: call(path, print, onlyDefinedKey(children));
|
||||
},
|
||||
annotationInterfaceElementDeclaration(path, print) {
|
||||
const { dims, defaultValue } = path.node.children;
|
||||
const declaration = [
|
||||
call(path, print, "unannType"),
|
||||
" ",
|
||||
call(path, print, "Identifier"),
|
||||
"()"
|
||||
];
|
||||
if (dims) {
|
||||
declaration.push(call(path, print, "dims"));
|
||||
}
|
||||
if (defaultValue) {
|
||||
declaration.push(" ", call(path, print, "defaultValue"));
|
||||
}
|
||||
declaration.push(";");
|
||||
return printWithModifiers(path, print, "annotationInterfaceElementModifier", declaration);
|
||||
},
|
||||
annotationInterfaceElementModifier: printSingle,
|
||||
defaultValue(path, print) {
|
||||
return ["default ", call(path, print, "elementValue")];
|
||||
},
|
||||
annotation(path, print) {
|
||||
const { children } = path.node;
|
||||
const annotation = ["@", call(path, print, "typeName")];
|
||||
if (children.elementValue || children.elementValuePairList) {
|
||||
const valuesKey = onlyDefinedKey(children, [
|
||||
"elementValue",
|
||||
"elementValuePairList"
|
||||
]);
|
||||
annotation.push(indentInParentheses(call(path, print, valuesKey)));
|
||||
}
|
||||
return annotation;
|
||||
},
|
||||
elementValuePairList(path, print) {
|
||||
return printList(path, print, "elementValuePair");
|
||||
},
|
||||
elementValuePair(path, print) {
|
||||
return join(" ", [
|
||||
call(path, print, "Identifier"),
|
||||
call(path, print, "Equals"),
|
||||
call(path, print, "elementValue")
|
||||
]);
|
||||
},
|
||||
elementValue: printSingle,
|
||||
elementValueArrayInitializer(path, print, options) {
|
||||
return printArrayInitializer(path, print, options, "elementValueList");
|
||||
},
|
||||
elementValueList(path, print) {
|
||||
return group(printList(path, print, "elementValue"));
|
||||
}
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import { builders } from "prettier/doc";
|
||||
import { printSingle } from "./helpers.js";
|
||||
declare const _default: {
|
||||
literal(path: import("prettier").AstPath<import("java-parser").LiteralCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc;
|
||||
integerLiteral: typeof printSingle;
|
||||
floatingPointLiteral: typeof printSingle;
|
||||
booleanLiteral: typeof printSingle;
|
||||
shiftOperator(path: import("prettier").AstPath<import("java-parser").ShiftOperatorCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
};
|
||||
export default _default;
|
||||
@@ -1,29 +0,0 @@
|
||||
import { builders } from "prettier/doc";
|
||||
import { findBaseIndent, map, onlyDefinedKey, printSingle } from "./helpers.js";
|
||||
const { hardline, indent, join } = builders;
|
||||
export default {
|
||||
literal(path, print) {
|
||||
const { TextBlock } = path.node.children;
|
||||
if (!TextBlock) {
|
||||
return printSingle(path, print);
|
||||
}
|
||||
const [open, ...lines] = TextBlock[0].image.split("\n");
|
||||
const baseIndent = findBaseIndent(lines);
|
||||
const textBlock = join(hardline, [
|
||||
open,
|
||||
...lines.map(line => line.slice(baseIndent))
|
||||
]);
|
||||
const ancestor = path.getNode(14);
|
||||
return (ancestor === null || ancestor === void 0 ? void 0 : ancestor.name) === "variableInitializer" ||
|
||||
((ancestor === null || ancestor === void 0 ? void 0 : ancestor.name) === "binaryExpression" &&
|
||||
ancestor.children.AssignmentOperator)
|
||||
? indent(textBlock)
|
||||
: textBlock;
|
||||
},
|
||||
integerLiteral: printSingle,
|
||||
floatingPointLiteral: printSingle,
|
||||
booleanLiteral: printSingle,
|
||||
shiftOperator(path, print) {
|
||||
return map(path, print, onlyDefinedKey(path.node.children));
|
||||
}
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import { printName, printSingle } from "./helpers.js";
|
||||
declare const _default: {
|
||||
typeIdentifier: typeof printSingle;
|
||||
moduleName: typeof printName;
|
||||
packageName: typeof printName;
|
||||
typeName: typeof printName;
|
||||
expressionName: typeof printName;
|
||||
methodName: typeof printSingle;
|
||||
packageOrTypeName: typeof printName;
|
||||
ambiguousName: typeof printName;
|
||||
};
|
||||
export default _default;
|
||||
@@ -1,11 +0,0 @@
|
||||
import { printName, printSingle } from "./helpers.js";
|
||||
export default {
|
||||
typeIdentifier: printSingle,
|
||||
moduleName: printName,
|
||||
packageName: printName,
|
||||
typeName: printName,
|
||||
expressionName: printName,
|
||||
methodName: printSingle,
|
||||
packageOrTypeName: printName,
|
||||
ambiguousName: printName
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
import type { ExportsModuleDirectiveCstNode, ImportDeclarationCstNode, OpensModuleDirectiveCstNode } from "java-parser";
|
||||
import type { AstPath } from "prettier";
|
||||
import { builders } from "prettier/doc";
|
||||
import { printSingle, type JavaPrintFn } from "./helpers.js";
|
||||
declare const _default: {
|
||||
compilationUnit(path: AstPath<import("java-parser").CompilationUnitCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
ordinaryCompilationUnit(path: AstPath<import("java-parser").OrdinaryCompilationUnitCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
modularCompilationUnit(path: AstPath<import("java-parser").ModularCompilationUnitCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
packageDeclaration(path: AstPath<import("java-parser").PackageDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
packageModifier: typeof printSingle;
|
||||
importDeclaration(path: AstPath<ImportDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc;
|
||||
typeDeclaration(path: AstPath<import("java-parser").TypeDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc;
|
||||
moduleDeclaration(path: AstPath<import("java-parser").ModuleDeclarationCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
moduleDirective: typeof printSingle;
|
||||
requiresModuleDirective(path: AstPath<import("java-parser").RequiresModuleDirectiveCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
exportsModuleDirective(path: AstPath<ExportsModuleDirectiveCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
opensModuleDirective(path: AstPath<OpensModuleDirectiveCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
usesModuleDirective(path: AstPath<import("java-parser").UsesModuleDirectiveCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
providesModuleDirective(path: AstPath<import("java-parser").ProvidesModuleDirectiveCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: JavaPrintFn): builders.Doc[];
|
||||
requiresModifier: typeof printSingle;
|
||||
};
|
||||
export default _default;
|
||||
@@ -1,169 +0,0 @@
|
||||
import { builders } from "prettier/doc";
|
||||
import { call, lineEndWithComments, lineStartWithComments, map, printBlock, printDanglingComments, printName, printSingle } from "./helpers.js";
|
||||
const { group, hardline, indent, join, line } = builders;
|
||||
export default {
|
||||
compilationUnit(path, print) {
|
||||
return [...printDanglingComments(path), printSingle(path, print), hardline];
|
||||
},
|
||||
ordinaryCompilationUnit(path, print) {
|
||||
const { children } = path.node;
|
||||
const declarations = [];
|
||||
if (children.packageDeclaration) {
|
||||
declarations.push(call(path, print, "packageDeclaration"));
|
||||
}
|
||||
if (children.importDeclaration) {
|
||||
const staticCount = sortImports(children.importDeclaration);
|
||||
const importDeclarations = map(path, print, "importDeclaration").filter(doc => doc !== "");
|
||||
const staticDeclarations = importDeclarations.slice(0, staticCount);
|
||||
const nonStaticDeclarations = importDeclarations.slice(staticCount);
|
||||
declarations.push(...[staticDeclarations, nonStaticDeclarations]
|
||||
.filter(({ length }) => length)
|
||||
.map(declarations => join(hardline, declarations)));
|
||||
}
|
||||
if (children.typeDeclaration) {
|
||||
declarations.push(...map(path, print, "typeDeclaration").filter(declaration => declaration !== ""));
|
||||
}
|
||||
return join([hardline, hardline], declarations);
|
||||
},
|
||||
modularCompilationUnit(path, print) {
|
||||
const { children } = path.node;
|
||||
const declarations = [];
|
||||
if (children.importDeclaration) {
|
||||
const staticCount = sortImports(children.importDeclaration);
|
||||
const importDeclarations = map(path, print, "importDeclaration").filter(doc => doc !== "");
|
||||
const staticDeclarations = importDeclarations.slice(0, staticCount);
|
||||
const nonStaticDeclarations = importDeclarations.slice(staticCount);
|
||||
declarations.push(...[staticDeclarations, nonStaticDeclarations]
|
||||
.filter(({ length }) => length)
|
||||
.map(declarations => join(hardline, declarations)));
|
||||
}
|
||||
declarations.push(call(path, print, "moduleDeclaration"));
|
||||
return join([hardline, hardline], declarations);
|
||||
},
|
||||
packageDeclaration(path, print) {
|
||||
return join(hardline, [
|
||||
...map(path, print, "packageModifier"),
|
||||
["package ", printName(path, print), ";"]
|
||||
]);
|
||||
},
|
||||
packageModifier: printSingle,
|
||||
importDeclaration(path, print) {
|
||||
const { children } = path.node;
|
||||
if (children.emptyStatement) {
|
||||
return call(path, print, "emptyStatement");
|
||||
}
|
||||
const declaration = ["import "];
|
||||
if (children.Static) {
|
||||
declaration.push("static ");
|
||||
}
|
||||
declaration.push(call(path, print, "packageOrTypeName"));
|
||||
if (children.Star) {
|
||||
declaration.push(".*");
|
||||
}
|
||||
declaration.push(";");
|
||||
return declaration;
|
||||
},
|
||||
typeDeclaration(path, print) {
|
||||
return path.node.children.Semicolon ? "" : printSingle(path, print);
|
||||
},
|
||||
moduleDeclaration(path, print) {
|
||||
const { annotation, Open } = path.node.children;
|
||||
const prefix = [];
|
||||
if (annotation) {
|
||||
prefix.push(...map(path, print, "annotation"));
|
||||
}
|
||||
if (Open) {
|
||||
prefix.push("open");
|
||||
}
|
||||
const declarations = map(path, declarationPath => {
|
||||
const declaration = print(declarationPath);
|
||||
const { node, previous } = declarationPath;
|
||||
return !previous ||
|
||||
lineStartWithComments(node) <= lineEndWithComments(previous) + 1
|
||||
? declaration
|
||||
: [hardline, declaration];
|
||||
}, "moduleDirective");
|
||||
return join(" ", [
|
||||
...prefix,
|
||||
"module",
|
||||
printName(path, print),
|
||||
printBlock(path, declarations)
|
||||
]);
|
||||
},
|
||||
moduleDirective: printSingle,
|
||||
requiresModuleDirective(path, print) {
|
||||
return join(" ", [
|
||||
"requires",
|
||||
...map(path, print, "requiresModifier"),
|
||||
[call(path, print, "moduleName"), ";"]
|
||||
]);
|
||||
},
|
||||
exportsModuleDirective(path, print) {
|
||||
return printToModuleNamesDirective(path, print, "exports");
|
||||
},
|
||||
opensModuleDirective(path, print) {
|
||||
return printToModuleNamesDirective(path, print, "opens");
|
||||
},
|
||||
usesModuleDirective(path, print) {
|
||||
return ["uses ", call(path, print, "typeName"), ";"];
|
||||
},
|
||||
providesModuleDirective(path, print) {
|
||||
const [firstTypeName, ...restTypeNames] = map(path, print, "typeName");
|
||||
return [
|
||||
"provides ",
|
||||
firstTypeName,
|
||||
group(indent([
|
||||
line,
|
||||
group(indent(["with", line, ...join([",", line], restTypeNames)]))
|
||||
])),
|
||||
";"
|
||||
];
|
||||
},
|
||||
requiresModifier: printSingle
|
||||
};
|
||||
function sortImports(importDeclarations) {
|
||||
importDeclarations.sort(({ children: a }, { children: b }) => {
|
||||
if (a.Static && !b.Static) {
|
||||
return -1;
|
||||
}
|
||||
else if (b.Static && !a.Static) {
|
||||
return 1;
|
||||
}
|
||||
if (!b.packageOrTypeName) {
|
||||
if (a.packageOrTypeName) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else if (!a.packageOrTypeName) {
|
||||
return 1;
|
||||
}
|
||||
return compareFqn(a.packageOrTypeName[0], b.packageOrTypeName[0]);
|
||||
});
|
||||
return importDeclarations.reduce((staticCount, importDeclaration) => importDeclaration.children.Static ? staticCount + 1 : staticCount, 0);
|
||||
}
|
||||
function compareFqn(a, b) {
|
||||
const identifiersA = a.children.Identifier;
|
||||
const identifiersB = b.children.Identifier;
|
||||
const minParts = Math.min(identifiersA.length, identifiersB.length);
|
||||
for (let i = 0; i < minParts; i++) {
|
||||
const imageA = identifiersA[i].image;
|
||||
const imageB = identifiersB[i].image;
|
||||
if (imageA < imageB) {
|
||||
return -1;
|
||||
}
|
||||
else if (imageA > imageB) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return identifiersA.length - identifiersB.length;
|
||||
}
|
||||
function printToModuleNamesDirective(path, print, prefix) {
|
||||
const directive = [prefix, " ", call(path, print, "packageName")];
|
||||
if (path.node.children.moduleName) {
|
||||
const moduleNames = join([",", line], map(path, print, "moduleName"));
|
||||
directive.push(group(indent([line, group(indent(["to", line, ...moduleNames]))])));
|
||||
}
|
||||
directive.push(";");
|
||||
return directive;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { builders } from "prettier/doc";
|
||||
import { printClassType, printSingle } from "./helpers.js";
|
||||
declare const _default: {
|
||||
primitiveType(path: import("prettier").AstPath<import("java-parser").PrimitiveTypeCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
numericType: typeof printSingle;
|
||||
integralType: typeof printSingle;
|
||||
floatingPointType: typeof printSingle;
|
||||
referenceType(path: import("prettier").AstPath<import("java-parser").ReferenceTypeCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
classOrInterfaceType: typeof printSingle;
|
||||
classType: typeof printClassType;
|
||||
interfaceType: typeof printSingle;
|
||||
typeVariable(path: import("prettier").AstPath<import("java-parser").TypeVariableCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
dims(path: import("prettier").AstPath<import("java-parser").DimsCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
typeParameter(path: import("prettier").AstPath<import("java-parser").TypeParameterCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
typeParameterModifier: typeof printSingle;
|
||||
typeBound(path: import("prettier").AstPath<import("java-parser").TypeBoundCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
additionalBound(path: import("prettier").AstPath<import("java-parser").AdditionalBoundCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
typeArguments(path: import("prettier").AstPath<import("java-parser").TypeArgumentsCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Group;
|
||||
typeArgumentList(path: import("prettier").AstPath<import("java-parser").TypeArgumentListCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
typeArgument: typeof printSingle;
|
||||
wildcard(path: import("prettier").AstPath<import("java-parser").WildcardCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
wildcardBounds(path: import("prettier").AstPath<import("java-parser").WildcardBoundsCstNode & {
|
||||
comments?: import("../comments.js").JavaComment[];
|
||||
}>, print: import("./helpers.js").JavaPrintFn): builders.Doc[];
|
||||
};
|
||||
export default _default;
|
||||
@@ -1,90 +0,0 @@
|
||||
import { builders } from "prettier/doc";
|
||||
import { call, definedKeys, flatMap, isNonTerminal, map, onlyDefinedKey, printClassType, printList, printSingle } from "./helpers.js";
|
||||
const { group, indent, join, line, softline } = builders;
|
||||
export default {
|
||||
primitiveType(path, print) {
|
||||
const { children } = path.node;
|
||||
const typeKey = onlyDefinedKey(children, ["Boolean", "numericType"]);
|
||||
return join(" ", [
|
||||
...map(path, print, "annotation"),
|
||||
call(path, print, typeKey)
|
||||
]);
|
||||
},
|
||||
numericType: printSingle,
|
||||
integralType: printSingle,
|
||||
floatingPointType: printSingle,
|
||||
referenceType(path, print) {
|
||||
const { children } = path.node;
|
||||
const typeKey = onlyDefinedKey(children, [
|
||||
"primitiveType",
|
||||
"classOrInterfaceType"
|
||||
]);
|
||||
const type = call(path, print, typeKey);
|
||||
return join(" ", [
|
||||
...map(path, print, "annotation"),
|
||||
children.dims ? [type, call(path, print, "dims")] : type
|
||||
]);
|
||||
},
|
||||
classOrInterfaceType: printSingle,
|
||||
classType: printClassType,
|
||||
interfaceType: printSingle,
|
||||
typeVariable(path, print) {
|
||||
return join(" ", [
|
||||
...map(path, print, "annotation"),
|
||||
call(path, print, "Identifier")
|
||||
]);
|
||||
},
|
||||
dims(path, print) {
|
||||
return flatMap(path, childPath => {
|
||||
const child = print(childPath);
|
||||
return isNonTerminal(childPath.node) ? [child, " "] : child;
|
||||
}, definedKeys(path.node.children, ["annotation", "LSquare", "RSquare"]));
|
||||
},
|
||||
typeParameter(path, print) {
|
||||
const parameter = [
|
||||
...map(path, print, "typeParameterModifier"),
|
||||
call(path, print, "typeIdentifier")
|
||||
];
|
||||
if (path.node.children.typeBound) {
|
||||
parameter.push(call(path, print, "typeBound"));
|
||||
}
|
||||
return join(" ", parameter);
|
||||
},
|
||||
typeParameterModifier: printSingle,
|
||||
typeBound(path, print) {
|
||||
const bound = ["extends ", call(path, print, "classOrInterfaceType")];
|
||||
if (path.node.children.additionalBound) {
|
||||
bound.push(group(indent([line, ...join(line, map(path, print, "additionalBound"))])));
|
||||
}
|
||||
return bound;
|
||||
},
|
||||
additionalBound(path, print) {
|
||||
return ["& ", call(path, print, "interfaceType")];
|
||||
},
|
||||
typeArguments(path, print) {
|
||||
return group([
|
||||
"<",
|
||||
indent([softline, call(path, print, "typeArgumentList")]),
|
||||
softline,
|
||||
">"
|
||||
]);
|
||||
},
|
||||
typeArgumentList(path, print) {
|
||||
return printList(path, print, "typeArgument");
|
||||
},
|
||||
typeArgument: printSingle,
|
||||
wildcard(path, print) {
|
||||
const wildcard = [...map(path, print, "annotation"), "?"];
|
||||
if (path.node.children.wildcardBounds) {
|
||||
wildcard.push(call(path, print, "wildcardBounds"));
|
||||
}
|
||||
return join(" ", wildcard);
|
||||
},
|
||||
wildcardBounds(path, print) {
|
||||
return [
|
||||
path.node.children.Extends ? "extends" : "super",
|
||||
" ",
|
||||
call(path, print, "referenceType")
|
||||
];
|
||||
}
|
||||
};
|
||||
@@ -182,7 +182,7 @@ export default {
|
||||
general: 'General',
|
||||
editing: 'Editor',
|
||||
appearance: 'Appearance',
|
||||
backupPage: 'Backup',
|
||||
syncPage: 'Sync',
|
||||
keyBindings: 'Key Bindings',
|
||||
updates: 'Updates',
|
||||
reset: 'Reset',
|
||||
@@ -257,11 +257,16 @@ export default {
|
||||
restartNow: 'Restart Now',
|
||||
hotkeyPreview: 'Preview:',
|
||||
none: 'None',
|
||||
backup: {
|
||||
sync: {
|
||||
basicSettings: 'Basic Settings',
|
||||
enableBackup: 'Enable Git Backup',
|
||||
autoBackup: 'Auto Backup',
|
||||
backupInterval: 'Backup Interval',
|
||||
enableSync: 'Enable Sync',
|
||||
targetType: 'Sync Type',
|
||||
targetTypes: {
|
||||
git: 'Git',
|
||||
localfs: 'Local File System'
|
||||
},
|
||||
autoSync: 'Auto Sync',
|
||||
syncInterval: 'Sync Interval',
|
||||
intervals: {
|
||||
'5min': '5 minutes',
|
||||
'10min': '10 minutes',
|
||||
@@ -270,8 +275,11 @@ export default {
|
||||
'1hour': '1 hour'
|
||||
},
|
||||
repositoryConfig: 'Repository Configuration',
|
||||
storageConfig: 'Storage Configuration',
|
||||
repoUrl: 'Repository URL',
|
||||
repoUrlPlaceholder: 'Enter Git repository URL',
|
||||
localfsRootPath: 'Local Storage Directory',
|
||||
localfsRootPathPlaceholder: 'Select local sync directory',
|
||||
authConfig: 'Authentication Configuration',
|
||||
authMethod: 'Authentication Method',
|
||||
authMethods: {
|
||||
@@ -289,9 +297,11 @@ export default {
|
||||
sshKeyPathPlaceholder: 'Select SSH key file',
|
||||
sshKeyPassphrase: 'SSH Key Passphrase',
|
||||
sshKeyPassphrasePlaceholder: 'Enter SSH key passphrase',
|
||||
backupOperations: 'Backup Operations',
|
||||
syncOperations: 'Sync Operations',
|
||||
syncToRemote: 'Sync to Remote',
|
||||
syncToTarget: 'Sync to Target',
|
||||
syncing: 'Syncing...',
|
||||
syncSuccess: 'Sync completed',
|
||||
actions: {
|
||||
sync: 'Sync',
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ export default {
|
||||
general: '常规',
|
||||
editing: '编辑器',
|
||||
appearance: '外观',
|
||||
backupPage: '备份',
|
||||
syncPage: '同步',
|
||||
extensions: '扩展',
|
||||
keyBindings: '快捷键',
|
||||
updates: '更新',
|
||||
@@ -259,11 +259,16 @@ export default {
|
||||
colorValue: '颜色值',
|
||||
hotkeyPreview: '预览:',
|
||||
none: '无',
|
||||
backup: {
|
||||
sync: {
|
||||
basicSettings: '基本设置',
|
||||
enableBackup: '启用备份',
|
||||
autoBackup: '自动备份',
|
||||
backupInterval: '备份间隔',
|
||||
enableSync: '启用同步',
|
||||
targetType: '同步方式',
|
||||
targetTypes: {
|
||||
git: 'Git',
|
||||
localfs: '本地文件系统'
|
||||
},
|
||||
autoSync: '自动同步',
|
||||
syncInterval: '同步间隔',
|
||||
intervals: {
|
||||
'5min': '5分钟',
|
||||
'10min': '10分钟',
|
||||
@@ -272,8 +277,11 @@ export default {
|
||||
'1hour': '1小时'
|
||||
},
|
||||
repositoryConfig: '仓库配置',
|
||||
storageConfig: '存储配置',
|
||||
repoUrl: '仓库地址',
|
||||
repoUrlPlaceholder: '请输入Git仓库地址',
|
||||
localfsRootPath: '本地存储目录',
|
||||
localfsRootPathPlaceholder: '请选择本地同步目录',
|
||||
authConfig: '认证配置',
|
||||
authMethod: '认证方式',
|
||||
authMethods: {
|
||||
@@ -291,9 +299,11 @@ export default {
|
||||
sshKeyPathPlaceholder: '请选择SSH密钥文件',
|
||||
sshKeyPassphrase: 'SSH密钥密码',
|
||||
sshKeyPassphrasePlaceholder: '请输入SSH密钥密码',
|
||||
backupOperations: '备份操作',
|
||||
syncOperations: '同步操作',
|
||||
syncToRemote: '同步到远程',
|
||||
syncToTarget: '同步到目标',
|
||||
syncing: '同步中...',
|
||||
syncSuccess: '同步成功',
|
||||
actions: {
|
||||
sync: '同步',
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import AppearancePage from '@/views/settings/pages/AppearancePage.vue';
|
||||
import KeyBindingsPage from '@/views/settings/pages/KeyBindingsPage.vue';
|
||||
import UpdatesPage from '@/views/settings/pages/UpdatesPage.vue';
|
||||
import ExtensionsPage from '@/views/settings/pages/ExtensionsPage.vue';
|
||||
import BackupPage from '@/views/settings/pages/BackupPage.vue';
|
||||
import SyncPage from '@/views/settings/pages/SyncPage.vue';
|
||||
// 测试页面
|
||||
import TestPage from '@/views/settings/pages/TestPage.vue';
|
||||
|
||||
@@ -44,9 +44,9 @@ const settingsChildren: RouteRecordRaw[] = [
|
||||
component: UpdatesPage
|
||||
},
|
||||
{
|
||||
path: 'backup',
|
||||
name: 'SettingsBackup',
|
||||
component: BackupPage
|
||||
path: 'sync',
|
||||
name: 'SettingsSync',
|
||||
component: SyncPage
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -1,113 +1,124 @@
|
||||
import {defineStore} from 'pinia';
|
||||
import {computed, reactive} from 'vue';
|
||||
import {ConfigService, StartupService} from '@/../bindings/voidraft/internal/services';
|
||||
import {AppConfig, AuthMethod, LanguageType, SystemThemeType, TabType} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {
|
||||
AppConfig,
|
||||
AuthMethod,
|
||||
SyncTarget,
|
||||
LanguageType,
|
||||
SystemThemeType,
|
||||
} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {ConfigUtils} from '@/common/utils/configUtils';
|
||||
import {FONT_OPTIONS} from '@/common/constant/fonts';
|
||||
import {
|
||||
CONFIG_KEY_MAP,
|
||||
CONFIG_LIMITS,
|
||||
ConfigKey,
|
||||
ConfigSection,
|
||||
DEFAULT_CONFIG,
|
||||
NumberConfigKey
|
||||
} from '@/common/constant/config';
|
||||
import {CONFIG_KEY_MAP, CONFIG_LIMITS, ConfigKey, DEFAULT_CONFIG, NumberConfigKey} from '@/common/constant/config';
|
||||
import * as runtime from '@wailsio/runtime';
|
||||
|
||||
export const useConfigStore = defineStore('config', () => {
|
||||
const {locale} = useI18n();
|
||||
|
||||
// 响应式状态
|
||||
const state = reactive({
|
||||
config: {...DEFAULT_CONFIG} as AppConfig,
|
||||
config: structuredClone(DEFAULT_CONFIG) as AppConfig,
|
||||
isLoading: false,
|
||||
configLoaded: false
|
||||
});
|
||||
|
||||
// Font options (no longer localized)
|
||||
const fontOptions = computed(() => FONT_OPTIONS);
|
||||
|
||||
// 统一配置更新方法
|
||||
const updateConfig = async <K extends ConfigKey>(key: K, value: any): Promise<void> => {
|
||||
const applyConfig = (appConfig?: AppConfig | null): void => {
|
||||
const nextConfig = structuredClone(DEFAULT_CONFIG) as AppConfig;
|
||||
|
||||
if (appConfig?.general) Object.assign(nextConfig.general, appConfig.general);
|
||||
if (appConfig?.editing) Object.assign(nextConfig.editing, appConfig.editing);
|
||||
if (appConfig?.appearance) Object.assign(nextConfig.appearance, appConfig.appearance);
|
||||
if (appConfig?.updates) Object.assign(nextConfig.updates, appConfig.updates);
|
||||
if (appConfig?.sync) {
|
||||
if (appConfig.sync.target) {
|
||||
nextConfig.sync.target = appConfig.sync.target;
|
||||
}
|
||||
if (appConfig.sync.git) {
|
||||
Object.assign(nextConfig.sync.git, appConfig.sync.git);
|
||||
}
|
||||
if (appConfig.sync.localfs) {
|
||||
Object.assign(nextConfig.sync.localfs, appConfig.sync.localfs);
|
||||
}
|
||||
}
|
||||
if (appConfig?.metadata) Object.assign(nextConfig.metadata, appConfig.metadata);
|
||||
|
||||
state.config = nextConfig;
|
||||
};
|
||||
|
||||
const ensureConfigLoaded = async (): Promise<void> => {
|
||||
if (!state.configLoaded && !state.isLoading) {
|
||||
await initConfig();
|
||||
}
|
||||
};
|
||||
|
||||
const backendKey = CONFIG_KEY_MAP[key];
|
||||
if (!backendKey) {
|
||||
throw new Error(`No backend key mapping found for ${String(key)}`);
|
||||
const setValueByPath = (target: Record<string, any>, path: string, value: unknown): void => {
|
||||
const segments = path.split('.');
|
||||
const lastIndex = segments.length - 1;
|
||||
|
||||
let current: Record<string, any> = target;
|
||||
for (let index = 0; index < lastIndex; index++) {
|
||||
current = current[segments[index]];
|
||||
}
|
||||
|
||||
// 从 backendKey 提取 section(例如 'general.alwaysOnTop' -> 'general')
|
||||
const section = backendKey.split('.')[0] as ConfigSection;
|
||||
|
||||
await ConfigService.Set(backendKey, value);
|
||||
(state.config[section] as any)[key] = value;
|
||||
current[segments[lastIndex]] = value;
|
||||
};
|
||||
|
||||
// 只更新本地状态,不保存到后端
|
||||
const updateConfigLocal = <K extends ConfigKey>(key: K, value: any): void => {
|
||||
const backendKey = CONFIG_KEY_MAP[key];
|
||||
const section = backendKey.split('.')[0] as ConfigSection;
|
||||
(state.config[section] as any)[key] = value;
|
||||
const getValueByPath = (target: Record<string, any>, path: string): unknown => {
|
||||
return path.split('.').reduce<unknown>((current, segment) => (current as Record<string, any>)[segment], target);
|
||||
};
|
||||
|
||||
const updateConfig = async <K extends ConfigKey>(key: K, value: unknown): Promise<void> => {
|
||||
await ensureConfigLoaded();
|
||||
const path = CONFIG_KEY_MAP[key];
|
||||
await ConfigService.Set(path, value);
|
||||
setValueByPath(state.config as Record<string, any>, path, value);
|
||||
};
|
||||
|
||||
const updateConfigLocal = <K extends ConfigKey>(key: K, value: unknown): void => {
|
||||
setValueByPath(state.config as Record<string, any>, CONFIG_KEY_MAP[key], value);
|
||||
};
|
||||
|
||||
// 保存指定配置到后端
|
||||
const saveConfig = async <K extends ConfigKey>(key: K): Promise<void> => {
|
||||
const backendKey = CONFIG_KEY_MAP[key];
|
||||
const section = backendKey.split('.')[0] as ConfigSection;
|
||||
await ConfigService.Set(backendKey, (state.config[section] as any)[key]);
|
||||
const path = CONFIG_KEY_MAP[key];
|
||||
await ConfigService.Set(path, getValueByPath(state.config as Record<string, any>, path));
|
||||
};
|
||||
|
||||
// 加载配置
|
||||
const activeSyncKey = <G extends ConfigKey, L extends ConfigKey>(gitKey: G, localFSKey: L): G | L => (
|
||||
state.config.sync.target === SyncTarget.SyncTargetGit ? gitKey : localFSKey
|
||||
);
|
||||
|
||||
const initConfig = async (): Promise<void> => {
|
||||
if (state.isLoading) return;
|
||||
|
||||
state.isLoading = true;
|
||||
try {
|
||||
const appConfig = await ConfigService.GetConfig();
|
||||
|
||||
if (appConfig) {
|
||||
// 合并配置
|
||||
if (appConfig.general) Object.assign(state.config.general, appConfig.general);
|
||||
if (appConfig.editing) Object.assign(state.config.editing, appConfig.editing);
|
||||
if (appConfig.appearance) Object.assign(state.config.appearance, appConfig.appearance);
|
||||
if (appConfig.updates) Object.assign(state.config.updates, appConfig.updates);
|
||||
if (appConfig.backup) Object.assign(state.config.backup, appConfig.backup);
|
||||
if (appConfig.metadata) Object.assign(state.config.metadata, appConfig.metadata);
|
||||
}
|
||||
|
||||
applyConfig(await ConfigService.GetConfig());
|
||||
state.configLoaded = true;
|
||||
|
||||
} finally {
|
||||
state.isLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 重置配置
|
||||
const resetConfig = async (): Promise<void> => {
|
||||
if (state.isLoading) return;
|
||||
|
||||
state.isLoading = true;
|
||||
try {
|
||||
await ConfigService.ResetConfig();
|
||||
const appConfig = await ConfigService.GetConfig();
|
||||
if (appConfig) {
|
||||
state.config = JSON.parse(JSON.stringify(appConfig)) as AppConfig;
|
||||
}
|
||||
applyConfig(await ConfigService.GetConfig());
|
||||
state.configLoaded = true;
|
||||
} finally {
|
||||
state.isLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 辅助函数:限制数值范围
|
||||
const clampValue = (value: number, key: NumberConfigKey): number => {
|
||||
const limit = CONFIG_LIMITS[key];
|
||||
return ConfigUtils.clamp(value, limit.min, limit.max);
|
||||
};
|
||||
|
||||
// 计算属性
|
||||
const fontConfig = computed(() => ({
|
||||
fontSize: state.config.editing.fontSize,
|
||||
fontFamily: state.config.editing.fontFamily,
|
||||
@@ -122,7 +133,6 @@ export const useConfigStore = defineStore('config', () => {
|
||||
}));
|
||||
|
||||
return {
|
||||
// 状态
|
||||
config: computed(() => state.config),
|
||||
configLoaded: computed(() => state.configLoaded),
|
||||
isLoading: computed(() => state.isLoading),
|
||||
@@ -130,31 +140,25 @@ export const useConfigStore = defineStore('config', () => {
|
||||
fontConfig,
|
||||
tabConfig,
|
||||
|
||||
// 核心方法
|
||||
initConfig,
|
||||
resetConfig,
|
||||
|
||||
// 语言相关方法
|
||||
setLanguage: (value: LanguageType) => {
|
||||
updateConfig('language', value);
|
||||
setLanguage: async (value: LanguageType) => {
|
||||
await updateConfig('language', value);
|
||||
locale.value = value as any;
|
||||
},
|
||||
|
||||
// 主题相关方法
|
||||
setSystemTheme: (value: SystemThemeType) => updateConfig('systemTheme', value),
|
||||
setCurrentTheme: (value: string) => updateConfig('currentTheme', value),
|
||||
|
||||
// 字体大小操作
|
||||
setFontSize: async (value: number) => {
|
||||
await updateConfig('fontSize', clampValue(value, 'fontSize'));
|
||||
},
|
||||
increaseFontSize: async () => {
|
||||
const newValue = state.config.editing.fontSize + 1;
|
||||
await updateConfig('fontSize', clampValue(newValue, 'fontSize'));
|
||||
await updateConfig('fontSize', clampValue(state.config.editing.fontSize + 1, 'fontSize'));
|
||||
},
|
||||
decreaseFontSize: async () => {
|
||||
const newValue = state.config.editing.fontSize - 1;
|
||||
await updateConfig('fontSize', clampValue(newValue, 'fontSize'));
|
||||
await updateConfig('fontSize', clampValue(state.config.editing.fontSize - 1, 'fontSize'));
|
||||
},
|
||||
resetFontSize: async () => {
|
||||
await updateConfig('fontSize', CONFIG_LIMITS.fontSize.default);
|
||||
@@ -169,89 +173,63 @@ export const useConfigStore = defineStore('config', () => {
|
||||
await saveConfig('fontSize');
|
||||
},
|
||||
|
||||
// 字体操作
|
||||
setFontFamily: (value: string) => updateConfig('fontFamily', value),
|
||||
setFontWeight: (value: string) => updateConfig('fontWeight', value),
|
||||
|
||||
// 行高操作
|
||||
setLineHeight: async (value: number) => {
|
||||
await updateConfig('lineHeight', clampValue(value, 'lineHeight'));
|
||||
},
|
||||
|
||||
// Tab操作
|
||||
setEnableTabIndent: (value: boolean) => updateConfig('enableTabIndent', value),
|
||||
setTabSize: async (value: number) => {
|
||||
await updateConfig('tabSize', clampValue(value, 'tabSize'));
|
||||
},
|
||||
increaseTabSize: async () => {
|
||||
const newValue = state.config.editing.tabSize + 1;
|
||||
await updateConfig('tabSize', clampValue(newValue, 'tabSize'));
|
||||
await updateConfig('tabSize', clampValue(state.config.editing.tabSize + 1, 'tabSize'));
|
||||
},
|
||||
decreaseTabSize: async () => {
|
||||
const newValue = state.config.editing.tabSize - 1;
|
||||
await updateConfig('tabSize', clampValue(newValue, 'tabSize'));
|
||||
await updateConfig('tabSize', clampValue(state.config.editing.tabSize - 1, 'tabSize'));
|
||||
},
|
||||
toggleTabType: async () => {
|
||||
const values = CONFIG_LIMITS.tabType.values;
|
||||
const currentIndex = values.indexOf(state.config.editing.tabType as typeof values[number]);
|
||||
const nextIndex = (currentIndex + 1) % values.length;
|
||||
await updateConfig('tabType', values[nextIndex]);
|
||||
await updateConfig('tabType', values[(currentIndex + 1) % values.length]);
|
||||
},
|
||||
|
||||
// 窗口操作
|
||||
toggleAlwaysOnTop: async () => {
|
||||
await updateConfig('alwaysOnTop', !state.config.general.alwaysOnTop);
|
||||
await runtime.Window.SetAlwaysOnTop(state.config.general.alwaysOnTop);
|
||||
},
|
||||
setAlwaysOnTop: (value: boolean) => updateConfig('alwaysOnTop', value),
|
||||
|
||||
// 路径操作
|
||||
setDataPath: (value: string) => updateConfigLocal('dataPath', value),
|
||||
|
||||
// 保存配置相关方法
|
||||
setAutoSaveDelay: (value: number) => updateConfig('autoSaveDelay', value),
|
||||
|
||||
// 热键配置相关方法
|
||||
setEnableGlobalHotkey: (value: boolean) => updateConfig('enableGlobalHotkey', value),
|
||||
setGlobalHotkey: (hotkey: any) => updateConfig('globalHotkey', hotkey),
|
||||
|
||||
// 系统托盘配置相关方法
|
||||
setEnableSystemTray: (value: boolean) => updateConfig('enableSystemTray', value),
|
||||
|
||||
// 开机启动配置相关方法
|
||||
setStartAtLogin: async (value: boolean) => {
|
||||
await updateConfig('startAtLogin', value);
|
||||
await StartupService.SetEnabled(value);
|
||||
},
|
||||
|
||||
// 窗口吸附配置相关方法
|
||||
setEnableWindowSnap: (value: boolean) => updateConfig('enableWindowSnap', value),
|
||||
|
||||
// 加载动画配置相关方法
|
||||
setEnableLoadingAnimation: (value: boolean) => updateConfig('enableLoadingAnimation', value),
|
||||
|
||||
// 标签页配置相关方法
|
||||
setEnableTabs: (value: boolean) => updateConfig('enableTabs', value),
|
||||
|
||||
// 内存监视器配置相关方法
|
||||
setEnableMemoryMonitor: (value: boolean) => updateConfig('enableMemoryMonitor', value),
|
||||
|
||||
// 快捷键模式配置相关方法
|
||||
setKeymapMode: (value: any) => updateConfig('keymapMode', value),
|
||||
|
||||
// 更新配置相关方法
|
||||
setAutoUpdate: (value: boolean) => updateConfig('autoUpdate', value),
|
||||
|
||||
// 备份配置相关方法
|
||||
setEnableBackup: (value: boolean) => updateConfig('enabled', value),
|
||||
setAutoBackup: (value: boolean) => updateConfig('auto_backup', value),
|
||||
setRepoUrl: (value: string) => updateConfig('repo_url', value),
|
||||
setAuthMethod: (value: AuthMethod) => updateConfig('auth_method', value),
|
||||
setUsername: (value: string) => updateConfig('username', value),
|
||||
setPassword: (value: string) => updateConfig('password', value),
|
||||
setToken: (value: string) => updateConfig('token', value),
|
||||
setSshKeyPath: (value: string) => updateConfig('ssh_key_path', value),
|
||||
setSshKeyPassphrase: (value: string) => updateConfig('ssh_key_passphrase', value),
|
||||
setBackupInterval: (value: number) => updateConfig('backup_interval', value),
|
||||
setSyncTarget: (value: SyncTarget) => updateConfig('sync_target', value),
|
||||
setEnableSync: (value: boolean) => updateConfig(activeSyncKey('git_enabled', 'localfs_enabled'), value),
|
||||
setAutoSync: (value: boolean) => updateConfig(activeSyncKey('git_auto_sync', 'localfs_auto_sync'), value),
|
||||
setSyncInterval: (value: number) => updateConfig(
|
||||
activeSyncKey('git_sync_interval', 'localfs_sync_interval'),
|
||||
Math.max(1, value)
|
||||
),
|
||||
setRepoUrl: (value: string) => updateConfig('git_repo_url', value),
|
||||
setAuthMethod: (value: AuthMethod) => updateConfig('git_auth_method', value),
|
||||
setUsername: (value: string) => updateConfig('git_username', value),
|
||||
setPassword: (value: string) => updateConfig('git_password', value),
|
||||
setToken: (value: string) => updateConfig('git_token', value),
|
||||
setSshKeyPath: (value: string) => updateConfig('git_ssh_key_path', value),
|
||||
setSshKeyPassphrase: (value: string) => updateConfig('git_ssh_key_passphrase', value),
|
||||
setLocalFSRootPath: (value: string) => updateConfig('localfs_root_path', value),
|
||||
};
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { BackupService } from '@/../bindings/voidraft/internal/services';
|
||||
import { SyncService } from '@/../bindings/voidraft/internal/services';
|
||||
|
||||
export const useBackupStore = defineStore('backup', () => {
|
||||
export const useSyncStore = defineStore('sync', () => {
|
||||
const isSyncing = ref(false);
|
||||
|
||||
const sync = async (): Promise<void> => {
|
||||
@@ -11,11 +11,8 @@ export const useBackupStore = defineStore('backup', () => {
|
||||
}
|
||||
|
||||
isSyncing.value = true;
|
||||
|
||||
try {
|
||||
await BackupService.Sync();
|
||||
} catch (e) {
|
||||
throw e;
|
||||
await SyncService.Sync();
|
||||
} finally {
|
||||
isSyncing.value = false;
|
||||
}
|
||||
@@ -56,7 +56,6 @@ import yamlPrettierPlugin from "prettier/plugins/yaml";
|
||||
import goPrettierPlugin from "@/common/prettier/plugins/go";
|
||||
import sqlPrettierPlugin from "@/common/prettier/plugins/sql";
|
||||
import phpPrettierPlugin from "@/common/prettier/plugins/php";
|
||||
import javaPrettierPlugin from "@/common/prettier/plugins/java";
|
||||
import xmlPrettierPlugin from "@prettier/plugin-xml";
|
||||
import shellPrettierPlugin from "@/common/prettier/plugins/shell";
|
||||
import dockerfilePrettierPlugin from "@/common/prettier/plugins/docker";
|
||||
@@ -134,8 +133,12 @@ export const LANGUAGES: LanguageInfo[] = [
|
||||
plugins: [markdownPrettierPlugin]
|
||||
}),
|
||||
new LanguageInfo("java", "Java", javaLanguage.parser, ["java"], {
|
||||
parser: "java",
|
||||
plugins: [javaPrettierPlugin]
|
||||
parser: "clang-format",
|
||||
plugins: [clangPrettierPlugin],
|
||||
options: {
|
||||
filename: "Main.java",
|
||||
clangStyle: "Google"
|
||||
}
|
||||
}),
|
||||
new LanguageInfo("php", "PHP", phpLanguage.configure({top: "Program"}).parser, ["php"], {
|
||||
parser: "php",
|
||||
|
||||
@@ -18,7 +18,7 @@ const navItems = [
|
||||
{ id: 'general', icon: '⚙️', route: '/settings/general' },
|
||||
{ id: 'editing', icon: '✏️', route: '/settings/editing' },
|
||||
{ id: 'appearance', icon: '🎨', route: '/settings/appearance' },
|
||||
{ id: 'backupPage', icon: '🔗', route: '/settings/backup' },
|
||||
{ id: 'syncPage', icon: '🔗', route: '/settings/sync' },
|
||||
{ id: 'extensions', icon: '🧩', route: '/settings/extensions' },
|
||||
{ id: 'keyBindings', icon: '⌨️', route: '/settings/key-bindings' },
|
||||
{ id: 'updates', icon: '🔄', route: '/settings/updates' }
|
||||
|
||||
@@ -1,321 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {useConfigStore} from '@/stores/configStore';
|
||||
import {useBackupStore} from '@/stores/backupStore';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {computed} from 'vue';
|
||||
import SettingSection from '../components/SettingSection.vue';
|
||||
import SettingItem from '../components/SettingItem.vue';
|
||||
import ToggleSwitch from '../components/ToggleSwitch.vue';
|
||||
import {AuthMethod} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {DialogService} from '@/../bindings/voidraft/internal/services';
|
||||
import toast from '@/components/toast';
|
||||
|
||||
const {t} = useI18n();
|
||||
const configStore = useConfigStore();
|
||||
const backupStore = useBackupStore();
|
||||
|
||||
const authMethodOptions = computed(() => [
|
||||
{value: AuthMethod.Token, label: t('settings.backup.authMethods.token')},
|
||||
{value: AuthMethod.SSHKey, label: t('settings.backup.authMethods.sshKey')},
|
||||
{value: AuthMethod.UserPass, label: t('settings.backup.authMethods.userPass')}
|
||||
]);
|
||||
|
||||
const backupIntervalOptions = computed(() => [
|
||||
{value: 5, label: t('settings.backup.intervals.5min')},
|
||||
{value: 10, label: t('settings.backup.intervals.10min')},
|
||||
{value: 15, label: t('settings.backup.intervals.15min')},
|
||||
{value: 30, label: t('settings.backup.intervals.30min')},
|
||||
{value: 60, label: t('settings.backup.intervals.1hour')}
|
||||
]);
|
||||
|
||||
const selectSshKeyFile = async () => {
|
||||
const selectedPath = await DialogService.SelectFile();
|
||||
if (selectedPath.trim()) {
|
||||
configStore.setSshKeyPath(selectedPath.trim());
|
||||
}
|
||||
};
|
||||
|
||||
const handleSync = async () => {
|
||||
try {
|
||||
await backupStore.sync();
|
||||
toast.success('Sync successful');
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="settings-page">
|
||||
<!-- 基本设置 -->
|
||||
<SettingSection :title="t('settings.backup.basicSettings')">
|
||||
<SettingItem :title="t('settings.backup.enableBackup')">
|
||||
<ToggleSwitch
|
||||
:modelValue="configStore.config.backup.enabled"
|
||||
@update:modelValue="configStore.setEnableBackup"
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
:title="t('settings.backup.autoBackup')"
|
||||
:class="{ 'disabled-setting': !configStore.config.backup.enabled }"
|
||||
>
|
||||
<ToggleSwitch
|
||||
:modelValue="configStore.config.backup.auto_backup"
|
||||
@update:modelValue="configStore.setAutoBackup"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
:title="t('settings.backup.backupInterval')"
|
||||
:class="{ 'disabled-setting': !configStore.config.backup.enabled || !configStore.config.backup.auto_backup }"
|
||||
>
|
||||
<select
|
||||
class="backup-interval-select"
|
||||
:value="configStore.config.backup.backup_interval"
|
||||
@change="(e) => configStore.setBackupInterval(Number((e.target as HTMLSelectElement).value))"
|
||||
:disabled="!configStore.config.backup.enabled || !configStore.config.backup.auto_backup"
|
||||
>
|
||||
<option v-for="option in backupIntervalOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</SettingItem>
|
||||
</SettingSection>
|
||||
|
||||
<!-- 仓库配置 -->
|
||||
<SettingSection :title="t('settings.backup.repositoryConfig')">
|
||||
<SettingItem :title="t('settings.backup.repoUrl')">
|
||||
<input
|
||||
type="text"
|
||||
class="repo-url-input"
|
||||
:value="configStore.config.backup.repo_url"
|
||||
@input="(e) => configStore.setRepoUrl((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.backup.repoUrlPlaceholder')"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
</SettingSection>
|
||||
|
||||
<!-- 认证配置 -->
|
||||
<SettingSection :title="t('settings.backup.authConfig')">
|
||||
<SettingItem :title="t('settings.backup.authMethod')">
|
||||
<select
|
||||
class="auth-method-select"
|
||||
:value="configStore.config.backup.auth_method"
|
||||
@change="(e) => configStore.setAuthMethod((e.target as HTMLSelectElement).value as AuthMethod)"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
>
|
||||
<option v-for="option in authMethodOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</SettingItem>
|
||||
|
||||
<!-- 用户名密码认证 -->
|
||||
<template v-if="configStore.config.backup.auth_method === AuthMethod.UserPass">
|
||||
<SettingItem :title="t('settings.backup.username')">
|
||||
<input
|
||||
type="text"
|
||||
class="username-input"
|
||||
:value="configStore.config.backup.username"
|
||||
@input="(e) => configStore.setUsername((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.backup.usernamePlaceholder')"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem :title="t('settings.backup.password')">
|
||||
<input
|
||||
type="password"
|
||||
class="password-input"
|
||||
:value="configStore.config.backup.password"
|
||||
@input="(e) => configStore.setPassword((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.backup.passwordPlaceholder')"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
</template>
|
||||
|
||||
<!-- 访问令牌认证 -->
|
||||
<template v-if="configStore.config.backup.auth_method === AuthMethod.Token">
|
||||
<SettingItem :title="t('settings.backup.token')">
|
||||
<input
|
||||
type="password"
|
||||
class="token-input"
|
||||
:value="configStore.config.backup.token"
|
||||
@input="(e) => configStore.setToken((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.backup.tokenPlaceholder')"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
</template>
|
||||
|
||||
<!-- SSH密钥认证 -->
|
||||
<template v-if="configStore.config.backup.auth_method === AuthMethod.SSHKey">
|
||||
<SettingItem :title="t('settings.backup.sshKeyPath')">
|
||||
<input
|
||||
type="text"
|
||||
class="ssh-key-path-input"
|
||||
:value="configStore.config.backup.ssh_key_path"
|
||||
:placeholder="t('settings.backup.sshKeyPathPlaceholder')"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
readonly
|
||||
@click="configStore.config.backup.enabled && selectSshKeyFile()"
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem :title="t('settings.backup.sshKeyPassphrase')">
|
||||
<input
|
||||
type="password"
|
||||
class="ssh-passphrase-input"
|
||||
:value="configStore.config.backup.ssh_key_passphrase"
|
||||
@input="(e) => configStore.setSshKeyPassphrase((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.backup.sshKeyPassphrasePlaceholder')"
|
||||
:disabled="!configStore.config.backup.enabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
</template>
|
||||
</SettingSection>
|
||||
|
||||
<!-- 备份操作 -->
|
||||
<SettingSection :title="t('settings.backup.backupOperations')">
|
||||
<SettingItem :title="t('settings.backup.syncToRemote')">
|
||||
<button
|
||||
class="sync-button"
|
||||
@click="handleSync"
|
||||
:disabled="!configStore.config.backup.enabled || !configStore.config.backup.repo_url || backupStore.isSyncing"
|
||||
:class="{ 'syncing': backupStore.isSyncing }"
|
||||
>
|
||||
<span v-if="backupStore.isSyncing" class="loading-spinner"></span>
|
||||
{{ backupStore.isSyncing ? t('settings.backup.syncing') : t('settings.backup.actions.sync') }}
|
||||
</button>
|
||||
</SettingItem>
|
||||
</SettingSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// 统一的输入控件样式
|
||||
.repo-url-input,
|
||||
.branch-input,
|
||||
.username-input,
|
||||
.password-input,
|
||||
.token-input,
|
||||
.ssh-key-path-input,
|
||||
.ssh-passphrase-input,
|
||||
.backup-interval-select,
|
||||
.auth-method-select {
|
||||
width: 50%;
|
||||
min-width: 200px;
|
||||
padding: 10px 12px;
|
||||
background-color: var(--settings-input-bg);
|
||||
border: 1px solid var(--settings-input-border);
|
||||
border-radius: 4px;
|
||||
color: var(--settings-text);
|
||||
font-size: 12px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #4a9eff;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background-color: var(--settings-hover);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--settings-text-secondary);
|
||||
}
|
||||
|
||||
&[readonly]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--settings-hover);
|
||||
background-color: var(--settings-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选择框特有样式
|
||||
.backup-interval-select,
|
||||
.auth-method-select {
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999999' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 8px center;
|
||||
background-size: 16px;
|
||||
padding-right: 30px;
|
||||
|
||||
option {
|
||||
background-color: var(--settings-input-bg);
|
||||
color: var(--settings-text);
|
||||
}
|
||||
}
|
||||
|
||||
// 按钮样式
|
||||
.sync-button {
|
||||
padding: 8px 16px;
|
||||
background-color: var(--settings-input-bg);
|
||||
border: 1px solid var(--settings-input-border);
|
||||
border-radius: 4px;
|
||||
color: var(--settings-text);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: var(--settings-hover);
|
||||
border-color: var(--settings-border);
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: var(--settings-text);
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
&.syncing {
|
||||
background-color: #2196f3;
|
||||
border-color: #2196f3;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
// 禁用状态
|
||||
.disabled-setting {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// 加载动画
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
368
frontend/src/views/settings/pages/SyncPage.vue
Normal file
368
frontend/src/views/settings/pages/SyncPage.vue
Normal file
@@ -0,0 +1,368 @@
|
||||
<script setup lang="ts">
|
||||
import {useConfigStore} from '@/stores/configStore';
|
||||
import {useSyncStore} from '@/stores/syncStore';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {computed} from 'vue';
|
||||
import SettingSection from '../components/SettingSection.vue';
|
||||
import SettingItem from '../components/SettingItem.vue';
|
||||
import ToggleSwitch from '../components/ToggleSwitch.vue';
|
||||
import {AuthMethod, SyncTarget} from '@/../bindings/voidraft/internal/models/models';
|
||||
import {DialogService} from '@/../bindings/voidraft/internal/services';
|
||||
import toast from '@/components/toast';
|
||||
|
||||
const {t} = useI18n();
|
||||
const configStore = useConfigStore();
|
||||
const syncStore = useSyncStore();
|
||||
|
||||
const syncConfig = computed(() => configStore.config.sync);
|
||||
const isGitTarget = computed(() => syncConfig.value.target === SyncTarget.SyncTargetGit);
|
||||
const isSyncEnabled = computed(() => isGitTarget.value ? syncConfig.value.git.enabled : syncConfig.value.localfs.enabled);
|
||||
const isAutoSyncEnabled = computed(() => isGitTarget.value ? syncConfig.value.git.auto_sync : syncConfig.value.localfs.auto_sync);
|
||||
const currentInterval = computed(() => isGitTarget.value ? syncConfig.value.git.sync_interval : syncConfig.value.localfs.sync_interval);
|
||||
|
||||
const authMethodOptions = computed(() => [
|
||||
{value: AuthMethod.Token, label: t('settings.sync.authMethods.token')},
|
||||
{value: AuthMethod.SSHKey, label: t('settings.sync.authMethods.sshKey')},
|
||||
{value: AuthMethod.UserPass, label: t('settings.sync.authMethods.userPass')}
|
||||
]);
|
||||
|
||||
const syncTargetOptions = computed(() => [
|
||||
{value: SyncTarget.SyncTargetGit, label: t('settings.sync.targetTypes.git')},
|
||||
{value: SyncTarget.SyncTargetLocalFS, label: t('settings.sync.targetTypes.localfs')}
|
||||
]);
|
||||
|
||||
const syncIntervalOptions = computed(() => [
|
||||
{value: 5, label: t('settings.sync.intervals.5min')},
|
||||
{value: 10, label: t('settings.sync.intervals.10min')},
|
||||
{value: 15, label: t('settings.sync.intervals.15min')},
|
||||
{value: 30, label: t('settings.sync.intervals.30min')},
|
||||
{value: 60, label: t('settings.sync.intervals.1hour')}
|
||||
]);
|
||||
|
||||
const canSync = computed(() => {
|
||||
if (!isSyncEnabled.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isGitTarget.value
|
||||
? Boolean(syncConfig.value.git.repo_url.trim())
|
||||
: Boolean(syncConfig.value.localfs.root_path.trim());
|
||||
});
|
||||
|
||||
const selectSshKeyFile = async () => {
|
||||
const selectedPath = await DialogService.SelectFile();
|
||||
if (selectedPath.trim()) {
|
||||
await configStore.setSshKeyPath(selectedPath.trim());
|
||||
}
|
||||
};
|
||||
|
||||
const selectLocalFSDirectory = async () => {
|
||||
const selectedPath = await DialogService.SelectDirectory();
|
||||
if (selectedPath.trim()) {
|
||||
await configStore.setLocalFSRootPath(selectedPath.trim());
|
||||
}
|
||||
};
|
||||
|
||||
const handleSync = async () => {
|
||||
try {
|
||||
await syncStore.sync();
|
||||
toast.success(t('settings.sync.syncSuccess'));
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="settings-page">
|
||||
<SettingSection :title="t('settings.sync.basicSettings')">
|
||||
<SettingItem :title="t('settings.sync.enableSync')">
|
||||
<ToggleSwitch
|
||||
:modelValue="isSyncEnabled"
|
||||
@update:modelValue="configStore.setEnableSync"
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem :title="t('settings.sync.targetType')">
|
||||
<select
|
||||
class="target-type-select"
|
||||
:value="syncConfig.target"
|
||||
@change="(e) => configStore.setSyncTarget((e.target as HTMLSelectElement).value as SyncTarget)"
|
||||
>
|
||||
<option v-for="option in syncTargetOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
:title="t('settings.sync.autoSync')"
|
||||
:class="{ 'disabled-setting': !isSyncEnabled }"
|
||||
>
|
||||
<ToggleSwitch
|
||||
:modelValue="isAutoSyncEnabled"
|
||||
@update:modelValue="configStore.setAutoSync"
|
||||
:disabled="!isSyncEnabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
:title="t('settings.sync.syncInterval')"
|
||||
:class="{ 'disabled-setting': !isSyncEnabled || !isAutoSyncEnabled }"
|
||||
>
|
||||
<select
|
||||
class="sync-interval-select"
|
||||
:value="currentInterval"
|
||||
@change="(e) => configStore.setSyncInterval(Number((e.target as HTMLSelectElement).value))"
|
||||
:disabled="!isSyncEnabled || !isAutoSyncEnabled"
|
||||
>
|
||||
<option v-for="option in syncIntervalOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</SettingItem>
|
||||
</SettingSection>
|
||||
|
||||
<SettingSection :title="isGitTarget ? t('settings.sync.repositoryConfig') : t('settings.sync.storageConfig')">
|
||||
<template v-if="isGitTarget">
|
||||
<SettingItem :title="t('settings.sync.repoUrl')">
|
||||
<input
|
||||
type="text"
|
||||
class="repo-url-input"
|
||||
:value="syncConfig.git.repo_url"
|
||||
@input="(e) => configStore.setRepoUrl((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.sync.repoUrlPlaceholder')"
|
||||
:disabled="!isSyncEnabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<SettingItem :title="t('settings.sync.localfsRootPath')">
|
||||
<input
|
||||
type="text"
|
||||
class="localfs-root-path-input"
|
||||
:value="syncConfig.localfs.root_path"
|
||||
:placeholder="t('settings.sync.localfsRootPathPlaceholder')"
|
||||
:disabled="!isSyncEnabled"
|
||||
readonly
|
||||
@click="isSyncEnabled && selectLocalFSDirectory()"
|
||||
/>
|
||||
</SettingItem>
|
||||
</template>
|
||||
</SettingSection>
|
||||
|
||||
<SettingSection v-if="isGitTarget" :title="t('settings.sync.authConfig')">
|
||||
<SettingItem :title="t('settings.sync.authMethod')">
|
||||
<select
|
||||
class="auth-method-select"
|
||||
:value="syncConfig.git.auth_method"
|
||||
@change="(e) => configStore.setAuthMethod((e.target as HTMLSelectElement).value as AuthMethod)"
|
||||
:disabled="!isSyncEnabled"
|
||||
>
|
||||
<option v-for="option in authMethodOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</SettingItem>
|
||||
|
||||
<template v-if="syncConfig.git.auth_method === AuthMethod.UserPass">
|
||||
<SettingItem :title="t('settings.sync.username')">
|
||||
<input
|
||||
type="text"
|
||||
class="username-input"
|
||||
:value="syncConfig.git.username ?? ''"
|
||||
@input="(e) => configStore.setUsername((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.sync.usernamePlaceholder')"
|
||||
:disabled="!isSyncEnabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem :title="t('settings.sync.password')">
|
||||
<input
|
||||
type="password"
|
||||
class="password-input"
|
||||
:value="syncConfig.git.password ?? ''"
|
||||
@input="(e) => configStore.setPassword((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.sync.passwordPlaceholder')"
|
||||
:disabled="!isSyncEnabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
</template>
|
||||
|
||||
<template v-if="syncConfig.git.auth_method === AuthMethod.Token">
|
||||
<SettingItem :title="t('settings.sync.token')">
|
||||
<input
|
||||
type="password"
|
||||
class="token-input"
|
||||
:value="syncConfig.git.token ?? ''"
|
||||
@input="(e) => configStore.setToken((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.sync.tokenPlaceholder')"
|
||||
:disabled="!isSyncEnabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
</template>
|
||||
|
||||
<template v-if="syncConfig.git.auth_method === AuthMethod.SSHKey">
|
||||
<SettingItem :title="t('settings.sync.sshKeyPath')">
|
||||
<input
|
||||
type="text"
|
||||
class="ssh-key-path-input"
|
||||
:value="syncConfig.git.ssh_key_path ?? ''"
|
||||
:placeholder="t('settings.sync.sshKeyPathPlaceholder')"
|
||||
:disabled="!isSyncEnabled"
|
||||
readonly
|
||||
@click="isSyncEnabled && selectSshKeyFile()"
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem :title="t('settings.sync.sshKeyPassphrase')">
|
||||
<input
|
||||
type="password"
|
||||
class="ssh-passphrase-input"
|
||||
:value="syncConfig.git.ssh_key_passphrase ?? ''"
|
||||
@input="(e) => configStore.setSshKeyPassphrase((e.target as HTMLInputElement).value)"
|
||||
:placeholder="t('settings.sync.sshKeyPassphrasePlaceholder')"
|
||||
:disabled="!isSyncEnabled"
|
||||
/>
|
||||
</SettingItem>
|
||||
</template>
|
||||
</SettingSection>
|
||||
|
||||
<SettingSection :title="t('settings.sync.syncOperations')">
|
||||
<SettingItem :title="t('settings.sync.syncToTarget')">
|
||||
<button
|
||||
class="sync-button"
|
||||
@click="handleSync"
|
||||
:disabled="!canSync || syncStore.isSyncing"
|
||||
:class="{ 'syncing': syncStore.isSyncing }"
|
||||
>
|
||||
<span v-if="syncStore.isSyncing" class="loading-spinner"></span>
|
||||
{{ syncStore.isSyncing ? t('settings.sync.syncing') : t('settings.sync.actions.sync') }}
|
||||
</button>
|
||||
</SettingItem>
|
||||
</SettingSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.repo-url-input,
|
||||
.localfs-root-path-input,
|
||||
.username-input,
|
||||
.password-input,
|
||||
.token-input,
|
||||
.ssh-key-path-input,
|
||||
.ssh-passphrase-input,
|
||||
.sync-interval-select,
|
||||
.auth-method-select,
|
||||
.target-type-select {
|
||||
width: 50%;
|
||||
min-width: 200px;
|
||||
padding: 10px 12px;
|
||||
background-color: var(--settings-input-bg);
|
||||
border: 1px solid var(--settings-input-border);
|
||||
border-radius: 4px;
|
||||
color: var(--settings-text);
|
||||
font-size: 12px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #4a9eff;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background-color: var(--settings-hover);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--settings-text-secondary);
|
||||
}
|
||||
|
||||
&[readonly]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--settings-hover);
|
||||
background-color: var(--settings-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sync-interval-select,
|
||||
.auth-method-select,
|
||||
.target-type-select {
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999999' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 8px center;
|
||||
background-size: 16px;
|
||||
padding-right: 30px;
|
||||
|
||||
option {
|
||||
background-color: var(--settings-input-bg);
|
||||
color: var(--settings-text);
|
||||
}
|
||||
}
|
||||
|
||||
.sync-button {
|
||||
padding: 8px 16px;
|
||||
background-color: var(--settings-input-bg);
|
||||
border: 1px solid var(--settings-input-border);
|
||||
border-radius: 4px;
|
||||
color: var(--settings-text);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: var(--settings-hover);
|
||||
border-color: var(--settings-border);
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: var(--settings-text);
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
&.syncing {
|
||||
background-color: #2196f3;
|
||||
border-color: #2196f3;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled-setting {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
61
go.mod
61
go.mod
@@ -1,54 +1,54 @@
|
||||
module voidraft
|
||||
|
||||
go 1.25
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
entgo.io/ent v0.14.5
|
||||
entgo.io/ent v0.14.6
|
||||
github.com/creativeprojects/go-selfupdate v1.5.2
|
||||
github.com/go-git/go-git/v5 v5.16.4
|
||||
github.com/go-git/go-git/v5 v5.17.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/knadh/koanf/parsers/json v1.0.0
|
||||
github.com/knadh/koanf/providers/file v1.2.1
|
||||
github.com/knadh/koanf/providers/structs v1.0.0
|
||||
github.com/knadh/koanf/v2 v2.3.0
|
||||
github.com/mattn/go-sqlite3 v1.14.33
|
||||
github.com/knadh/koanf/v2 v2.3.4
|
||||
github.com/mattn/go-sqlite3 v1.14.37
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.55
|
||||
golang.org/x/net v0.48.0
|
||||
golang.org/x/sys v0.39.0
|
||||
golang.org/x/text v0.32.0
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.74
|
||||
golang.org/x/net v0.52.0
|
||||
golang.org/x/sys v0.42.0
|
||||
golang.org/x/text v0.35.0
|
||||
resty.dev/v3 v3.0.0-beta.6
|
||||
)
|
||||
|
||||
require (
|
||||
ariga.io/atlas v1.0.0 // indirect
|
||||
code.gitea.io/sdk/gitea v0.22.1 // indirect
|
||||
ariga.io/atlas v1.1.0 // indirect
|
||||
code.gitea.io/sdk/gitea v0.24.1 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
|
||||
github.com/42wim/httpsig v1.2.3 // indirect
|
||||
github.com/42wim/httpsig v1.2.4 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.4.1 // indirect
|
||||
github.com/adrg/xdg v0.5.3 // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||
github.com/cloudflare/circl v1.6.2 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/coder/websocket v1.8.14 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.7.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.8.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/inflect v0.21.5 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
@@ -60,12 +60,12 @@ require (
|
||||
github.com/hashicorp/hcl/v2 v2.24.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
|
||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.6.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.2 // indirect
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||
github.com/leaanthony/u v1.1.1 // indirect
|
||||
github.com/lmittmann/tint v1.1.2 // indirect
|
||||
github.com/lmittmann/tint v1.1.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
@@ -75,23 +75,22 @@ require (
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/samber/lo v1.52.0 // indirect
|
||||
github.com/samber/lo v1.53.0 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/skeema/knownhosts v1.3.2 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.23 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/zclconf/go-cty v1.17.0 // indirect
|
||||
github.com/zclconf/go-cty v1.18.0 // indirect
|
||||
github.com/zclconf/go-cty-yaml v1.2.0 // indirect
|
||||
gitlab.com/gitlab-org/api/client-go v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect
|
||||
golang.org/x/image v0.34.0 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
gitlab.com/gitlab-org/api/client-go v1.46.0 // indirect
|
||||
golang.org/x/crypto v0.49.0 // indirect
|
||||
golang.org/x/image v0.38.0 // indirect
|
||||
golang.org/x/mod v0.34.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.43.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
144
go.sum
144
go.sum
@@ -1,15 +1,15 @@
|
||||
ariga.io/atlas v1.0.0 h1:v9DQH49xK+SM2kKwk4OQBjfz/KNRMUR+pvDiEIxSJto=
|
||||
ariga.io/atlas v1.0.0/go.mod h1:esBbk3F+pi/mM2PvbCymDm+kWhaOk4PaaiegQdNELk8=
|
||||
code.gitea.io/sdk/gitea v0.22.1 h1:7K05KjRORyTcTYULQ/AwvlVS6pawLcWyXZcTr7gHFyA=
|
||||
code.gitea.io/sdk/gitea v0.22.1/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM=
|
||||
ariga.io/atlas v1.1.0 h1:Dk9Xemh6pr5RogNCsFylf/9ozhSPWDqzHb8EkR2rA78=
|
||||
ariga.io/atlas v1.1.0/go.mod h1:esBbk3F+pi/mM2PvbCymDm+kWhaOk4PaaiegQdNELk8=
|
||||
code.gitea.io/sdk/gitea v0.24.1 h1:hpaqcdGcBmfMpV7JSbBJVwE99qo+WqGreJYKrDKEyW8=
|
||||
code.gitea.io/sdk/gitea v0.24.1/go.mod h1:5/77BL3sHneCMEiZaMT9lfTvnnibsYxyO48mceCF3qA=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
entgo.io/ent v0.14.5 h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=
|
||||
entgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U=
|
||||
entgo.io/ent v0.14.6 h1:/f2696BpwuWAEEG6PVGWflg6+Inrpq4pRWuNlWz/Skk=
|
||||
entgo.io/ent v0.14.6/go.mod h1:z46QBUdGC+BATwsedbDuREfSS0oSCV+csdEYlL4p73s=
|
||||
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA=
|
||||
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc=
|
||||
github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
|
||||
github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM=
|
||||
github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU=
|
||||
github.com/42wim/httpsig v1.2.4/go.mod h1:yKsYfSyTBEohkPik224QPFylmzEBtda/kjyIAJjh3ps=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
@@ -17,8 +17,8 @@ github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
|
||||
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
@@ -33,16 +33,10 @@ github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
|
||||
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
|
||||
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
|
||||
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
|
||||
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
|
||||
github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||
github.com/creativeprojects/go-selfupdate v1.5.2 h1:3KR3JLrq70oplb9yZzbmJ89qRP78D1AN/9u+l3k0LJ4=
|
||||
github.com/creativeprojects/go-selfupdate v1.5.2/go.mod h1:BCOuwIl1dRRCmPNRPH0amULeZqayhKyY2mH/h4va7Dk=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||
@@ -52,8 +46,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
|
||||
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
|
||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
@@ -70,12 +64,12 @@ github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
||||
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
|
||||
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
|
||||
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
|
||||
github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
|
||||
github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
github.com/go-git/go-git/v5 v5.17.0 h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM=
|
||||
github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI=
|
||||
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
|
||||
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
@@ -84,10 +78,8 @@ github.com/go-openapi/inflect v0.21.5 h1:M2RCq6PPS3YbIaL7CXosGL3BbzAcmfBAT0nC3Yf
|
||||
github.com/go-openapi/inflect v0.21.5/go.mod h1:GypUyi6bU880NYurWaEH2CmH84zFDNd+EhhmzroHmB4=
|
||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
||||
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
@@ -101,6 +93,8 @@ github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfh
|
||||
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/graph-gophers/graphql-go v1.9.0 h1:yu0ucKHLc5qGpRwLYKIWtr9bOoxovkWasuBrPQwlHls=
|
||||
github.com/graph-gophers/graphql-go v1.9.0/go.mod h1:23olKZ7duEvHlF/2ELEoSZaY1aNPfShjP782SOoNTyM=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
@@ -115,10 +109,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
|
||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
||||
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
|
||||
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
|
||||
@@ -129,8 +121,8 @@ github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP
|
||||
github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
|
||||
github.com/knadh/koanf/providers/structs v1.0.0 h1:DznjB7NQykhqCar2LvNug3MuxEQsZ5KvfgMbio+23u4=
|
||||
github.com/knadh/koanf/providers/structs v1.0.0/go.mod h1:kjo5TFtgpaZORlpoJqcbeLowM2cINodv8kX+oFAeQ1w=
|
||||
github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM=
|
||||
github.com/knadh/koanf/v2 v2.3.0/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
|
||||
github.com/knadh/koanf/v2 v2.3.4 h1:fnynNSDlujWE+v83hAp8wKr/cdoxHLO0629SN+U8Urc=
|
||||
github.com/knadh/koanf/v2 v2.3.4/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -142,8 +134,8 @@ github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
||||
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
|
||||
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/lmittmann/tint v1.1.3 h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I=
|
||||
github.com/lmittmann/tint v1.1.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
@@ -151,18 +143,14 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
|
||||
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg=
|
||||
github.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
||||
@@ -178,8 +166,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
|
||||
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
@@ -190,47 +178,43 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0=
|
||||
github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.55 h1:Wxwxc4EN6axDAvH/O5n3uoZQ+XRY/HQZ5rMdn0npq78=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.55/go.mod h1:AyH9vRcseorpL3p5XvxKgK0Lv/agJ7pTmcPdy25xZPo=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.74 h1:wRm1EiDQtxDisXk46NtpiBH90STwfKp36NrTDwOEdxw=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.74/go.mod h1:4saK4A4K9970X+X7RkMwP2lyGbLogcUz54wVeq4C/V8=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=
|
||||
github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=
|
||||
github.com/zclconf/go-cty v1.18.0 h1:pJ8+HNI4gFoyRNqVE37wWbJWVw43BZczFo7KUoRczaA=
|
||||
github.com/zclconf/go-cty v1.18.0/go.mod h1:qpnV6EDNgC1sns/AleL1fvatHw72j+S+nS+MJ+T2CSg=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
||||
github.com/zclconf/go-cty-yaml v1.2.0 h1:GDyL4+e/Qe/S0B7YaecMLbVvAR/Mp21CXMOSiCTOi1M=
|
||||
github.com/zclconf/go-cty-yaml v1.2.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
|
||||
gitlab.com/gitlab-org/api/client-go v1.10.0 h1:VlB9gXQdG6w643lH53VduUHVnCWQG5Ty86VbXnyi70A=
|
||||
gitlab.com/gitlab-org/api/client-go v1.10.0/go.mod h1:U3QKvjbT1J1FrgLsA7w/XlhoBIendUqB4o3/Ht3UhEQ=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
gitlab.com/gitlab-org/api/client-go v1.46.0 h1:YxBWFZIFYKcGESCb9fpkwzouo+apyB9pr/XTWzNoL24=
|
||||
gitlab.com/gitlab-org/api/client-go v1.46.0/go.mod h1:FtgyU6g2HS5+fMhw6nLK96GBEEBx5MzntOiJWfIaiN8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
|
||||
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
||||
golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
|
||||
golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -242,21 +226,21 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
||||
@@ -110,7 +110,7 @@ type UpdatesConfig struct {
|
||||
Github GithubConfig `json:"github"` // GitHub配置
|
||||
}
|
||||
|
||||
// Git备份相关类型定义
|
||||
// Git同步相关类型定义
|
||||
type (
|
||||
// AuthMethod 定义Git认证方式
|
||||
AuthMethod string
|
||||
@@ -123,9 +123,21 @@ const (
|
||||
UserPass AuthMethod = "user_pass"
|
||||
)
|
||||
|
||||
// GitBackupConfig Git备份配置
|
||||
type GitBackupConfig struct {
|
||||
// SyncTarget 定义当前可选择的同步目标。
|
||||
type SyncTarget string
|
||||
|
||||
const (
|
||||
// SyncTargetGit 表示 Git 同步。
|
||||
SyncTargetGit SyncTarget = "git"
|
||||
// SyncTargetLocalFS 表示本地文件系统同步。
|
||||
SyncTargetLocalFS SyncTarget = "localfs"
|
||||
)
|
||||
|
||||
// GitSyncConfig 描述 Git 同步配置。
|
||||
type GitSyncConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
AutoSync bool `json:"auto_sync"`
|
||||
SyncInterval int `json:"sync_interval"` // 分钟
|
||||
RepoURL string `json:"repo_url"`
|
||||
AuthMethod AuthMethod `json:"auth_method"`
|
||||
Username string `json:"username,omitempty"`
|
||||
@@ -133,8 +145,21 @@ type GitBackupConfig struct {
|
||||
Token string `json:"token,omitempty"`
|
||||
SSHKeyPath string `json:"ssh_key_path,omitempty"`
|
||||
SSHKeyPass string `json:"ssh_key_passphrase,omitempty"`
|
||||
BackupInterval int `json:"backup_interval"` // 分钟
|
||||
AutoBackup bool `json:"auto_backup"`
|
||||
}
|
||||
|
||||
// LocalFSSyncConfig 描述本地文件系统同步配置。
|
||||
type LocalFSSyncConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
AutoSync bool `json:"auto_sync"`
|
||||
SyncInterval int `json:"sync_interval"` // 分钟
|
||||
RootPath string `json:"root_path"`
|
||||
}
|
||||
|
||||
// SyncConfig 描述同步模块配置。
|
||||
type SyncConfig struct {
|
||||
Target SyncTarget `json:"target"`
|
||||
Git GitSyncConfig `json:"git"`
|
||||
LocalFS LocalFSSyncConfig `json:"localfs"`
|
||||
}
|
||||
|
||||
// AppConfig 应用配置 - 按照前端设置页面分类组织
|
||||
@@ -143,7 +168,7 @@ type AppConfig struct {
|
||||
Editing EditingConfig `json:"editing"` // 编辑设置
|
||||
Appearance AppearanceConfig `json:"appearance"` // 外观设置
|
||||
Updates UpdatesConfig `json:"updates"` // 更新设置
|
||||
Backup GitBackupConfig `json:"backup"` // Git备份设置
|
||||
Sync SyncConfig `json:"sync"` // 同步设置
|
||||
Metadata ConfigMetadata `json:"metadata"` // 配置元数据
|
||||
}
|
||||
|
||||
@@ -208,16 +233,26 @@ func NewDefaultAppConfig() *AppConfig {
|
||||
Repo: "voidraft",
|
||||
},
|
||||
},
|
||||
Backup: GitBackupConfig{
|
||||
Sync: SyncConfig{
|
||||
Target: SyncTargetGit,
|
||||
Git: GitSyncConfig{
|
||||
Enabled: false,
|
||||
AutoSync: false,
|
||||
SyncInterval: 60,
|
||||
RepoURL: "",
|
||||
AuthMethod: UserPass,
|
||||
Username: "",
|
||||
Password: "",
|
||||
Token: "",
|
||||
SSHKeyPath: "",
|
||||
BackupInterval: 60,
|
||||
AutoBackup: false,
|
||||
SSHKeyPass: "",
|
||||
},
|
||||
LocalFS: LocalFSSyncConfig{
|
||||
Enabled: false,
|
||||
AutoSync: false,
|
||||
SyncInterval: 60,
|
||||
RootPath: "",
|
||||
},
|
||||
},
|
||||
Metadata: ConfigMetadata{
|
||||
LastUpdated: time.Now().Format(time.RFC3339),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,7 @@ type ServiceManager struct {
|
||||
badgeService *dock.DockService
|
||||
notificationService *notifications.NotificationService
|
||||
testService *TestService // 测试服务(仅开发环境)
|
||||
BackupService *BackupService
|
||||
SyncService *SyncService
|
||||
httpClientService *HttpClientService // HTTP客户端服务
|
||||
logger *log.LogService
|
||||
}
|
||||
@@ -95,8 +95,8 @@ func NewServiceManager() *ServiceManager {
|
||||
// 初始化主题服务
|
||||
themeService := NewThemeService(databaseService, logger)
|
||||
|
||||
// 初始化备份服务
|
||||
backupService := NewBackupService(configService, databaseService, logger)
|
||||
// 初始化同步服务
|
||||
syncService := NewSyncService(configService, databaseService, logger)
|
||||
|
||||
// 初始化HTTP客户端服务
|
||||
httpClientService := NewHttpClientService(logger)
|
||||
@@ -124,7 +124,7 @@ func NewServiceManager() *ServiceManager {
|
||||
badgeService: badgeService,
|
||||
notificationService: notificationService,
|
||||
testService: testService,
|
||||
BackupService: backupService,
|
||||
SyncService: syncService,
|
||||
httpClientService: httpClientService,
|
||||
logger: logger,
|
||||
}
|
||||
@@ -150,7 +150,7 @@ func (sm *ServiceManager) GetServices() []application.Service {
|
||||
application.NewService(sm.badgeService),
|
||||
application.NewService(sm.notificationService),
|
||||
application.NewService(sm.testService),
|
||||
application.NewService(sm.BackupService),
|
||||
application.NewService(sm.SyncService),
|
||||
application.NewService(sm.httpClientService),
|
||||
}
|
||||
return services
|
||||
|
||||
242
internal/services/sync_service.go
Normal file
242
internal/services/sync_service.go
Normal file
@@ -0,0 +1,242 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"time"
|
||||
"voidraft/internal/common/helper"
|
||||
"voidraft/internal/models"
|
||||
"voidraft/internal/syncer"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||
)
|
||||
|
||||
const (
|
||||
syncDir = "sync"
|
||||
localFSHeadKey = "head.json"
|
||||
)
|
||||
|
||||
// SyncService 提供应用层同步服务入口。
|
||||
type SyncService struct {
|
||||
configService *ConfigService
|
||||
dbService *DatabaseService
|
||||
logger *log.LogService
|
||||
app *syncer.App
|
||||
cancelObservers []helper.CancelFunc
|
||||
}
|
||||
|
||||
// NewSyncService 创建新的同步服务实例。
|
||||
func NewSyncService(configService *ConfigService, dbService *DatabaseService, logger *log.LogService) *SyncService {
|
||||
return &SyncService{
|
||||
configService: configService,
|
||||
dbService: dbService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceStartup 在服务启动时初始化同步系统。
|
||||
func (s *SyncService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
_ = options
|
||||
|
||||
if err := s.ensureApp(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.cancelObservers = []helper.CancelFunc{
|
||||
s.configService.Watch("sync", s.onSyncConfigChange),
|
||||
s.configService.Watch("general.dataPath", s.onDataPathChange),
|
||||
}
|
||||
|
||||
if err := s.Initialize(); err != nil {
|
||||
s.logger.Error("initializing sync service: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initialize 重新加载配置并启动自动同步。
|
||||
func (s *SyncService) Initialize() error {
|
||||
if err := s.ensureApp(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := s.buildConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.app.Reconfigure(context.Background(), config); err != nil {
|
||||
return fmt.Errorf("reconfigure sync app: %w", err)
|
||||
}
|
||||
if err := s.app.Start(context.Background()); err != nil {
|
||||
return fmt.Errorf("start sync app: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reinitialize 重新初始化同步服务。
|
||||
func (s *SyncService) Reinitialize() error {
|
||||
return s.Initialize()
|
||||
}
|
||||
|
||||
// HandleConfigChange 在配置变化时重新应用配置。
|
||||
func (s *SyncService) HandleConfigChange(config *models.SyncConfig) error {
|
||||
_ = config
|
||||
return s.Initialize()
|
||||
}
|
||||
|
||||
// StartAutoSync 启动自动同步调度。
|
||||
func (s *SyncService) StartAutoSync() error {
|
||||
if err := s.ensureApp(); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.app.Start(context.Background())
|
||||
}
|
||||
|
||||
// StopAutoSync 停止自动同步调度。
|
||||
func (s *SyncService) StopAutoSync() {
|
||||
if s.app == nil {
|
||||
return
|
||||
}
|
||||
if err := s.app.Stop(context.Background()); err != nil {
|
||||
s.logger.Warning("stop sync app: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Sync 执行一次手动同步。
|
||||
func (s *SyncService) Sync() error {
|
||||
if err := s.ensureApp(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetID, err := s.selectedTargetID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := s.app.Sync(context.Background(), targetID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServiceShutdown 停止同步服务并释放资源。
|
||||
func (s *SyncService) ServiceShutdown() {
|
||||
for _, cancel := range s.cancelObservers {
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
s.StopAutoSync()
|
||||
}
|
||||
|
||||
// onSyncConfigChange 响应 sync 配置变化。
|
||||
func (s *SyncService) onSyncConfigChange(oldValue interface{}, newValue interface{}) {
|
||||
_, _ = oldValue, newValue
|
||||
if err := s.Initialize(); err != nil {
|
||||
s.logger.Error("reconfigure sync after sync config change: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// onDataPathChange 响应数据目录变化。
|
||||
func (s *SyncService) onDataPathChange(oldValue interface{}, newValue interface{}) {
|
||||
_, _ = oldValue, newValue
|
||||
if err := s.Reinitialize(); err != nil {
|
||||
s.logger.Error("reconfigure sync after data path change: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ensureApp 保证同步应用已被创建。
|
||||
func (s *SyncService) ensureApp() error {
|
||||
if s.app != nil {
|
||||
return nil
|
||||
}
|
||||
if s.dbService == nil || s.dbService.Client == nil {
|
||||
return fmt.Errorf("sync database client is not ready")
|
||||
}
|
||||
|
||||
s.app = syncer.NewApp(s.dbService.Client, syncer.Options{
|
||||
Logger: s.logger,
|
||||
MaxSyncAttempts: 3,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildConfig 将现有应用配置映射为同步核心配置。
|
||||
func (s *SyncService) buildConfig() (syncer.Config, error) {
|
||||
appConfig, err := s.configService.GetConfig()
|
||||
if err != nil {
|
||||
return syncer.Config{}, err
|
||||
}
|
||||
|
||||
return syncer.Config{
|
||||
Targets: []syncer.TargetConfig{
|
||||
s.buildGitTargetConfig(appConfig.General.DataPath, appConfig.Sync.Git),
|
||||
s.buildLocalFSTargetConfig(appConfig.Sync.LocalFS),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// selectedTargetID 返回当前选中的同步目标标识。
|
||||
func (s *SyncService) selectedTargetID() (string, error) {
|
||||
appConfig, err := s.configService.GetConfig()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch appConfig.Sync.Target {
|
||||
case models.SyncTargetGit:
|
||||
return string(models.SyncTargetGit), nil
|
||||
case models.SyncTargetLocalFS:
|
||||
return string(models.SyncTargetLocalFS), nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported sync target: %s", appConfig.Sync.Target)
|
||||
}
|
||||
}
|
||||
|
||||
// buildGitTargetConfig 将 Git 配置转换为同步核心目标配置。
|
||||
func (s *SyncService) buildGitTargetConfig(dataPath string, config models.GitSyncConfig) syncer.TargetConfig {
|
||||
return syncer.TargetConfig{
|
||||
Kind: syncer.TargetKindGit,
|
||||
Enabled: config.Enabled,
|
||||
Schedule: syncer.ScheduleConfig{
|
||||
AutoSync: config.AutoSync,
|
||||
Interval: time.Duration(config.SyncInterval) * time.Minute,
|
||||
},
|
||||
Git: &syncer.GitTargetConfig{
|
||||
RepoPath: filepath.Join(dataPath, syncDir),
|
||||
RepoURL: config.RepoURL,
|
||||
Branch: syncer.DefaultBranch,
|
||||
RemoteName: syncer.DefaultRemoteName,
|
||||
AuthorName: "voidraft",
|
||||
AuthorEmail: "sync@voidraft.app",
|
||||
Auth: syncer.GitAuthConfig{
|
||||
Method: string(config.AuthMethod),
|
||||
Username: config.Username,
|
||||
Password: config.Password,
|
||||
Token: config.Token,
|
||||
SSHKeyPath: config.SSHKeyPath,
|
||||
SSHKeyPassword: config.SSHKeyPass,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// buildLocalFSTargetConfig 将 localfs 配置转换为同步核心目标配置。
|
||||
func (s *SyncService) buildLocalFSTargetConfig(config models.LocalFSSyncConfig) syncer.TargetConfig {
|
||||
return syncer.TargetConfig{
|
||||
Kind: syncer.TargetKindLocalFS,
|
||||
Enabled: config.Enabled,
|
||||
Schedule: syncer.ScheduleConfig{
|
||||
AutoSync: config.AutoSync,
|
||||
Interval: time.Duration(config.SyncInterval) * time.Minute,
|
||||
},
|
||||
LocalFS: &syncer.LocalFSTargetConfig{
|
||||
Namespace: string(models.SyncTargetLocalFS),
|
||||
HeadKey: localFSHeadKey,
|
||||
RootPath: config.RootPath,
|
||||
},
|
||||
}
|
||||
}
|
||||
283
internal/syncer/app.go
Normal file
283
internal/syncer/app.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package syncer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
"voidraft/internal/models/ent"
|
||||
"voidraft/internal/syncer/backend"
|
||||
gitbackend "voidraft/internal/syncer/backend/git"
|
||||
snapshotstorebackend "voidraft/internal/syncer/backend/snapshotstore"
|
||||
localfsblob "voidraft/internal/syncer/backend/snapshotstore/blob/localfs"
|
||||
"voidraft/internal/syncer/engine"
|
||||
"voidraft/internal/syncer/merge"
|
||||
"voidraft/internal/syncer/resource"
|
||||
"voidraft/internal/syncer/scheduler"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultAuthorName = "voidraft"
|
||||
defaultAuthorEmail = "sync@voidraft.app"
|
||||
defaultSyncAttempts = 3
|
||||
)
|
||||
|
||||
// Options 描述同步应用的构造选项。
|
||||
type Options struct {
|
||||
Logger Logger
|
||||
MaxSyncAttempts int
|
||||
}
|
||||
|
||||
// App 是同步系统的编排入口。
|
||||
type App struct {
|
||||
logger Logger
|
||||
snapshotter snapshot.Snapshotter
|
||||
store snapshot.Store
|
||||
merger merge.Merger
|
||||
maxSyncAttempts int
|
||||
|
||||
mu sync.RWMutex
|
||||
syncMu sync.Mutex
|
||||
config Config
|
||||
schedulers map[string]*scheduler.Ticker
|
||||
}
|
||||
|
||||
// NewApp 创建新的同步应用实例。
|
||||
func NewApp(client *ent.Client, options Options) *App {
|
||||
maxSyncAttempts := options.MaxSyncAttempts
|
||||
if maxSyncAttempts <= 0 {
|
||||
maxSyncAttempts = defaultSyncAttempts
|
||||
}
|
||||
|
||||
return &App{
|
||||
logger: options.Logger,
|
||||
snapshotter: resource.NewRegistry(
|
||||
resource.NewDocumentAdapter(client),
|
||||
resource.NewExtensionAdapter(client),
|
||||
resource.NewKeyBindingAdapter(client),
|
||||
resource.NewThemeAdapter(client),
|
||||
),
|
||||
store: snapshot.NewFileStore(),
|
||||
merger: merge.NewUpdatedAtWinsMerger(),
|
||||
maxSyncAttempts: maxSyncAttempts,
|
||||
schedulers: make(map[string]*scheduler.Ticker),
|
||||
}
|
||||
}
|
||||
|
||||
// Reconfigure 更新同步系统配置。
|
||||
func (a *App) Reconfigure(ctx context.Context, cfg Config) error {
|
||||
_ = ctx
|
||||
|
||||
normalized := cfg.Normalize()
|
||||
for _, target := range normalized.Targets {
|
||||
if err := target.Validate(); err != nil {
|
||||
return fmt.Errorf("validate target %s: %w", target.Kind, err)
|
||||
}
|
||||
}
|
||||
|
||||
a.mu.Lock()
|
||||
a.config = normalized
|
||||
a.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start 按当前配置启动自动同步调度。
|
||||
func (a *App) Start(ctx context.Context) error {
|
||||
targets := a.targetsSnapshot()
|
||||
if err := a.verifyTargets(ctx, targets); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
a.stopSchedulersLocked()
|
||||
|
||||
for _, target := range targets {
|
||||
if !target.Ready() || !target.Schedule.AutoSync || target.Schedule.Interval <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
currentTargetID := target.Kind
|
||||
task := scheduler.NewTicker()
|
||||
task.Start(target.Schedule.Interval, func(runCtx context.Context) error {
|
||||
_, err := a.Sync(runCtx, currentTargetID)
|
||||
if err != nil && a.logger != nil {
|
||||
a.logger.Error("sync auto run failed for target %s: %v", currentTargetID, err)
|
||||
}
|
||||
return err
|
||||
})
|
||||
a.schedulers[currentTargetID] = task
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop 停止所有自动同步调度。
|
||||
func (a *App) Stop(ctx context.Context) error {
|
||||
_ = ctx
|
||||
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
a.stopSchedulersLocked()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sync 执行指定目标的一次完整同步。
|
||||
func (a *App) Sync(ctx context.Context, targetID string) (*SyncResult, error) {
|
||||
target, err := a.currentTarget(targetID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !target.Enabled {
|
||||
return nil, ErrTargetDisabled
|
||||
}
|
||||
if !target.Ready() {
|
||||
return nil, ErrTargetNotReady
|
||||
}
|
||||
|
||||
backendInstance, err := a.newBackend(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = backendInstance.Close()
|
||||
}()
|
||||
|
||||
syncEngine := engine.NewSyncEngine(
|
||||
backendInstance,
|
||||
a.store,
|
||||
a.snapshotter,
|
||||
a.merger,
|
||||
engine.Options{
|
||||
Logger: a.logger,
|
||||
MaxAttempts: a.maxSyncAttempts,
|
||||
},
|
||||
)
|
||||
|
||||
a.syncMu.Lock()
|
||||
defer a.syncMu.Unlock()
|
||||
|
||||
result, err := syncEngine.Sync(ctx, engine.SyncOptions{
|
||||
CommitMessage: a.commitMessage(target),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SyncResult{
|
||||
TargetID: target.Kind,
|
||||
LocalChanged: result.LocalChanged,
|
||||
RemoteChanged: result.RemoteChanged,
|
||||
AppliedToLocal: result.AppliedToLocal,
|
||||
Published: result.Published,
|
||||
ConflictCount: result.ConflictCount,
|
||||
Revision: result.Revision,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// commitMessage 生成提交信息。
|
||||
func (a *App) commitMessage(target TargetConfig) string {
|
||||
return fmt.Sprintf("Sync %s %s", target.Kind, time.Now().Format(time.RFC3339))
|
||||
}
|
||||
|
||||
// currentTarget 返回当前内存中的目标配置。
|
||||
func (a *App) currentTarget(targetID string) (TargetConfig, error) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.config.Target(targetID)
|
||||
}
|
||||
|
||||
// targetsSnapshot 返回当前所有目标的快照。
|
||||
func (a *App) targetsSnapshot() []TargetConfig {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
|
||||
targets := make([]TargetConfig, len(a.config.Targets))
|
||||
copy(targets, a.config.Targets)
|
||||
return targets
|
||||
}
|
||||
|
||||
// verifyTargets 预先校验所有已就绪目标。
|
||||
func (a *App) verifyTargets(ctx context.Context, targets []TargetConfig) error {
|
||||
for _, target := range targets {
|
||||
if !target.Ready() {
|
||||
continue
|
||||
}
|
||||
|
||||
backendInstance, err := a.newBackend(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
verifyErr := backendInstance.Verify(ctx)
|
||||
closeErr := backendInstance.Close()
|
||||
if verifyErr != nil {
|
||||
return fmt.Errorf("verify target %s: %w", target.Kind, verifyErr)
|
||||
}
|
||||
if closeErr != nil {
|
||||
return fmt.Errorf("close target %s backend: %w", target.Kind, closeErr)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// newBackend 根据目标配置构造后端实例。
|
||||
func (a *App) newBackend(target TargetConfig) (backend.Backend, error) {
|
||||
switch target.Kind {
|
||||
case TargetKindGit:
|
||||
if target.Git == nil {
|
||||
return nil, fmt.Errorf("target %s: git config is nil", target.Kind)
|
||||
}
|
||||
return gitbackend.New(gitbackend.Config{
|
||||
RepoPath: target.Git.RepoPath,
|
||||
RepoURL: target.Git.RepoURL,
|
||||
Branch: target.Git.Branch,
|
||||
RemoteName: target.Git.RemoteName,
|
||||
AuthorName: fallbackString(target.Git.AuthorName, defaultAuthorName),
|
||||
AuthorEmail: fallbackString(target.Git.AuthorEmail, defaultAuthorEmail),
|
||||
Auth: gitbackend.AuthConfig{
|
||||
Method: target.Git.Auth.Method,
|
||||
Username: target.Git.Auth.Username,
|
||||
Password: target.Git.Auth.Password,
|
||||
Token: target.Git.Auth.Token,
|
||||
SSHKeyPath: target.Git.Auth.SSHKeyPath,
|
||||
SSHKeyPassword: target.Git.Auth.SSHKeyPassword,
|
||||
},
|
||||
})
|
||||
case TargetKindLocalFS:
|
||||
if target.LocalFS == nil {
|
||||
return nil, fmt.Errorf("target %s: localfs config is nil", target.Kind)
|
||||
}
|
||||
store, err := localfsblob.New(target.LocalFS.RootPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return snapshotstorebackend.New(snapshotstorebackend.Config{
|
||||
Store: store,
|
||||
Namespace: target.LocalFS.Namespace,
|
||||
HeadKey: target.LocalFS.HeadKey,
|
||||
})
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", ErrUnsupportedBackend, target.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
// stopSchedulersLocked 停止所有调度器。
|
||||
func (a *App) stopSchedulersLocked() {
|
||||
for targetID, task := range a.schedulers {
|
||||
task.Stop()
|
||||
delete(a.schedulers, targetID)
|
||||
}
|
||||
}
|
||||
|
||||
// fallbackString 返回第一个非空字符串。
|
||||
func fallbackString(value string, fallback string) string {
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
31
internal/syncer/backend/backend.go
Normal file
31
internal/syncer/backend/backend.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrRevisionConflict 表示远端版本已变化,需要重新拉取合并。
|
||||
ErrRevisionConflict = errors.New("sync revision conflict")
|
||||
)
|
||||
|
||||
// RemoteState 描述远端最新状态。
|
||||
type RemoteState struct {
|
||||
Revision string
|
||||
Exists bool
|
||||
}
|
||||
|
||||
// PublishOptions 描述一次发布操作的参数。
|
||||
type PublishOptions struct {
|
||||
ExpectedRevision string
|
||||
Message string
|
||||
}
|
||||
|
||||
// Backend 描述统一同步后端接口。
|
||||
type Backend interface {
|
||||
Verify(ctx context.Context) error
|
||||
DownloadLatest(ctx context.Context, dst string) (RemoteState, error)
|
||||
Upload(ctx context.Context, src string, options PublishOptions) (RemoteState, error)
|
||||
Close() error
|
||||
}
|
||||
60
internal/syncer/backend/git/auth.go
Normal file
60
internal/syncer/backend/git/auth.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
||||
)
|
||||
|
||||
// AuthConfig 描述 Git 鉴权方式。
|
||||
type AuthConfig struct {
|
||||
Method string
|
||||
Username string
|
||||
Password string
|
||||
Token string
|
||||
SSHKeyPath string
|
||||
SSHKeyPassword string
|
||||
}
|
||||
|
||||
const (
|
||||
// AuthMethodToken 使用 Token 鉴权。
|
||||
AuthMethodToken = "token"
|
||||
// AuthMethodSSHKey 使用 SSH Key 鉴权。
|
||||
AuthMethodSSHKey = "ssh_key"
|
||||
// AuthMethodUserPass 使用用户名密码鉴权。
|
||||
AuthMethodUserPass = "user_pass"
|
||||
)
|
||||
|
||||
// authMethod 根据配置构造 go-git 鉴权实例。
|
||||
func authMethod(config AuthConfig) (transport.AuthMethod, error) {
|
||||
switch config.Method {
|
||||
case AuthMethodToken:
|
||||
if config.Token == "" {
|
||||
return nil, errors.New("git token is required")
|
||||
}
|
||||
return &http.BasicAuth{
|
||||
Username: "git",
|
||||
Password: config.Token,
|
||||
}, nil
|
||||
case AuthMethodUserPass:
|
||||
if config.Username == "" || config.Password == "" {
|
||||
return nil, errors.New("git username and password are required")
|
||||
}
|
||||
return &http.BasicAuth{
|
||||
Username: config.Username,
|
||||
Password: config.Password,
|
||||
}, nil
|
||||
case AuthMethodSSHKey:
|
||||
if config.SSHKeyPath == "" {
|
||||
return nil, errors.New("git ssh key path is required")
|
||||
}
|
||||
return ssh.NewPublicKeysFromFile("git", config.SSHKeyPath, config.SSHKeyPassword)
|
||||
case "":
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported git auth method: %s", config.Method)
|
||||
}
|
||||
}
|
||||
518
internal/syncer/backend/git/backend.go
Normal file
518
internal/syncer/backend/git/backend.go
Normal file
@@ -0,0 +1,518 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"voidraft/internal/syncer/backend"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
gitconfig "github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
)
|
||||
|
||||
const defaultGitIgnore = "*.tmp\n*.log\n"
|
||||
|
||||
// Config 描述 Git 后端配置。
|
||||
type Config struct {
|
||||
RepoPath string
|
||||
RepoURL string
|
||||
Branch string
|
||||
RemoteName string
|
||||
AuthorName string
|
||||
AuthorEmail string
|
||||
Auth AuthConfig
|
||||
}
|
||||
|
||||
// Backend 提供基于 Git 的后端实现。
|
||||
type Backend struct {
|
||||
config Config
|
||||
repository *git.Repository
|
||||
}
|
||||
|
||||
// New 创建新的 Git 后端实例。
|
||||
func New(config Config) (*Backend, error) {
|
||||
normalized, err := normalizeConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Backend{config: normalized}, nil
|
||||
}
|
||||
|
||||
// Verify 校验本地仓库和远端连接是否可用。
|
||||
func (b *Backend) Verify(ctx context.Context) error {
|
||||
_ = ctx
|
||||
|
||||
if err := b.ensureRepository(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
auth, err := authMethod(b.config.Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remote, err := b.repository.Remote(b.config.RemoteName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = remote.List(&git.ListOptions{Auth: auth})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if isEmptyRemoteError(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// DownloadLatest 拉取远端最新快照并导出到目标目录。
|
||||
func (b *Backend) DownloadLatest(ctx context.Context, dst string) (backend.RemoteState, error) {
|
||||
_ = ctx
|
||||
|
||||
if err := b.ensureRepository(); err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
if err := recreateDir(dst); err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
remoteState, err := b.fetchRemoteState()
|
||||
if err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
if !remoteState.Exists {
|
||||
return remoteState, nil
|
||||
}
|
||||
|
||||
if err := b.exportRemoteTree(remoteState.Revision, dst); err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
return remoteState, nil
|
||||
}
|
||||
|
||||
// Upload 将本地快照目录发布到远端 Git 仓库。
|
||||
func (b *Backend) Upload(ctx context.Context, src string, options backend.PublishOptions) (backend.RemoteState, error) {
|
||||
_ = ctx
|
||||
|
||||
if err := b.ensureRepository(); err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
remoteState, err := b.fetchRemoteState()
|
||||
if err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
if options.ExpectedRevision != "" && remoteState.Exists && remoteState.Revision != options.ExpectedRevision {
|
||||
return backend.RemoteState{}, backend.ErrRevisionConflict
|
||||
}
|
||||
|
||||
if err := b.prepareBranch(remoteState); err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
if err := syncDir(src, b.config.RepoPath); err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
worktree, err := b.repository.Worktree()
|
||||
if err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
changed, err := stageAll(worktree)
|
||||
if err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
if !changed {
|
||||
return b.currentLocalState()
|
||||
}
|
||||
|
||||
if _, err := worktree.Commit(options.Message, &git.CommitOptions{
|
||||
Author: &object.Signature{
|
||||
Name: b.config.AuthorName,
|
||||
Email: b.config.AuthorEmail,
|
||||
When: time.Now(),
|
||||
},
|
||||
}); err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
auth, err := authMethod(b.config.Auth)
|
||||
if err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
branchRef := plumbing.NewBranchReferenceName(b.config.Branch)
|
||||
remoteRef := plumbing.NewRemoteReferenceName(b.config.RemoteName, b.config.Branch)
|
||||
err = b.repository.Push(&git.PushOptions{
|
||||
RemoteName: b.config.RemoteName,
|
||||
Auth: auth,
|
||||
RefSpecs: []gitconfig.RefSpec{
|
||||
gitconfig.RefSpec(fmt.Sprintf("%s:%s", branchRef, remoteRef)),
|
||||
},
|
||||
})
|
||||
if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
|
||||
if errors.Is(err, git.ErrNonFastForwardUpdate) {
|
||||
return backend.RemoteState{}, backend.ErrRevisionConflict
|
||||
}
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
return b.currentLocalState()
|
||||
}
|
||||
|
||||
// Close 关闭后端。
|
||||
func (b *Backend) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeConfig 填充 Git 后端配置默认值。
|
||||
func normalizeConfig(config Config) (Config, error) {
|
||||
normalized := config
|
||||
if strings.TrimSpace(normalized.RepoPath) == "" {
|
||||
return Config{}, errors.New("git repo path is required")
|
||||
}
|
||||
if strings.TrimSpace(normalized.Branch) == "" {
|
||||
normalized.Branch = "master"
|
||||
}
|
||||
if strings.TrimSpace(normalized.RemoteName) == "" {
|
||||
normalized.RemoteName = "origin"
|
||||
}
|
||||
if strings.TrimSpace(normalized.AuthorName) == "" {
|
||||
normalized.AuthorName = "voidraft"
|
||||
}
|
||||
if strings.TrimSpace(normalized.AuthorEmail) == "" {
|
||||
normalized.AuthorEmail = "sync@voidraft.app"
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
// ensureRepository 确保本地 Git 仓库存在且远端配置正确。
|
||||
func (b *Backend) ensureRepository() error {
|
||||
if b.repository != nil {
|
||||
return b.ensureRemote()
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(b.config.RepoPath, 0755); err != nil {
|
||||
return fmt.Errorf("create git repo dir: %w", err)
|
||||
}
|
||||
|
||||
gitPath := filepath.Join(b.config.RepoPath, ".git")
|
||||
if _, err := os.Stat(gitPath); os.IsNotExist(err) {
|
||||
repository, initErr := git.PlainInit(b.config.RepoPath, false)
|
||||
if initErr != nil {
|
||||
return fmt.Errorf("init git repo: %w", initErr)
|
||||
}
|
||||
b.repository = repository
|
||||
if err := ensureGitIgnore(b.config.RepoPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return b.ensureRemote()
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("stat git repo: %w", err)
|
||||
}
|
||||
|
||||
repository, err := git.PlainOpen(b.config.RepoPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open git repo: %w", err)
|
||||
}
|
||||
b.repository = repository
|
||||
if err := ensureGitIgnore(b.config.RepoPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return b.ensureRemote()
|
||||
}
|
||||
|
||||
// ensureRemote 确保远端配置与当前目标一致。
|
||||
func (b *Backend) ensureRemote() error {
|
||||
if strings.TrimSpace(b.config.RepoURL) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
remote, err := b.repository.Remote(b.config.RemoteName)
|
||||
if errors.Is(err, git.ErrRemoteNotFound) {
|
||||
_, err = b.repository.CreateRemote(&gitconfig.RemoteConfig{
|
||||
Name: b.config.RemoteName,
|
||||
URLs: []string{b.config.RepoURL},
|
||||
})
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(remote.Config().URLs) > 0 && remote.Config().URLs[0] == b.config.RepoURL {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := b.repository.DeleteRemote(b.config.RemoteName); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = b.repository.CreateRemote(&gitconfig.RemoteConfig{
|
||||
Name: b.config.RemoteName,
|
||||
URLs: []string{b.config.RepoURL},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// fetchRemoteState 拉取远端分支并返回最新状态。
|
||||
func (b *Backend) fetchRemoteState() (backend.RemoteState, error) {
|
||||
auth, err := authMethod(b.config.Auth)
|
||||
if err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
err = b.repository.Fetch(&git.FetchOptions{
|
||||
RemoteName: b.config.RemoteName,
|
||||
Auth: auth,
|
||||
Force: true,
|
||||
})
|
||||
if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
|
||||
if isEmptyRemoteError(err) || isMissingRemoteRefError(err) {
|
||||
return backend.RemoteState{}, nil
|
||||
}
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
ref, err := b.repository.Reference(plumbing.NewRemoteReferenceName(b.config.RemoteName, b.config.Branch), true)
|
||||
if err != nil {
|
||||
if errors.Is(err, plumbing.ErrReferenceNotFound) {
|
||||
return backend.RemoteState{}, nil
|
||||
}
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
return backend.RemoteState{
|
||||
Exists: true,
|
||||
Revision: ref.Hash().String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// exportRemoteTree 将指定提交的树内容导出为普通文件。
|
||||
func (b *Backend) exportRemoteTree(revision string, dst string) error {
|
||||
commit, err := b.repository.CommitObject(plumbing.NewHash(revision))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tree, err := commit.Tree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tree.Files().ForEach(func(file *object.File) error {
|
||||
targetPath := filepath.Join(dst, filepath.FromSlash(file.Name))
|
||||
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reader, err := file.Reader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
writer, err := os.Create(targetPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer writer.Close()
|
||||
|
||||
_, err = io.Copy(writer, reader)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// prepareBranch 将本地分支重置到远端最新版本。
|
||||
func (b *Backend) prepareBranch(remoteState backend.RemoteState) error {
|
||||
branchRef := plumbing.NewBranchReferenceName(b.config.Branch)
|
||||
if remoteState.Exists {
|
||||
if err := b.repository.Storer.SetReference(plumbing.NewHashReference(branchRef, plumbing.NewHash(remoteState.Revision))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := b.repository.Storer.SetReference(plumbing.NewSymbolicReference(plumbing.HEAD, branchRef)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !remoteState.Exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
worktree, err := b.repository.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return worktree.Checkout(&git.CheckoutOptions{
|
||||
Branch: branchRef,
|
||||
Force: true,
|
||||
})
|
||||
}
|
||||
|
||||
// currentLocalState 返回当前本地 HEAD 状态。
|
||||
func (b *Backend) currentLocalState() (backend.RemoteState, error) {
|
||||
head, err := b.repository.Head()
|
||||
if err != nil {
|
||||
if errors.Is(err, plumbing.ErrReferenceNotFound) {
|
||||
return backend.RemoteState{}, nil
|
||||
}
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
return backend.RemoteState{
|
||||
Exists: true,
|
||||
Revision: head.Hash().String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ensureGitIgnore 保证仓库目录中存在默认 .gitignore。
|
||||
func ensureGitIgnore(repoPath string) error {
|
||||
gitIgnorePath := filepath.Join(repoPath, ".gitignore")
|
||||
if _, err := os.Stat(gitIgnorePath); err == nil {
|
||||
return nil
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(gitIgnorePath, []byte(defaultGitIgnore), 0644)
|
||||
}
|
||||
|
||||
// recreateDir 清空并重建目录。
|
||||
func recreateDir(dir string) error {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(dir, 0755)
|
||||
}
|
||||
|
||||
// syncDir 将源目录内容同步到目标目录。
|
||||
func syncDir(src string, dst string) error {
|
||||
sourceEntries, err := os.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(dst, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sourceIndex := make(map[string]os.DirEntry, len(sourceEntries))
|
||||
for _, entry := range sourceEntries {
|
||||
sourceIndex[entry.Name()] = entry
|
||||
srcPath := filepath.Join(src, entry.Name())
|
||||
dstPath := filepath.Join(dst, entry.Name())
|
||||
|
||||
if entry.IsDir() {
|
||||
if err := syncDir(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err := copyFile(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
targetEntries, err := os.ReadDir(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range targetEntries {
|
||||
if entry.Name() == ".git" || entry.Name() == ".gitignore" {
|
||||
continue
|
||||
}
|
||||
if _, exists := sourceIndex[entry.Name()]; exists {
|
||||
continue
|
||||
}
|
||||
if err := os.RemoveAll(filepath.Join(dst, entry.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyFile 复制单个文件并保留权限位。
|
||||
func copyFile(src string, dst string) error {
|
||||
sourceFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
info, err := sourceFile.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetFile, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode().Perm())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer targetFile.Close()
|
||||
|
||||
_, err = io.Copy(targetFile, sourceFile)
|
||||
return err
|
||||
}
|
||||
|
||||
// stageAll 将工作区所有变化加入索引。
|
||||
func stageAll(worktree *git.Worktree) (bool, error) {
|
||||
status, err := worktree.Status()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for path, fileStatus := range status {
|
||||
switch fileStatus.Worktree {
|
||||
case git.Untracked, git.Modified, git.Added, git.Copied, git.Renamed:
|
||||
if _, err := worktree.Add(path); err != nil {
|
||||
return false, err
|
||||
}
|
||||
case git.Deleted:
|
||||
if _, err := worktree.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if fileStatus.Staging == git.Deleted && fileStatus.Worktree == git.Unmodified {
|
||||
if _, err := worktree.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status, err = worktree.Status()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !status.IsClean(), nil
|
||||
}
|
||||
|
||||
// isEmptyRemoteError 判断错误是否表示远端仓库为空。
|
||||
func isEmptyRemoteError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
message := err.Error()
|
||||
return strings.Contains(message, "empty") || strings.Contains(message, "no reference")
|
||||
}
|
||||
|
||||
// isMissingRemoteRefError 判断错误是否表示远端分支不存在。
|
||||
func isMissingRemoteRefError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
message := err.Error()
|
||||
return strings.Contains(message, "reference not found") || strings.Contains(message, "couldn't find remote ref")
|
||||
}
|
||||
413
internal/syncer/backend/snapshotstore/backend.go
Normal file
413
internal/syncer/backend/snapshotstore/backend.go
Normal file
@@ -0,0 +1,413 @@
|
||||
package snapshotstore
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"voidraft/internal/syncer/backend"
|
||||
"voidraft/internal/syncer/backend/snapshotstore/blob"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultNamespace = "sync"
|
||||
defaultHeadKey = "head.json"
|
||||
bundleDirName = "bundles"
|
||||
)
|
||||
|
||||
var stableBundleTime = time.Unix(0, 0).UTC()
|
||||
|
||||
// Config 描述 snapshot_store 后端配置。
|
||||
type Config struct {
|
||||
Store blob.Store
|
||||
Namespace string
|
||||
HeadKey string
|
||||
}
|
||||
|
||||
type headDocument struct {
|
||||
Revision string `json:"revision"`
|
||||
BundleKey string `json:"bundle_key"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
type headState struct {
|
||||
Document headDocument
|
||||
Info blob.ObjectInfo
|
||||
}
|
||||
|
||||
// Backend 提供基于对象/文件存储的快照后端实现。
|
||||
type Backend struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
// New 创建新的 snapshot_store 后端。
|
||||
func New(config Config) (*Backend, error) {
|
||||
if config.Store == nil {
|
||||
return nil, errors.New("snapshot store blob backend is required")
|
||||
}
|
||||
if strings.TrimSpace(config.Namespace) == "" {
|
||||
config.Namespace = defaultNamespace
|
||||
}
|
||||
if strings.TrimSpace(config.HeadKey) == "" {
|
||||
config.HeadKey = defaultHeadKey
|
||||
}
|
||||
return &Backend{config: config}, nil
|
||||
}
|
||||
|
||||
// Verify 校验后端是否可读。
|
||||
func (b *Backend) Verify(ctx context.Context) error {
|
||||
_, _, err := b.readHead(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// DownloadLatest 下载远端最新快照包并解压到目标目录。
|
||||
func (b *Backend) DownloadLatest(ctx context.Context, dst string) (backend.RemoteState, error) {
|
||||
head, exists, err := b.readHead(ctx)
|
||||
if err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
if !exists {
|
||||
return backend.RemoteState{}, nil
|
||||
}
|
||||
|
||||
reader, _, err := b.config.Store.Get(ctx, head.Document.BundleKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, blob.ErrObjectNotFound) {
|
||||
return backend.RemoteState{}, nil
|
||||
}
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
if err := recreateDir(dst); err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
if err := extractBundle(reader, dst); err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
return backend.RemoteState{
|
||||
Exists: true,
|
||||
Revision: head.Document.Revision,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Upload 打包并发布本地快照目录。
|
||||
func (b *Backend) Upload(ctx context.Context, src string, options backend.PublishOptions) (backend.RemoteState, error) {
|
||||
currentHead, exists, err := b.readHead(ctx)
|
||||
if err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case options.ExpectedRevision != "" && !exists:
|
||||
return backend.RemoteState{}, backend.ErrRevisionConflict
|
||||
case options.ExpectedRevision != "" && currentHead.Document.Revision != options.ExpectedRevision:
|
||||
return backend.RemoteState{}, backend.ErrRevisionConflict
|
||||
}
|
||||
|
||||
bundlePath, revision, err := createBundle(src)
|
||||
if err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
defer os.Remove(bundlePath)
|
||||
|
||||
if exists && currentHead.Document.Revision == revision {
|
||||
return backend.RemoteState{
|
||||
Exists: true,
|
||||
Revision: revision,
|
||||
}, nil
|
||||
}
|
||||
|
||||
bundleKey := b.bundleKey(revision)
|
||||
file, err := os.Open(bundlePath)
|
||||
if err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err := b.config.Store.Put(ctx, bundleKey, file, blob.PutOptions{}); err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
nextHead := headDocument{
|
||||
Revision: revision,
|
||||
BundleKey: bundleKey,
|
||||
UpdatedAt: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
headPayload, err := json.MarshalIndent(nextHead, "", " ")
|
||||
if err != nil {
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
headPayload = append(headPayload, '\n')
|
||||
|
||||
putOptions := blob.PutOptions{}
|
||||
if exists {
|
||||
putOptions.IfMatch = currentHead.Info.Revision
|
||||
}
|
||||
|
||||
if _, err := b.config.Store.Put(ctx, b.headKey(), bytes.NewReader(headPayload), putOptions); err != nil {
|
||||
if errors.Is(err, blob.ErrConditionNotMet) {
|
||||
return backend.RemoteState{}, backend.ErrRevisionConflict
|
||||
}
|
||||
return backend.RemoteState{}, err
|
||||
}
|
||||
|
||||
return backend.RemoteState{
|
||||
Exists: true,
|
||||
Revision: revision,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close 关闭后端。
|
||||
func (b *Backend) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// readHead 读取远端 head 指针。
|
||||
func (b *Backend) readHead(ctx context.Context) (headState, bool, error) {
|
||||
reader, info, err := b.config.Store.Get(ctx, b.headKey())
|
||||
if err != nil {
|
||||
if errors.Is(err, blob.ErrObjectNotFound) {
|
||||
return headState{}, false, nil
|
||||
}
|
||||
return headState{}, false, err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return headState{}, false, err
|
||||
}
|
||||
|
||||
var document headDocument
|
||||
if err := json.Unmarshal(data, &document); err != nil {
|
||||
return headState{}, false, err
|
||||
}
|
||||
if document.Revision == "" || document.BundleKey == "" {
|
||||
return headState{}, false, errors.New("snapshot store head is invalid")
|
||||
}
|
||||
|
||||
return headState{
|
||||
Document: document,
|
||||
Info: info,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
// headKey 返回完整的 head 对象键。
|
||||
func (b *Backend) headKey() string {
|
||||
return path.Join(b.config.Namespace, b.config.HeadKey)
|
||||
}
|
||||
|
||||
// bundleKey 返回 revision 对应的 bundle 键。
|
||||
func (b *Backend) bundleKey(revision string) string {
|
||||
return path.Join(b.config.Namespace, bundleDirName, revision+".tar.gz")
|
||||
}
|
||||
|
||||
// createBundle 将目录稳定打包成 tar.gz,并返回文件路径与摘要。
|
||||
func createBundle(root string) (string, string, error) {
|
||||
tempFile, err := os.CreateTemp("", "voidraft-snapshot-*.tar.gz")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
tempName := tempFile.Name()
|
||||
|
||||
hasher := sha256.New()
|
||||
multiWriter := io.MultiWriter(tempFile, hasher)
|
||||
|
||||
gzipWriter := gzip.NewWriter(multiWriter)
|
||||
gzipWriter.ModTime = stableBundleTime
|
||||
gzipWriter.Name = ""
|
||||
gzipWriter.Comment = ""
|
||||
|
||||
tarWriter := tar.NewWriter(gzipWriter)
|
||||
|
||||
writeErr := writeBundle(root, tarWriter)
|
||||
closeErr := tarWriter.Close()
|
||||
gzipCloseErr := gzipWriter.Close()
|
||||
fileCloseErr := tempFile.Close()
|
||||
if writeErr != nil {
|
||||
_ = os.Remove(tempName)
|
||||
return "", "", writeErr
|
||||
}
|
||||
if closeErr != nil {
|
||||
_ = os.Remove(tempName)
|
||||
return "", "", closeErr
|
||||
}
|
||||
if gzipCloseErr != nil {
|
||||
_ = os.Remove(tempName)
|
||||
return "", "", gzipCloseErr
|
||||
}
|
||||
if fileCloseErr != nil {
|
||||
_ = os.Remove(tempName)
|
||||
return "", "", fileCloseErr
|
||||
}
|
||||
|
||||
revision := hex.EncodeToString(hasher.Sum(nil))
|
||||
return tempName, revision, nil
|
||||
}
|
||||
|
||||
// writeBundle 将目录内容按稳定顺序写入 tar。
|
||||
func writeBundle(root string, writer *tar.Writer) error {
|
||||
paths, err := collectPaths(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entryPath := range paths {
|
||||
info, err := os.Lstat(entryPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relativePath, err := filepath.Rel(root, entryPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header, err := tar.FileInfoHeader(info, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Name = filepath.ToSlash(relativePath)
|
||||
header.ModTime = stableBundleTime
|
||||
header.AccessTime = stableBundleTime
|
||||
header.ChangeTime = stableBundleTime
|
||||
header.Uid = 0
|
||||
header.Gid = 0
|
||||
header.Uname = ""
|
||||
header.Gname = ""
|
||||
|
||||
if info.IsDir() && !strings.HasSuffix(header.Name, "/") {
|
||||
header.Name += "/"
|
||||
}
|
||||
|
||||
if err := writer.WriteHeader(header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
file, err := os.Open(entryPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(writer, file); err != nil {
|
||||
file.Close()
|
||||
return err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectPaths 返回稳定排序后的目录项列表。
|
||||
func collectPaths(root string) ([]string, error) {
|
||||
entries := make([]string, 0)
|
||||
if err := filepath.WalkDir(root, func(entryPath string, entry os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if entryPath == root {
|
||||
return nil
|
||||
}
|
||||
entries = append(entries, entryPath)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Strings(entries)
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// extractBundle 将 tar.gz 包解压到目标目录。
|
||||
func extractBundle(reader io.Reader, dst string) error {
|
||||
gzipReader, err := gzip.NewReader(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzipReader.Close()
|
||||
|
||||
tarReader := tar.NewReader(gzipReader)
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetPath, err := resolveExtractPath(dst, header.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err := os.MkdirAll(targetPath, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeReg:
|
||||
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.OpenFile(targetPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(file, tarReader); err != nil {
|
||||
file.Close()
|
||||
return err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported tar entry type: %d", header.Typeflag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recreateDir 清空并重建目录。
|
||||
func recreateDir(dir string) error {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(dir, 0755)
|
||||
}
|
||||
|
||||
// resolveExtractPath 将归档路径安全映射到目标目录。
|
||||
func resolveExtractPath(root string, name string) (string, error) {
|
||||
clean := filepath.Clean(filepath.FromSlash(name))
|
||||
if clean == "." {
|
||||
return "", errors.New("invalid archive entry")
|
||||
}
|
||||
targetPath := filepath.Join(root, clean)
|
||||
relativePath, err := filepath.Rel(root, targetPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.HasPrefix(relativePath, "..") {
|
||||
return "", errors.New("archive entry escapes target directory")
|
||||
}
|
||||
return targetPath, nil
|
||||
}
|
||||
109
internal/syncer/backend/snapshotstore/backend_test.go
Normal file
109
internal/syncer/backend/snapshotstore/backend_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package snapshotstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"voidraft/internal/syncer/backend"
|
||||
localfsblob "voidraft/internal/syncer/backend/snapshotstore/blob/localfs"
|
||||
)
|
||||
|
||||
// TestBackendUploadDownload 验证 snapshot_store 后端可以发布并回放快照包。
|
||||
func TestBackendUploadDownload(t *testing.T) {
|
||||
store, err := localfsblob.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatalf("create blob store: %v", err)
|
||||
}
|
||||
|
||||
backendInstance, err := New(Config{
|
||||
Store: store,
|
||||
Namespace: "tests",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create backend: %v", err)
|
||||
}
|
||||
|
||||
sourceDir := t.TempDir()
|
||||
if err := os.MkdirAll(filepath.Join(sourceDir, "documents"), 0755); err != nil {
|
||||
t.Fatalf("mkdir source dir: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(sourceDir, "documents", "doc-1.json"), []byte("{\"title\":\"v1\"}\n"), 0644); err != nil {
|
||||
t.Fatalf("write source file: %v", err)
|
||||
}
|
||||
|
||||
firstState, err := backendInstance.Upload(context.Background(), sourceDir, backend.PublishOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("upload snapshot: %v", err)
|
||||
}
|
||||
if !firstState.Exists || firstState.Revision == "" {
|
||||
t.Fatalf("expected remote state after first upload")
|
||||
}
|
||||
|
||||
downloadDir := t.TempDir()
|
||||
downloadState, err := backendInstance.DownloadLatest(context.Background(), downloadDir)
|
||||
if err != nil {
|
||||
t.Fatalf("download latest snapshot: %v", err)
|
||||
}
|
||||
if downloadState.Revision != firstState.Revision {
|
||||
t.Fatalf("expected revision %s, got %s", firstState.Revision, downloadState.Revision)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(downloadDir, "documents", "doc-1.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("read downloaded file: %v", err)
|
||||
}
|
||||
if string(data) != "{\"title\":\"v1\"}\n" {
|
||||
t.Fatalf("unexpected downloaded content: %s", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
// TestBackendRevisionConflict 验证 snapshot_store 后端会在版本过期时返回冲突。
|
||||
func TestBackendRevisionConflict(t *testing.T) {
|
||||
store, err := localfsblob.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatalf("create blob store: %v", err)
|
||||
}
|
||||
|
||||
backendInstance, err := New(Config{
|
||||
Store: store,
|
||||
Namespace: "tests",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create backend: %v", err)
|
||||
}
|
||||
|
||||
sourceDir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(sourceDir, "state.json"), []byte("{\"value\":1}\n"), 0644); err != nil {
|
||||
t.Fatalf("write source file: %v", err)
|
||||
}
|
||||
|
||||
firstState, err := backendInstance.Upload(context.Background(), sourceDir, backend.PublishOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("upload first snapshot: %v", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(sourceDir, "state.json"), []byte("{\"value\":2}\n"), 0644); err != nil {
|
||||
t.Fatalf("rewrite source file: %v", err)
|
||||
}
|
||||
secondState, err := backendInstance.Upload(context.Background(), sourceDir, backend.PublishOptions{
|
||||
ExpectedRevision: firstState.Revision,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("upload second snapshot: %v", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(sourceDir, "state.json"), []byte("{\"value\":3}\n"), 0644); err != nil {
|
||||
t.Fatalf("rewrite source file again: %v", err)
|
||||
}
|
||||
_, err = backendInstance.Upload(context.Background(), sourceDir, backend.PublishOptions{
|
||||
ExpectedRevision: firstState.Revision,
|
||||
})
|
||||
if !errors.Is(err, backend.ErrRevisionConflict) {
|
||||
t.Fatalf("expected ErrRevisionConflict, got %v", err)
|
||||
}
|
||||
if secondState.Revision == firstState.Revision {
|
||||
t.Fatalf("expected revision to change after second upload")
|
||||
}
|
||||
}
|
||||
34
internal/syncer/backend/snapshotstore/blob/blob.go
Normal file
34
internal/syncer/backend/snapshotstore/blob/blob.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package blob
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrObjectNotFound 表示对象不存在。
|
||||
ErrObjectNotFound = errors.New("blob object not found")
|
||||
// ErrConditionNotMet 表示条件写入失败。
|
||||
ErrConditionNotMet = errors.New("blob condition not met")
|
||||
)
|
||||
|
||||
// ObjectInfo 描述一个对象的元信息。
|
||||
type ObjectInfo struct {
|
||||
Key string
|
||||
Revision string
|
||||
Size int64
|
||||
}
|
||||
|
||||
// PutOptions 描述对象写入条件。
|
||||
type PutOptions struct {
|
||||
IfMatch string
|
||||
}
|
||||
|
||||
// Store 描述 blob 存储的最小能力集。
|
||||
type Store interface {
|
||||
Get(ctx context.Context, key string) (io.ReadCloser, ObjectInfo, error)
|
||||
Put(ctx context.Context, key string, body io.Reader, options PutOptions) (ObjectInfo, error)
|
||||
Stat(ctx context.Context, key string) (ObjectInfo, error)
|
||||
Delete(ctx context.Context, key string) error
|
||||
}
|
||||
182
internal/syncer/backend/snapshotstore/blob/localfs/store.go
Normal file
182
internal/syncer/backend/snapshotstore/blob/localfs/store.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package localfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"voidraft/internal/syncer/backend/snapshotstore/blob"
|
||||
)
|
||||
|
||||
// Store 提供基于本地目录的 blob 存储实现。
|
||||
type Store struct {
|
||||
rootPath string
|
||||
}
|
||||
|
||||
// New 创建新的 localfs blob 存储。
|
||||
func New(rootPath string) (*Store, error) {
|
||||
if strings.TrimSpace(rootPath) == "" {
|
||||
return nil, errors.New("localfs root path is required")
|
||||
}
|
||||
if err := os.MkdirAll(rootPath, 0755); err != nil {
|
||||
return nil, fmt.Errorf("create localfs root path: %w", err)
|
||||
}
|
||||
return &Store{rootPath: rootPath}, nil
|
||||
}
|
||||
|
||||
// Get 读取对象内容。
|
||||
func (s *Store) Get(ctx context.Context, key string) (io.ReadCloser, blob.ObjectInfo, error) {
|
||||
_ = ctx
|
||||
|
||||
info, err := s.Stat(ctx, key)
|
||||
if err != nil {
|
||||
return nil, blob.ObjectInfo{}, err
|
||||
}
|
||||
|
||||
path, err := s.resolvePath(key)
|
||||
if err != nil {
|
||||
return nil, blob.ObjectInfo{}, err
|
||||
}
|
||||
|
||||
reader, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, blob.ObjectInfo{}, blob.ErrObjectNotFound
|
||||
}
|
||||
return nil, blob.ObjectInfo{}, err
|
||||
}
|
||||
|
||||
return reader, info, nil
|
||||
}
|
||||
|
||||
// Put 写入对象内容。
|
||||
func (s *Store) Put(ctx context.Context, key string, body io.Reader, options blob.PutOptions) (blob.ObjectInfo, error) {
|
||||
_ = ctx
|
||||
|
||||
path, err := s.resolvePath(key)
|
||||
if err != nil {
|
||||
return blob.ObjectInfo{}, err
|
||||
}
|
||||
|
||||
if options.IfMatch != "" {
|
||||
currentInfo, err := s.Stat(ctx, key)
|
||||
if err != nil {
|
||||
if errors.Is(err, blob.ErrObjectNotFound) {
|
||||
return blob.ObjectInfo{}, blob.ErrConditionNotMet
|
||||
}
|
||||
return blob.ObjectInfo{}, err
|
||||
}
|
||||
if currentInfo.Revision != options.IfMatch {
|
||||
return blob.ObjectInfo{}, blob.ErrConditionNotMet
|
||||
}
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
return blob.ObjectInfo{}, err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return blob.ObjectInfo{}, err
|
||||
}
|
||||
|
||||
tempFile, err := os.CreateTemp(filepath.Dir(path), "blob-put-*")
|
||||
if err != nil {
|
||||
return blob.ObjectInfo{}, err
|
||||
}
|
||||
tempName := tempFile.Name()
|
||||
|
||||
if _, err := tempFile.Write(data); err != nil {
|
||||
tempFile.Close()
|
||||
_ = os.Remove(tempName)
|
||||
return blob.ObjectInfo{}, err
|
||||
}
|
||||
if err := tempFile.Close(); err != nil {
|
||||
_ = os.Remove(tempName)
|
||||
return blob.ObjectInfo{}, err
|
||||
}
|
||||
if err := os.Rename(tempName, path); err != nil {
|
||||
_ = os.Remove(tempName)
|
||||
return blob.ObjectInfo{}, err
|
||||
}
|
||||
|
||||
return blob.ObjectInfo{
|
||||
Key: key,
|
||||
Revision: digest(data),
|
||||
Size: int64(len(data)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Stat 返回对象元信息。
|
||||
func (s *Store) Stat(ctx context.Context, key string) (blob.ObjectInfo, error) {
|
||||
_ = ctx
|
||||
|
||||
path, err := s.resolvePath(key)
|
||||
if err != nil {
|
||||
return blob.ObjectInfo{}, err
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return blob.ObjectInfo{}, blob.ErrObjectNotFound
|
||||
}
|
||||
return blob.ObjectInfo{}, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hash := sha256.New()
|
||||
size, err := io.Copy(hash, file)
|
||||
if err != nil {
|
||||
return blob.ObjectInfo{}, err
|
||||
}
|
||||
|
||||
return blob.ObjectInfo{
|
||||
Key: key,
|
||||
Revision: hex.EncodeToString(hash.Sum(nil)),
|
||||
Size: size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Delete 删除指定对象。
|
||||
func (s *Store) Delete(ctx context.Context, key string) error {
|
||||
_ = ctx
|
||||
|
||||
path, err := s.resolvePath(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolvePath 将对象键转换为安全路径。
|
||||
func (s *Store) resolvePath(key string) (string, error) {
|
||||
normalized := filepath.Clean(filepath.FromSlash(key))
|
||||
if normalized == "." || normalized == string(filepath.Separator) {
|
||||
return "", errors.New("invalid blob key")
|
||||
}
|
||||
|
||||
path := filepath.Join(s.rootPath, normalized)
|
||||
rel, err := filepath.Rel(s.rootPath, path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.HasPrefix(rel, "..") {
|
||||
return "", errors.New("blob key escapes root path")
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// digest 计算内容摘要。
|
||||
func digest(data []byte) string {
|
||||
sum := sha256.Sum256(data)
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package localfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
"voidraft/internal/syncer/backend/snapshotstore/blob"
|
||||
)
|
||||
|
||||
// TestStorePutGetStat 验证 localfs blob 存储的基本读写流程。
|
||||
func TestStorePutGetStat(t *testing.T) {
|
||||
store, err := New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatalf("create store: %v", err)
|
||||
}
|
||||
|
||||
info, err := store.Put(context.Background(), "nested/file.txt", bytes.NewReader([]byte("hello")), blob.PutOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("put object: %v", err)
|
||||
}
|
||||
if info.Revision == "" {
|
||||
t.Fatalf("expected revision to be generated")
|
||||
}
|
||||
|
||||
stat, err := store.Stat(context.Background(), "nested/file.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("stat object: %v", err)
|
||||
}
|
||||
if stat.Revision != info.Revision {
|
||||
t.Fatalf("expected stat revision %s, got %s", info.Revision, stat.Revision)
|
||||
}
|
||||
|
||||
reader, _, err := store.Get(context.Background(), "nested/file.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("get object: %v", err)
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
t.Fatalf("read object: %v", err)
|
||||
}
|
||||
if string(data) != "hello" {
|
||||
t.Fatalf("expected object content hello, got %s", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
// TestStorePutIfMatch 验证 localfs blob 存储的条件写入。
|
||||
func TestStorePutIfMatch(t *testing.T) {
|
||||
store, err := New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatalf("create store: %v", err)
|
||||
}
|
||||
|
||||
info, err := store.Put(context.Background(), "file.txt", bytes.NewReader([]byte("v1")), blob.PutOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("put initial object: %v", err)
|
||||
}
|
||||
|
||||
if _, err := store.Put(context.Background(), "file.txt", bytes.NewReader([]byte("v2")), blob.PutOptions{IfMatch: "stale"}); !errors.Is(err, blob.ErrConditionNotMet) {
|
||||
t.Fatalf("expected ErrConditionNotMet, got %v", err)
|
||||
}
|
||||
|
||||
nextInfo, err := store.Put(context.Background(), "file.txt", bytes.NewReader([]byte("v2")), blob.PutOptions{IfMatch: info.Revision})
|
||||
if err != nil {
|
||||
t.Fatalf("put with correct if-match: %v", err)
|
||||
}
|
||||
if nextInfo.Revision == info.Revision {
|
||||
t.Fatalf("expected revision to change after overwrite")
|
||||
}
|
||||
}
|
||||
173
internal/syncer/config.go
Normal file
173
internal/syncer/config.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package syncer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultBranch 是默认 Git 分支名。
|
||||
DefaultBranch = "master"
|
||||
// DefaultRemoteName 是默认 Git 远端名。
|
||||
DefaultRemoteName = "origin"
|
||||
// DefaultHeadKey 是默认同步头文件名。
|
||||
DefaultHeadKey = "head.json"
|
||||
)
|
||||
|
||||
const (
|
||||
// TargetKindGit 表示 Git 同步目标。
|
||||
TargetKindGit = "git"
|
||||
// TargetKindLocalFS 表示本地文件系统同步目标。
|
||||
TargetKindLocalFS = "localfs"
|
||||
)
|
||||
|
||||
// Config 描述整个同步系统的运行配置。
|
||||
type Config struct {
|
||||
Targets []TargetConfig
|
||||
}
|
||||
|
||||
// TargetConfig 描述单个同步目标的配置。
|
||||
type TargetConfig struct {
|
||||
Kind string
|
||||
Enabled bool
|
||||
Schedule ScheduleConfig
|
||||
Git *GitTargetConfig
|
||||
LocalFS *LocalFSTargetConfig
|
||||
}
|
||||
|
||||
// ScheduleConfig 描述自动同步调度配置。
|
||||
type ScheduleConfig struct {
|
||||
AutoSync bool
|
||||
Interval time.Duration
|
||||
}
|
||||
|
||||
// GitTargetConfig 描述 Git 同步目标配置。
|
||||
type GitTargetConfig struct {
|
||||
RepoPath string
|
||||
RepoURL string
|
||||
Branch string
|
||||
RemoteName string
|
||||
AuthorName string
|
||||
AuthorEmail string
|
||||
Auth GitAuthConfig
|
||||
}
|
||||
|
||||
// GitAuthConfig 描述 Git 鉴权配置。
|
||||
type GitAuthConfig struct {
|
||||
Method string
|
||||
Username string
|
||||
Password string
|
||||
Token string
|
||||
SSHKeyPath string
|
||||
SSHKeyPassword string
|
||||
}
|
||||
|
||||
// LocalFSTargetConfig 描述本地文件系统同步目标配置。
|
||||
type LocalFSTargetConfig struct {
|
||||
Namespace string
|
||||
HeadKey string
|
||||
RootPath string
|
||||
}
|
||||
|
||||
// Normalize 返回带默认值的配置副本。
|
||||
func (c Config) Normalize() Config {
|
||||
if len(c.Targets) == 0 {
|
||||
return Config{}
|
||||
}
|
||||
|
||||
targets := make([]TargetConfig, 0, len(c.Targets))
|
||||
for _, target := range c.Targets {
|
||||
targets = append(targets, target.Normalize())
|
||||
}
|
||||
|
||||
return Config{Targets: targets}
|
||||
}
|
||||
|
||||
// Target 返回指定 kind 的目标配置。
|
||||
func (c Config) Target(targetKind string) (TargetConfig, error) {
|
||||
for _, target := range c.Targets {
|
||||
if target.Kind == targetKind {
|
||||
return target, nil
|
||||
}
|
||||
}
|
||||
return TargetConfig{}, fmt.Errorf("%w: %s", ErrTargetNotFound, targetKind)
|
||||
}
|
||||
|
||||
// Normalize 返回带默认值的目标配置副本。
|
||||
func (t TargetConfig) Normalize() TargetConfig {
|
||||
target := t
|
||||
if target.Kind == "" {
|
||||
target.Kind = TargetKindGit
|
||||
}
|
||||
if target.Schedule.Interval < 0 {
|
||||
target.Schedule.Interval = 0
|
||||
}
|
||||
if target.Kind == TargetKindGit && target.Git != nil {
|
||||
gitConfig := *target.Git
|
||||
if strings.TrimSpace(gitConfig.Branch) == "" {
|
||||
gitConfig.Branch = DefaultBranch
|
||||
}
|
||||
if strings.TrimSpace(gitConfig.RemoteName) == "" {
|
||||
gitConfig.RemoteName = DefaultRemoteName
|
||||
}
|
||||
target.Git = &gitConfig
|
||||
}
|
||||
if target.Kind == TargetKindLocalFS && target.LocalFS != nil {
|
||||
storeConfig := *target.LocalFS
|
||||
if strings.TrimSpace(storeConfig.Namespace) == "" {
|
||||
storeConfig.Namespace = target.Kind
|
||||
}
|
||||
if strings.TrimSpace(storeConfig.HeadKey) == "" {
|
||||
storeConfig.HeadKey = DefaultHeadKey
|
||||
}
|
||||
target.LocalFS = &storeConfig
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
// Validate 校验目标配置。
|
||||
func (t TargetConfig) Validate() error {
|
||||
switch t.Kind {
|
||||
case TargetKindGit:
|
||||
if t.Git == nil {
|
||||
return errors.New("git target config is required")
|
||||
}
|
||||
if strings.TrimSpace(t.Git.RepoPath) == "" {
|
||||
return errors.New("git repo path is required")
|
||||
}
|
||||
case TargetKindLocalFS:
|
||||
if t.LocalFS == nil {
|
||||
return errors.New("localfs target config is required")
|
||||
}
|
||||
if strings.TrimSpace(t.LocalFS.RootPath) == "" {
|
||||
return errors.New("localfs root path is required")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", ErrUnsupportedBackend, t.Kind)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ready 判断目标是否具备执行同步的必要信息。
|
||||
func (t TargetConfig) Ready() bool {
|
||||
if !t.Enabled {
|
||||
return false
|
||||
}
|
||||
|
||||
switch t.Kind {
|
||||
case TargetKindGit:
|
||||
if t.Git == nil {
|
||||
return false
|
||||
}
|
||||
return strings.TrimSpace(t.Git.RepoPath) != "" && strings.TrimSpace(t.Git.RepoURL) != ""
|
||||
case TargetKindLocalFS:
|
||||
if t.LocalFS == nil {
|
||||
return false
|
||||
}
|
||||
return strings.TrimSpace(t.LocalFS.RootPath) != ""
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
184
internal/syncer/engine/sync_engine.go
Normal file
184
internal/syncer/engine/sync_engine.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"voidraft/internal/syncer/backend"
|
||||
"voidraft/internal/syncer/merge"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
const defaultMaxAttempts = 3
|
||||
|
||||
// Logger 描述同步引擎依赖的最小日志接口。
|
||||
type Logger interface {
|
||||
Debug(message string, args ...interface{})
|
||||
Info(message string, args ...interface{})
|
||||
Warning(message string, args ...interface{})
|
||||
Error(message string, args ...interface{})
|
||||
}
|
||||
|
||||
// Options 描述同步引擎构造选项。
|
||||
type Options struct {
|
||||
Logger Logger
|
||||
MaxAttempts int
|
||||
}
|
||||
|
||||
// SyncOptions 描述一次同步执行参数。
|
||||
type SyncOptions struct {
|
||||
CommitMessage string
|
||||
}
|
||||
|
||||
// Result 描述同步引擎执行结果。
|
||||
type Result struct {
|
||||
LocalChanged bool
|
||||
RemoteChanged bool
|
||||
AppliedToLocal bool
|
||||
Published bool
|
||||
ConflictCount int
|
||||
Revision string
|
||||
}
|
||||
|
||||
// SyncEngine 负责执行一次完整的同步闭环。
|
||||
type SyncEngine struct {
|
||||
backend backend.Backend
|
||||
store snapshot.Store
|
||||
snapshotter snapshot.Snapshotter
|
||||
merger merge.Merger
|
||||
logger Logger
|
||||
maxAttempts int
|
||||
}
|
||||
|
||||
// NewSyncEngine 创建新的同步引擎实例。
|
||||
func NewSyncEngine(
|
||||
backendInstance backend.Backend,
|
||||
store snapshot.Store,
|
||||
snapshotter snapshot.Snapshotter,
|
||||
merger merge.Merger,
|
||||
options Options,
|
||||
) *SyncEngine {
|
||||
maxAttempts := options.MaxAttempts
|
||||
if maxAttempts <= 0 {
|
||||
maxAttempts = defaultMaxAttempts
|
||||
}
|
||||
|
||||
return &SyncEngine{
|
||||
backend: backendInstance,
|
||||
store: store,
|
||||
snapshotter: snapshotter,
|
||||
merger: merger,
|
||||
logger: options.Logger,
|
||||
maxAttempts: maxAttempts,
|
||||
}
|
||||
}
|
||||
|
||||
// Sync 执行同步,并在远端版本竞争时自动重试。
|
||||
func (e *SyncEngine) Sync(ctx context.Context, options SyncOptions) (*Result, error) {
|
||||
var lastErr error
|
||||
|
||||
for attempt := 1; attempt <= e.maxAttempts; attempt++ {
|
||||
result, retry, err := e.syncOnce(ctx, options)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if retry && errors.Is(err, backend.ErrRevisionConflict) {
|
||||
lastErr = err
|
||||
if e.logger != nil {
|
||||
e.logger.Warning("sync retry after revision conflict, attempt %d/%d", attempt, e.maxAttempts)
|
||||
}
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if lastErr == nil {
|
||||
lastErr = backend.ErrRevisionConflict
|
||||
}
|
||||
return nil, lastErr
|
||||
}
|
||||
|
||||
// syncOnce 执行一次同步尝试。
|
||||
func (e *SyncEngine) syncOnce(ctx context.Context, options SyncOptions) (*Result, bool, error) {
|
||||
localSnapshot, err := e.snapshotter.Export(ctx)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("export local snapshot: %w", err)
|
||||
}
|
||||
|
||||
localDigest, err := snapshot.Digest(localSnapshot)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("digest local snapshot: %w", err)
|
||||
}
|
||||
|
||||
remoteDir, err := os.MkdirTemp("", "voidraft-sync-remote-*")
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer os.RemoveAll(remoteDir)
|
||||
|
||||
remoteState, err := e.backend.DownloadLatest(ctx, remoteDir)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("download remote snapshot: %w", err)
|
||||
}
|
||||
|
||||
remoteSnapshot := snapshot.New()
|
||||
if remoteState.Exists {
|
||||
remoteSnapshot, err = e.store.Read(ctx, remoteDir)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("read remote snapshot: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
remoteDigest, err := snapshot.Digest(remoteSnapshot)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("digest remote snapshot: %w", err)
|
||||
}
|
||||
|
||||
mergedSnapshot, report, err := e.merger.Merge(ctx, localSnapshot, remoteSnapshot)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("merge snapshot: %w", err)
|
||||
}
|
||||
|
||||
mergedDigest, err := snapshot.Digest(mergedSnapshot)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("digest merged snapshot: %w", err)
|
||||
}
|
||||
|
||||
appliedToLocal := localDigest != mergedDigest
|
||||
if appliedToLocal {
|
||||
if err := e.snapshotter.Apply(ctx, mergedSnapshot); err != nil {
|
||||
return nil, false, fmt.Errorf("apply merged snapshot: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
stageDir, err := os.MkdirTemp("", "voidraft-sync-stage-*")
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer os.RemoveAll(stageDir)
|
||||
|
||||
if err := e.store.Write(ctx, stageDir, mergedSnapshot); err != nil {
|
||||
return nil, false, fmt.Errorf("write merged snapshot: %w", err)
|
||||
}
|
||||
|
||||
publishedState, err := e.backend.Upload(ctx, stageDir, backend.PublishOptions{
|
||||
ExpectedRevision: remoteState.Revision,
|
||||
Message: options.CommitMessage,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, backend.ErrRevisionConflict) {
|
||||
return nil, true, err
|
||||
}
|
||||
return nil, false, fmt.Errorf("upload merged snapshot: %w", err)
|
||||
}
|
||||
|
||||
return &Result{
|
||||
LocalChanged: appliedToLocal,
|
||||
RemoteChanged: remoteDigest != mergedDigest,
|
||||
AppliedToLocal: appliedToLocal,
|
||||
Published: remoteState != publishedState,
|
||||
ConflictCount: report.Conflicts,
|
||||
Revision: publishedState.Revision,
|
||||
}, false, nil
|
||||
}
|
||||
16
internal/syncer/errors.go
Normal file
16
internal/syncer/errors.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package syncer
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrTargetNotFound 表示目标不存在。
|
||||
ErrTargetNotFound = errors.New("sync target not found")
|
||||
// ErrTargetDisabled 表示目标未启用。
|
||||
ErrTargetDisabled = errors.New("sync target is disabled")
|
||||
// ErrTargetNotReady 表示目标缺少必要配置。
|
||||
ErrTargetNotReady = errors.New("sync target is not ready")
|
||||
// ErrUnsupportedBackend 表示后端类型未实现。
|
||||
ErrUnsupportedBackend = errors.New("sync backend is not supported")
|
||||
// ErrUnsupportedDriver 表示后端驱动未实现。
|
||||
ErrUnsupportedDriver = errors.New("sync driver is not supported")
|
||||
)
|
||||
19
internal/syncer/merge/merger.go
Normal file
19
internal/syncer/merge/merger.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package merge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
// Report 描述一次合并中的统计信息。
|
||||
type Report struct {
|
||||
Added int
|
||||
Updated int
|
||||
Deleted int
|
||||
Conflicts int
|
||||
}
|
||||
|
||||
// Merger 描述快照合并策略。
|
||||
type Merger interface {
|
||||
Merge(ctx context.Context, local *snapshot.Snapshot, remote *snapshot.Snapshot) (*snapshot.Snapshot, Report, error)
|
||||
}
|
||||
98
internal/syncer/merge/updated_at_wins.go
Normal file
98
internal/syncer/merge/updated_at_wins.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package merge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"time"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
// UpdatedAtWinsMerger 使用 updated_at 作为默认冲突解决依据。
|
||||
type UpdatedAtWinsMerger struct{}
|
||||
|
||||
// NewUpdatedAtWinsMerger 创建新的默认合并器。
|
||||
func NewUpdatedAtWinsMerger() *UpdatedAtWinsMerger {
|
||||
return &UpdatedAtWinsMerger{}
|
||||
}
|
||||
|
||||
// Merge 合并本地与远端快照。
|
||||
func (m *UpdatedAtWinsMerger) Merge(ctx context.Context, local *snapshot.Snapshot, remote *snapshot.Snapshot) (*snapshot.Snapshot, Report, error) {
|
||||
_ = ctx
|
||||
|
||||
localSnapshot := snapshot.Clone(local)
|
||||
remoteSnapshot := snapshot.Clone(remote)
|
||||
|
||||
index := make(map[string]snapshot.Record)
|
||||
report := Report{}
|
||||
|
||||
for _, kind := range sortedKinds(localSnapshot, remoteSnapshot) {
|
||||
for _, record := range localSnapshot.Resources[kind] {
|
||||
index[recordKey(kind, record.ID)] = snapshot.CloneRecord(record)
|
||||
}
|
||||
for _, remoteRecord := range remoteSnapshot.Resources[kind] {
|
||||
key := recordKey(kind, remoteRecord.ID)
|
||||
localRecord, exists := index[key]
|
||||
if !exists {
|
||||
index[key] = snapshot.CloneRecord(remoteRecord)
|
||||
report.Added++
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case remoteRecord.UpdatedAt.After(localRecord.UpdatedAt):
|
||||
index[key] = snapshot.CloneRecord(remoteRecord)
|
||||
report.Updated++
|
||||
case remoteRecord.UpdatedAt.Equal(localRecord.UpdatedAt):
|
||||
if snapshot.RecordDigest(localRecord) != snapshot.RecordDigest(remoteRecord) {
|
||||
report.Conflicts++
|
||||
}
|
||||
default:
|
||||
if remoteRecord.DeletedAt != nil && localRecord.DeletedAt == nil {
|
||||
report.Deleted++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
merged := snapshot.New()
|
||||
for _, key := range sortedKeys(index) {
|
||||
record := index[key]
|
||||
merged.Resources[record.Kind] = append(merged.Resources[record.Kind], snapshot.CloneRecord(record))
|
||||
}
|
||||
merged.CreatedAt = time.Now()
|
||||
|
||||
return merged, report, nil
|
||||
}
|
||||
|
||||
// sortedKinds 返回两个快照内的全部资源类型集合。
|
||||
func sortedKinds(local *snapshot.Snapshot, remote *snapshot.Snapshot) []string {
|
||||
index := make(map[string]struct{})
|
||||
for kind := range local.Resources {
|
||||
index[kind] = struct{}{}
|
||||
}
|
||||
for kind := range remote.Resources {
|
||||
index[kind] = struct{}{}
|
||||
}
|
||||
|
||||
kinds := make([]string, 0, len(index))
|
||||
for kind := range index {
|
||||
kinds = append(kinds, kind)
|
||||
}
|
||||
sort.Strings(kinds)
|
||||
return kinds
|
||||
}
|
||||
|
||||
// sortedKeys 返回稳定排序后的索引键集合。
|
||||
func sortedKeys(index map[string]snapshot.Record) []string {
|
||||
keys := make([]string, 0, len(index))
|
||||
for key := range index {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
// recordKey 生成 record 的稳定索引键。
|
||||
func recordKey(kind string, id string) string {
|
||||
return kind + ":" + id
|
||||
}
|
||||
50
internal/syncer/merge/updated_at_wins_test.go
Normal file
50
internal/syncer/merge/updated_at_wins_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package merge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
// TestUpdatedAtWinsMergerMerge 验证较新的记录会覆盖较旧记录。
|
||||
func TestUpdatedAtWinsMergerMerge(t *testing.T) {
|
||||
localRecord, err := snapshot.NewRecord("documents", "doc-1", map[string]interface{}{
|
||||
"uuid": "doc-1",
|
||||
"updated_at": time.Date(2026, 3, 29, 9, 0, 0, 0, time.UTC).Format(time.RFC3339),
|
||||
"title": "local",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("build local record: %v", err)
|
||||
}
|
||||
|
||||
remoteRecord, err := snapshot.NewRecord("documents", "doc-1", map[string]interface{}{
|
||||
"uuid": "doc-1",
|
||||
"updated_at": time.Date(2026, 3, 29, 10, 0, 0, 0, time.UTC).Format(time.RFC3339),
|
||||
"title": "remote",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("build remote record: %v", err)
|
||||
}
|
||||
|
||||
localSnapshot := snapshot.New()
|
||||
localSnapshot.Resources["documents"] = []snapshot.Record{localRecord}
|
||||
|
||||
remoteSnapshot := snapshot.New()
|
||||
remoteSnapshot.Resources["documents"] = []snapshot.Record{remoteRecord}
|
||||
|
||||
merger := NewUpdatedAtWinsMerger()
|
||||
merged, report, err := merger.Merge(context.Background(), localSnapshot, remoteSnapshot)
|
||||
if err != nil {
|
||||
t.Fatalf("merge snapshot: %v", err)
|
||||
}
|
||||
|
||||
if report.Updated != 1 {
|
||||
t.Fatalf("expected updated report to be 1, got %d", report.Updated)
|
||||
}
|
||||
|
||||
record := merged.Resources["documents"][0]
|
||||
if got := record.Values["title"]; got != "remote" {
|
||||
t.Fatalf("expected remote title, got %v", got)
|
||||
}
|
||||
}
|
||||
61
internal/syncer/resource/adapter.go
Normal file
61
internal/syncer/resource/adapter.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
// Adapter 描述单类资源的导出与应用能力。
|
||||
type Adapter interface {
|
||||
Kind() string
|
||||
Export(ctx context.Context) ([]snapshot.Record, error)
|
||||
Apply(ctx context.Context, records []snapshot.Record) error
|
||||
}
|
||||
|
||||
// Registry 聚合所有资源适配器,并实现快照导入导出接口。
|
||||
type Registry struct {
|
||||
adapters []Adapter
|
||||
}
|
||||
|
||||
// NewRegistry 创建新的资源注册表。
|
||||
func NewRegistry(adapters ...Adapter) *Registry {
|
||||
return &Registry{adapters: adapters}
|
||||
}
|
||||
|
||||
// Export 导出所有已注册资源的快照。
|
||||
func (r *Registry) Export(ctx context.Context) (*snapshot.Snapshot, error) {
|
||||
snap := snapshot.New()
|
||||
|
||||
for _, adapter := range r.adapters {
|
||||
records, err := adapter.Export(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(records) == 0 {
|
||||
continue
|
||||
}
|
||||
sort.Slice(records, func(i int, j int) bool {
|
||||
return records[i].ID < records[j].ID
|
||||
})
|
||||
snap.Resources[adapter.Kind()] = records
|
||||
}
|
||||
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
// Apply 将快照内容应用到本地资源。
|
||||
func (r *Registry) Apply(ctx context.Context, snap *snapshot.Snapshot) error {
|
||||
if snap == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, adapter := range r.adapters {
|
||||
records := snap.Resources[adapter.Kind()]
|
||||
if err := adapter.Apply(ctx, records); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
117
internal/syncer/resource/document_adapter.go
Normal file
117
internal/syncer/resource/document_adapter.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"voidraft/internal/models/ent"
|
||||
"voidraft/internal/models/ent/document"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
const documentContentBlob = "content.md"
|
||||
|
||||
// DocumentAdapter 负责文档资源的快照导入导出。
|
||||
type DocumentAdapter struct {
|
||||
client *ent.Client
|
||||
}
|
||||
|
||||
// NewDocumentAdapter 创建文档适配器。
|
||||
func NewDocumentAdapter(client *ent.Client) *DocumentAdapter {
|
||||
return &DocumentAdapter{client: client}
|
||||
}
|
||||
|
||||
// Kind 返回适配器负责的资源类型。
|
||||
func (a *DocumentAdapter) Kind() string {
|
||||
return "documents"
|
||||
}
|
||||
|
||||
// Export 导出文档快照记录。
|
||||
func (a *DocumentAdapter) Export(ctx context.Context) ([]snapshot.Record, error) {
|
||||
documents, err := a.client.Document.Query().Order(document.ByUUID()).All(exportContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records := make([]snapshot.Record, 0, len(documents))
|
||||
for _, item := range documents {
|
||||
values := map[string]interface{}{
|
||||
document.FieldUUID: item.UUID,
|
||||
document.FieldCreatedAt: item.CreatedAt,
|
||||
document.FieldUpdatedAt: item.UpdatedAt,
|
||||
document.FieldTitle: item.Title,
|
||||
document.FieldLocked: item.Locked,
|
||||
}
|
||||
if item.DeletedAt != nil {
|
||||
values[document.FieldDeletedAt] = *item.DeletedAt
|
||||
}
|
||||
|
||||
record, err := snapshot.NewRecord(a.Kind(), item.UUID, values, map[string][]byte{
|
||||
documentContentBlob: []byte(item.Content),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build document record %s: %w", item.UUID, err)
|
||||
}
|
||||
records = append(records, record)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// Apply 将快照记录应用到本地文档表。
|
||||
func (a *DocumentAdapter) Apply(ctx context.Context, records []snapshot.Record) error {
|
||||
applyCtx := importContext(ctx)
|
||||
|
||||
for _, record := range records {
|
||||
found, err := a.client.Document.Query().Where(document.UUIDEQ(record.ID)).First(applyCtx)
|
||||
switch {
|
||||
case ent.IsNotFound(err):
|
||||
if err := a.create(applyCtx, record); err != nil {
|
||||
return err
|
||||
}
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
if shouldApplyRecord(found.UpdatedAt, record) {
|
||||
if err := a.update(applyCtx, found.ID, record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// create 创建新的文档记录。
|
||||
func (a *DocumentAdapter) create(ctx context.Context, record snapshot.Record) error {
|
||||
builder := a.client.Document.Create().
|
||||
SetUUID(record.ID).
|
||||
SetTitle(stringValue(record, document.FieldTitle)).
|
||||
SetContent(blobString(record, documentContentBlob)).
|
||||
SetLocked(boolValue(record, document.FieldLocked)).
|
||||
SetCreatedAt(stringValue(record, document.FieldCreatedAt)).
|
||||
SetUpdatedAt(stringValue(record, document.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
|
||||
// update 更新已有文档记录。
|
||||
func (a *DocumentAdapter) update(ctx context.Context, id int, record snapshot.Record) error {
|
||||
builder := a.client.Document.UpdateOneID(id).
|
||||
SetTitle(stringValue(record, document.FieldTitle)).
|
||||
SetContent(blobString(record, documentContentBlob)).
|
||||
SetLocked(boolValue(record, document.FieldLocked)).
|
||||
SetUpdatedAt(stringValue(record, document.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
} else {
|
||||
builder.ClearDeletedAt()
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
114
internal/syncer/resource/extension_adapter.go
Normal file
114
internal/syncer/resource/extension_adapter.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"voidraft/internal/models/ent"
|
||||
"voidraft/internal/models/ent/extension"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
// ExtensionAdapter 负责扩展资源的快照导入导出。
|
||||
type ExtensionAdapter struct {
|
||||
client *ent.Client
|
||||
}
|
||||
|
||||
// NewExtensionAdapter 创建扩展适配器。
|
||||
func NewExtensionAdapter(client *ent.Client) *ExtensionAdapter {
|
||||
return &ExtensionAdapter{client: client}
|
||||
}
|
||||
|
||||
// Kind 返回适配器负责的资源类型。
|
||||
func (a *ExtensionAdapter) Kind() string {
|
||||
return "extensions"
|
||||
}
|
||||
|
||||
// Export 导出扩展快照记录。
|
||||
func (a *ExtensionAdapter) Export(ctx context.Context) ([]snapshot.Record, error) {
|
||||
extensions, err := a.client.Extension.Query().Order(extension.ByUUID()).All(exportContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records := make([]snapshot.Record, 0, len(extensions))
|
||||
for _, item := range extensions {
|
||||
values := map[string]interface{}{
|
||||
extension.FieldUUID: item.UUID,
|
||||
extension.FieldCreatedAt: item.CreatedAt,
|
||||
extension.FieldUpdatedAt: item.UpdatedAt,
|
||||
extension.FieldName: item.Name,
|
||||
extension.FieldEnabled: item.Enabled,
|
||||
extension.FieldConfig: cloneMap(item.Config),
|
||||
}
|
||||
if item.DeletedAt != nil {
|
||||
values[extension.FieldDeletedAt] = *item.DeletedAt
|
||||
}
|
||||
|
||||
record, err := snapshot.NewRecord(a.Kind(), item.UUID, values, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build extension record %s: %w", item.UUID, err)
|
||||
}
|
||||
records = append(records, record)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// Apply 将快照记录应用到本地扩展表。
|
||||
func (a *ExtensionAdapter) Apply(ctx context.Context, records []snapshot.Record) error {
|
||||
applyCtx := importContext(ctx)
|
||||
|
||||
for _, record := range records {
|
||||
found, err := a.client.Extension.Query().Where(extension.UUIDEQ(record.ID)).First(applyCtx)
|
||||
switch {
|
||||
case ent.IsNotFound(err):
|
||||
if err := a.create(applyCtx, record); err != nil {
|
||||
return err
|
||||
}
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
if shouldApplyRecord(found.UpdatedAt, record) {
|
||||
if err := a.update(applyCtx, found.ID, record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// create 创建新的扩展记录。
|
||||
func (a *ExtensionAdapter) create(ctx context.Context, record snapshot.Record) error {
|
||||
builder := a.client.Extension.Create().
|
||||
SetUUID(record.ID).
|
||||
SetName(stringValue(record, extension.FieldName)).
|
||||
SetEnabled(boolValue(record, extension.FieldEnabled)).
|
||||
SetConfig(mapValue(record, extension.FieldConfig)).
|
||||
SetCreatedAt(stringValue(record, extension.FieldCreatedAt)).
|
||||
SetUpdatedAt(stringValue(record, extension.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
|
||||
// update 更新已有扩展记录。
|
||||
func (a *ExtensionAdapter) update(ctx context.Context, id int, record snapshot.Record) error {
|
||||
builder := a.client.Extension.UpdateOneID(id).
|
||||
SetName(stringValue(record, extension.FieldName)).
|
||||
SetEnabled(boolValue(record, extension.FieldEnabled)).
|
||||
SetConfig(mapValue(record, extension.FieldConfig)).
|
||||
SetUpdatedAt(stringValue(record, extension.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
} else {
|
||||
builder.ClearDeletedAt()
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
75
internal/syncer/resource/helpers.go
Normal file
75
internal/syncer/resource/helpers.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
"time"
|
||||
"voidraft/internal/models/schema/mixin"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
// importContext 构造同步导入所需的上下文。
|
||||
func importContext(ctx context.Context) context.Context {
|
||||
return mixin.SkipAutoUpdate(mixin.SkipSoftDelete(ctx))
|
||||
}
|
||||
|
||||
// exportContext 构造同步导出所需的上下文。
|
||||
func exportContext(ctx context.Context) context.Context {
|
||||
return mixin.SkipSoftDelete(ctx)
|
||||
}
|
||||
|
||||
// cloneMap 返回 map 的安全副本。
|
||||
func cloneMap(value map[string]interface{}) map[string]interface{} {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
return maps.Clone(value)
|
||||
}
|
||||
|
||||
// recordDeletedAtString 返回记录中的删除时间字符串。
|
||||
func recordDeletedAtString(record snapshot.Record) *string {
|
||||
if record.DeletedAt == nil {
|
||||
return nil
|
||||
}
|
||||
value := record.DeletedAt.Format(time.RFC3339)
|
||||
return &value
|
||||
}
|
||||
|
||||
// shouldApplyRecord 判断记录是否应该覆盖本地数据。
|
||||
func shouldApplyRecord(localUpdatedAt string, record snapshot.Record) bool {
|
||||
if localUpdatedAt == "" {
|
||||
return true
|
||||
}
|
||||
localTime, err := time.Parse(time.RFC3339, localUpdatedAt)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return record.UpdatedAt.After(localTime)
|
||||
}
|
||||
|
||||
// stringValue 从记录字段中读取字符串。
|
||||
func stringValue(record snapshot.Record, key string) string {
|
||||
value, _ := record.Values[key].(string)
|
||||
return value
|
||||
}
|
||||
|
||||
// boolValue 从记录字段中读取布尔值。
|
||||
func boolValue(record snapshot.Record, key string) bool {
|
||||
value, _ := record.Values[key].(bool)
|
||||
return value
|
||||
}
|
||||
|
||||
// mapValue 从记录字段中读取 map 值。
|
||||
func mapValue(record snapshot.Record, key string) map[string]interface{} {
|
||||
value, _ := record.Values[key].(map[string]interface{})
|
||||
return cloneMap(value)
|
||||
}
|
||||
|
||||
// blobString 读取记录中的文本 blob。
|
||||
func blobString(record snapshot.Record, name string) string {
|
||||
value, ok := record.Blobs[name]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return string(value)
|
||||
}
|
||||
135
internal/syncer/resource/keybinding_adapter.go
Normal file
135
internal/syncer/resource/keybinding_adapter.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"voidraft/internal/models/ent"
|
||||
"voidraft/internal/models/ent/keybinding"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
// KeyBindingAdapter 负责快捷键资源的快照导入导出。
|
||||
type KeyBindingAdapter struct {
|
||||
client *ent.Client
|
||||
}
|
||||
|
||||
// NewKeyBindingAdapter 创建快捷键适配器。
|
||||
func NewKeyBindingAdapter(client *ent.Client) *KeyBindingAdapter {
|
||||
return &KeyBindingAdapter{client: client}
|
||||
}
|
||||
|
||||
// Kind 返回适配器负责的资源类型。
|
||||
func (a *KeyBindingAdapter) Kind() string {
|
||||
return "keybindings"
|
||||
}
|
||||
|
||||
// Export 导出快捷键快照记录。
|
||||
func (a *KeyBindingAdapter) Export(ctx context.Context) ([]snapshot.Record, error) {
|
||||
keyBindings, err := a.client.KeyBinding.Query().Order(keybinding.ByUUID()).All(exportContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records := make([]snapshot.Record, 0, len(keyBindings))
|
||||
for _, item := range keyBindings {
|
||||
values := map[string]interface{}{
|
||||
keybinding.FieldUUID: item.UUID,
|
||||
keybinding.FieldCreatedAt: item.CreatedAt,
|
||||
keybinding.FieldUpdatedAt: item.UpdatedAt,
|
||||
keybinding.FieldName: item.Name,
|
||||
keybinding.FieldType: item.Type,
|
||||
keybinding.FieldKey: item.Key,
|
||||
keybinding.FieldMacos: item.Macos,
|
||||
keybinding.FieldWindows: item.Windows,
|
||||
keybinding.FieldLinux: item.Linux,
|
||||
keybinding.FieldExtension: item.Extension,
|
||||
keybinding.FieldEnabled: item.Enabled,
|
||||
keybinding.FieldPreventDefault: item.PreventDefault,
|
||||
keybinding.FieldScope: item.Scope,
|
||||
}
|
||||
if item.DeletedAt != nil {
|
||||
values[keybinding.FieldDeletedAt] = *item.DeletedAt
|
||||
}
|
||||
|
||||
record, err := snapshot.NewRecord(a.Kind(), item.UUID, values, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build keybinding record %s: %w", item.UUID, err)
|
||||
}
|
||||
records = append(records, record)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// Apply 将快照记录应用到本地快捷键表。
|
||||
func (a *KeyBindingAdapter) Apply(ctx context.Context, records []snapshot.Record) error {
|
||||
applyCtx := importContext(ctx)
|
||||
|
||||
for _, record := range records {
|
||||
found, err := a.client.KeyBinding.Query().Where(keybinding.UUIDEQ(record.ID)).First(applyCtx)
|
||||
switch {
|
||||
case ent.IsNotFound(err):
|
||||
if err := a.create(applyCtx, record); err != nil {
|
||||
return err
|
||||
}
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
if shouldApplyRecord(found.UpdatedAt, record) {
|
||||
if err := a.update(applyCtx, found.ID, record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// create 创建新的快捷键记录。
|
||||
func (a *KeyBindingAdapter) create(ctx context.Context, record snapshot.Record) error {
|
||||
builder := a.client.KeyBinding.Create().
|
||||
SetUUID(record.ID).
|
||||
SetName(stringValue(record, keybinding.FieldName)).
|
||||
SetType(stringValue(record, keybinding.FieldType)).
|
||||
SetKey(stringValue(record, keybinding.FieldKey)).
|
||||
SetMacos(stringValue(record, keybinding.FieldMacos)).
|
||||
SetWindows(stringValue(record, keybinding.FieldWindows)).
|
||||
SetLinux(stringValue(record, keybinding.FieldLinux)).
|
||||
SetExtension(stringValue(record, keybinding.FieldExtension)).
|
||||
SetEnabled(boolValue(record, keybinding.FieldEnabled)).
|
||||
SetPreventDefault(boolValue(record, keybinding.FieldPreventDefault)).
|
||||
SetScope(stringValue(record, keybinding.FieldScope)).
|
||||
SetCreatedAt(stringValue(record, keybinding.FieldCreatedAt)).
|
||||
SetUpdatedAt(stringValue(record, keybinding.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
|
||||
// update 更新已有快捷键记录。
|
||||
func (a *KeyBindingAdapter) update(ctx context.Context, id int, record snapshot.Record) error {
|
||||
builder := a.client.KeyBinding.UpdateOneID(id).
|
||||
SetName(stringValue(record, keybinding.FieldName)).
|
||||
SetType(stringValue(record, keybinding.FieldType)).
|
||||
SetKey(stringValue(record, keybinding.FieldKey)).
|
||||
SetMacos(stringValue(record, keybinding.FieldMacos)).
|
||||
SetWindows(stringValue(record, keybinding.FieldWindows)).
|
||||
SetLinux(stringValue(record, keybinding.FieldLinux)).
|
||||
SetExtension(stringValue(record, keybinding.FieldExtension)).
|
||||
SetEnabled(boolValue(record, keybinding.FieldEnabled)).
|
||||
SetPreventDefault(boolValue(record, keybinding.FieldPreventDefault)).
|
||||
SetScope(stringValue(record, keybinding.FieldScope)).
|
||||
SetUpdatedAt(stringValue(record, keybinding.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
} else {
|
||||
builder.ClearDeletedAt()
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
114
internal/syncer/resource/theme_adapter.go
Normal file
114
internal/syncer/resource/theme_adapter.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"voidraft/internal/models/ent"
|
||||
"voidraft/internal/models/ent/theme"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
// ThemeAdapter 负责主题资源的快照导入导出。
|
||||
type ThemeAdapter struct {
|
||||
client *ent.Client
|
||||
}
|
||||
|
||||
// NewThemeAdapter 创建主题适配器。
|
||||
func NewThemeAdapter(client *ent.Client) *ThemeAdapter {
|
||||
return &ThemeAdapter{client: client}
|
||||
}
|
||||
|
||||
// Kind 返回适配器负责的资源类型。
|
||||
func (a *ThemeAdapter) Kind() string {
|
||||
return "themes"
|
||||
}
|
||||
|
||||
// Export 导出主题快照记录。
|
||||
func (a *ThemeAdapter) Export(ctx context.Context) ([]snapshot.Record, error) {
|
||||
themes, err := a.client.Theme.Query().Order(theme.ByUUID()).All(exportContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records := make([]snapshot.Record, 0, len(themes))
|
||||
for _, item := range themes {
|
||||
values := map[string]interface{}{
|
||||
theme.FieldUUID: item.UUID,
|
||||
theme.FieldCreatedAt: item.CreatedAt,
|
||||
theme.FieldUpdatedAt: item.UpdatedAt,
|
||||
theme.FieldName: item.Name,
|
||||
theme.FieldType: item.Type.String(),
|
||||
theme.FieldColors: cloneMap(item.Colors),
|
||||
}
|
||||
if item.DeletedAt != nil {
|
||||
values[theme.FieldDeletedAt] = *item.DeletedAt
|
||||
}
|
||||
|
||||
record, err := snapshot.NewRecord(a.Kind(), item.UUID, values, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build theme record %s: %w", item.UUID, err)
|
||||
}
|
||||
records = append(records, record)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// Apply 将快照记录应用到本地主题表。
|
||||
func (a *ThemeAdapter) Apply(ctx context.Context, records []snapshot.Record) error {
|
||||
applyCtx := importContext(ctx)
|
||||
|
||||
for _, record := range records {
|
||||
found, err := a.client.Theme.Query().Where(theme.UUIDEQ(record.ID)).First(applyCtx)
|
||||
switch {
|
||||
case ent.IsNotFound(err):
|
||||
if err := a.create(applyCtx, record); err != nil {
|
||||
return err
|
||||
}
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
if shouldApplyRecord(found.UpdatedAt, record) {
|
||||
if err := a.update(applyCtx, found.ID, record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// create 创建新的主题记录。
|
||||
func (a *ThemeAdapter) create(ctx context.Context, record snapshot.Record) error {
|
||||
builder := a.client.Theme.Create().
|
||||
SetUUID(record.ID).
|
||||
SetName(stringValue(record, theme.FieldName)).
|
||||
SetType(theme.Type(stringValue(record, theme.FieldType))).
|
||||
SetColors(mapValue(record, theme.FieldColors)).
|
||||
SetCreatedAt(stringValue(record, theme.FieldCreatedAt)).
|
||||
SetUpdatedAt(stringValue(record, theme.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
|
||||
// update 更新已有主题记录。
|
||||
func (a *ThemeAdapter) update(ctx context.Context, id int, record snapshot.Record) error {
|
||||
builder := a.client.Theme.UpdateOneID(id).
|
||||
SetName(stringValue(record, theme.FieldName)).
|
||||
SetType(theme.Type(stringValue(record, theme.FieldType))).
|
||||
SetColors(mapValue(record, theme.FieldColors)).
|
||||
SetUpdatedAt(stringValue(record, theme.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
} else {
|
||||
builder.ClearDeletedAt()
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
75
internal/syncer/scheduler/ticker.go
Normal file
75
internal/syncer/scheduler/ticker.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Ticker 提供可重启的周期任务调度器。
|
||||
type Ticker struct {
|
||||
mu sync.Mutex
|
||||
cancel context.CancelFunc
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// NewTicker 创建新的调度器实例。
|
||||
func NewTicker() *Ticker {
|
||||
return &Ticker{}
|
||||
}
|
||||
|
||||
// Start 启动周期任务。
|
||||
func (t *Ticker) Start(interval time.Duration, job func(context.Context) error) {
|
||||
if interval <= 0 || job == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.Stop()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan struct{})
|
||||
ticker := time.NewTicker(interval)
|
||||
|
||||
t.mu.Lock()
|
||||
t.cancel = cancel
|
||||
t.done = done
|
||||
t.mu.Unlock()
|
||||
|
||||
go func() {
|
||||
defer close(done)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
_ = job(ctx)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop 停止当前任务。
|
||||
func (t *Ticker) Stop() {
|
||||
t.mu.Lock()
|
||||
cancel := t.cancel
|
||||
done := t.done
|
||||
t.cancel = nil
|
||||
t.done = nil
|
||||
t.mu.Unlock()
|
||||
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
if done != nil {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
|
||||
// Running 返回调度器是否正在运行。
|
||||
func (t *Ticker) Running() bool {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
return t.cancel != nil
|
||||
}
|
||||
248
internal/syncer/snapshot/snapshot.go
Normal file
248
internal/syncer/snapshot/snapshot.go
Normal file
@@ -0,0 +1,248 @@
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// CurrentVersion 是当前快照格式版本。
|
||||
CurrentVersion = 1
|
||||
)
|
||||
|
||||
// Snapshot 描述一次完整的数据快照。
|
||||
type Snapshot struct {
|
||||
Version int
|
||||
CreatedAt time.Time
|
||||
Resources map[string][]Record
|
||||
}
|
||||
|
||||
// Record 描述单条资源记录。
|
||||
type Record struct {
|
||||
Kind string
|
||||
ID string
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
Values map[string]interface{}
|
||||
Blobs map[string][]byte
|
||||
}
|
||||
|
||||
// Snapshotter 描述快照导出与应用接口。
|
||||
type Snapshotter interface {
|
||||
Export(ctx context.Context) (*Snapshot, error)
|
||||
Apply(ctx context.Context, snap *Snapshot) error
|
||||
}
|
||||
|
||||
// New 创建新的空快照。
|
||||
func New() *Snapshot {
|
||||
return &Snapshot{
|
||||
Version: CurrentVersion,
|
||||
CreatedAt: time.Now(),
|
||||
Resources: make(map[string][]Record),
|
||||
}
|
||||
}
|
||||
|
||||
// NewRecord 根据业务字段构造规范化记录。
|
||||
func NewRecord(kind string, id string, values map[string]interface{}, blobs map[string][]byte) (Record, error) {
|
||||
if strings.TrimSpace(kind) == "" {
|
||||
return Record{}, errors.New("record kind is required")
|
||||
}
|
||||
|
||||
normalizedValues := cloneValues(values)
|
||||
if id == "" {
|
||||
uuid, _ := normalizedValues["uuid"].(string)
|
||||
id = uuid
|
||||
}
|
||||
if id == "" {
|
||||
return Record{}, errors.New("record id is required")
|
||||
}
|
||||
normalizedValues["uuid"] = id
|
||||
|
||||
updatedAt, err := parseRequiredTime(normalizedValues["updated_at"])
|
||||
if err != nil {
|
||||
return Record{}, fmt.Errorf("record %s updated_at: %w", id, err)
|
||||
}
|
||||
|
||||
deletedAt, err := parseOptionalTime(normalizedValues["deleted_at"])
|
||||
if err != nil {
|
||||
return Record{}, fmt.Errorf("record %s deleted_at: %w", id, err)
|
||||
}
|
||||
|
||||
return Record{
|
||||
Kind: kind,
|
||||
ID: id,
|
||||
UpdatedAt: updatedAt,
|
||||
DeletedAt: deletedAt,
|
||||
Values: normalizedValues,
|
||||
Blobs: cloneBlobs(blobs),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Clone 返回快照的深拷贝。
|
||||
func Clone(snap *Snapshot) *Snapshot {
|
||||
if snap == nil {
|
||||
return New()
|
||||
}
|
||||
|
||||
cloned := &Snapshot{
|
||||
Version: snap.Version,
|
||||
CreatedAt: snap.CreatedAt,
|
||||
Resources: make(map[string][]Record, len(snap.Resources)),
|
||||
}
|
||||
for kind, records := range snap.Resources {
|
||||
copied := make([]Record, 0, len(records))
|
||||
for _, record := range records {
|
||||
copied = append(copied, CloneRecord(record))
|
||||
}
|
||||
cloned.Resources[kind] = copied
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
// CloneRecord 返回记录的深拷贝。
|
||||
func CloneRecord(record Record) Record {
|
||||
return Record{
|
||||
Kind: record.Kind,
|
||||
ID: record.ID,
|
||||
UpdatedAt: record.UpdatedAt,
|
||||
DeletedAt: cloneTime(record.DeletedAt),
|
||||
Values: cloneValues(record.Values),
|
||||
Blobs: cloneBlobs(record.Blobs),
|
||||
}
|
||||
}
|
||||
|
||||
// Digest 计算快照的稳定摘要。
|
||||
func Digest(snap *Snapshot) (string, error) {
|
||||
normalized := Clone(snap)
|
||||
|
||||
type digestRecord struct {
|
||||
ID string `json:"id"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
DeletedAt *string `json:"deleted_at,omitempty"`
|
||||
Values map[string]interface{} `json:"values"`
|
||||
Blobs map[string][]byte `json:"blobs,omitempty"`
|
||||
}
|
||||
|
||||
payload := struct {
|
||||
Version int `json:"version"`
|
||||
Resources map[string][]digestRecord `json:"resources"`
|
||||
}{
|
||||
Version: normalized.Version,
|
||||
Resources: make(map[string][]digestRecord, len(normalized.Resources)),
|
||||
}
|
||||
|
||||
for _, kind := range sortedKinds(normalized.Resources) {
|
||||
records := normalized.Resources[kind]
|
||||
sort.Slice(records, func(i int, j int) bool {
|
||||
return records[i].ID < records[j].ID
|
||||
})
|
||||
|
||||
items := make([]digestRecord, 0, len(records))
|
||||
for _, record := range records {
|
||||
var deletedAt *string
|
||||
if record.DeletedAt != nil {
|
||||
value := record.DeletedAt.Format(time.RFC3339)
|
||||
deletedAt = &value
|
||||
}
|
||||
items = append(items, digestRecord{
|
||||
ID: record.ID,
|
||||
UpdatedAt: record.UpdatedAt.Format(time.RFC3339),
|
||||
DeletedAt: deletedAt,
|
||||
Values: record.Values,
|
||||
Blobs: record.Blobs,
|
||||
})
|
||||
}
|
||||
payload.Resources[kind] = items
|
||||
}
|
||||
|
||||
data, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sum := sha256.Sum256(data)
|
||||
return hex.EncodeToString(sum[:]), nil
|
||||
}
|
||||
|
||||
// RecordDigest 计算单条记录的稳定摘要。
|
||||
func RecordDigest(record Record) string {
|
||||
sum, err := Digest(&Snapshot{
|
||||
Version: CurrentVersion,
|
||||
Resources: map[string][]Record{
|
||||
record.Kind: {CloneRecord(record)},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// cloneValues 复制字段 map。
|
||||
func cloneValues(values map[string]interface{}) map[string]interface{} {
|
||||
if values == nil {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
return maps.Clone(values)
|
||||
}
|
||||
|
||||
// cloneBlobs 复制二进制 blob 集合。
|
||||
func cloneBlobs(blobs map[string][]byte) map[string][]byte {
|
||||
if len(blobs) == 0 {
|
||||
return nil
|
||||
}
|
||||
copied := make(map[string][]byte, len(blobs))
|
||||
for name, blob := range blobs {
|
||||
copied[name] = append([]byte(nil), blob...)
|
||||
}
|
||||
return copied
|
||||
}
|
||||
|
||||
// cloneTime 复制时间指针。
|
||||
func cloneTime(value *time.Time) *time.Time {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
cloned := *value
|
||||
return &cloned
|
||||
}
|
||||
|
||||
// parseRequiredTime 解析必填时间字段。
|
||||
func parseRequiredTime(value interface{}) (time.Time, error) {
|
||||
text, _ := value.(string)
|
||||
if text == "" {
|
||||
return time.Time{}, errors.New("time value is required")
|
||||
}
|
||||
return time.Parse(time.RFC3339, text)
|
||||
}
|
||||
|
||||
// parseOptionalTime 解析可选时间字段。
|
||||
func parseOptionalTime(value interface{}) (*time.Time, error) {
|
||||
text, _ := value.(string)
|
||||
if text == "" {
|
||||
return nil, nil
|
||||
}
|
||||
parsed, err := time.Parse(time.RFC3339, text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &parsed, nil
|
||||
}
|
||||
|
||||
// sortedKinds 返回稳定排序后的资源类型列表。
|
||||
func sortedKinds(resources map[string][]Record) []string {
|
||||
kinds := make([]string, 0, len(resources))
|
||||
for kind := range resources {
|
||||
kinds = append(kinds, kind)
|
||||
}
|
||||
sort.Strings(kinds)
|
||||
return kinds
|
||||
}
|
||||
266
internal/syncer/snapshot/store.go
Normal file
266
internal/syncer/snapshot/store.go
Normal file
@@ -0,0 +1,266 @@
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const manifestFileName = "manifest.json"
|
||||
|
||||
// Store 描述快照落盘与读取能力。
|
||||
type Store interface {
|
||||
Read(ctx context.Context, root string) (*Snapshot, error)
|
||||
Write(ctx context.Context, root string, snap *Snapshot) error
|
||||
}
|
||||
|
||||
// FileStore 提供基于目录树的快照读写实现。
|
||||
type FileStore struct{}
|
||||
|
||||
type manifest struct {
|
||||
Version int `json:"version"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// NewFileStore 创建新的文件快照存储。
|
||||
func NewFileStore() *FileStore {
|
||||
return &FileStore{}
|
||||
}
|
||||
|
||||
// Read 从目录树读取快照。
|
||||
func (s *FileStore) Read(ctx context.Context, root string) (*Snapshot, error) {
|
||||
_ = ctx
|
||||
|
||||
info, err := os.Stat(root)
|
||||
if os.IsNotExist(err) {
|
||||
return New(), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return New(), nil
|
||||
}
|
||||
|
||||
snap := New()
|
||||
if err := s.readManifest(root, snap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
kind := entry.Name()
|
||||
records, err := s.readKind(filepath.Join(root, kind), kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(records) == 0 {
|
||||
continue
|
||||
}
|
||||
sort.Slice(records, func(i int, j int) bool {
|
||||
return records[i].ID < records[j].ID
|
||||
})
|
||||
snap.Resources[kind] = records
|
||||
}
|
||||
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
// Write 将快照写入目录树。
|
||||
func (s *FileStore) Write(ctx context.Context, root string, snap *Snapshot) error {
|
||||
_ = ctx
|
||||
|
||||
if err := os.RemoveAll(root); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(root, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.writeManifest(root, snap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, kind := range sortedKinds(snap.Resources) {
|
||||
kindDir := filepath.Join(root, kind)
|
||||
if err := os.MkdirAll(kindDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
records := append([]Record(nil), snap.Resources[kind]...)
|
||||
sort.Slice(records, func(i int, j int) bool {
|
||||
return records[i].ID < records[j].ID
|
||||
})
|
||||
|
||||
for _, record := range records {
|
||||
if len(record.Blobs) == 0 {
|
||||
if err := writeJSON(filepath.Join(kindDir, record.ID+".json"), record.Values); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
recordDir := filepath.Join(kindDir, record.ID)
|
||||
if err := os.MkdirAll(recordDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeJSON(filepath.Join(recordDir, "record.json"), record.Values); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blobNames := make([]string, 0, len(record.Blobs))
|
||||
for name := range record.Blobs {
|
||||
blobNames = append(blobNames, name)
|
||||
}
|
||||
sort.Strings(blobNames)
|
||||
|
||||
for _, blobName := range blobNames {
|
||||
if err := os.WriteFile(filepath.Join(recordDir, blobName), record.Blobs[blobName], 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readManifest 读取快照 manifest。
|
||||
func (s *FileStore) readManifest(root string, snap *Snapshot) error {
|
||||
data, err := os.ReadFile(filepath.Join(root, manifestFileName))
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var current manifest
|
||||
if err := json.Unmarshal(data, ¤t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
snap.Version = current.Version
|
||||
if current.CreatedAt != "" {
|
||||
createdAt, err := time.Parse(time.RFC3339, current.CreatedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snap.CreatedAt = createdAt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeManifest 写入快照 manifest。
|
||||
func (s *FileStore) writeManifest(root string, snap *Snapshot) error {
|
||||
payload := manifest{
|
||||
Version: snap.Version,
|
||||
CreatedAt: snap.CreatedAt.Format(time.RFC3339),
|
||||
}
|
||||
return writeJSON(filepath.Join(root, manifestFileName), payload)
|
||||
}
|
||||
|
||||
// readKind 读取单类资源目录。
|
||||
func (s *FileStore) readKind(root string, kind string) ([]Record, error) {
|
||||
entries, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records := make([]Record, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
switch {
|
||||
case entry.IsDir():
|
||||
record, err := s.readBlobRecord(filepath.Join(root, entry.Name()), kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records = append(records, record)
|
||||
case strings.HasSuffix(entry.Name(), ".json"):
|
||||
record, err := s.readFlatRecord(filepath.Join(root, entry.Name()), kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// readFlatRecord 读取单文件记录。
|
||||
func (s *FileStore) readFlatRecord(path string, kind string) (Record, error) {
|
||||
values, err := readValues(path)
|
||||
if err != nil {
|
||||
return Record{}, err
|
||||
}
|
||||
|
||||
id := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
|
||||
return NewRecord(kind, id, values, nil)
|
||||
}
|
||||
|
||||
// readBlobRecord 读取目录型记录。
|
||||
func (s *FileStore) readBlobRecord(root string, kind string) (Record, error) {
|
||||
values, err := readValues(filepath.Join(root, "record.json"))
|
||||
if err != nil {
|
||||
return Record{}, err
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return Record{}, err
|
||||
}
|
||||
|
||||
blobs := make(map[string][]byte)
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || entry.Name() == "record.json" {
|
||||
continue
|
||||
}
|
||||
content, err := os.ReadFile(filepath.Join(root, entry.Name()))
|
||||
if err != nil {
|
||||
return Record{}, err
|
||||
}
|
||||
blobs[entry.Name()] = content
|
||||
}
|
||||
|
||||
return NewRecord(kind, filepath.Base(root), values, blobs)
|
||||
}
|
||||
|
||||
// readValues 读取 JSON 字段集合。
|
||||
func readValues(path string) (map[string]interface{}, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
values := make(map[string]interface{})
|
||||
if err := json.Unmarshal(data, &values); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// writeJSON 将结构体格式化写入 JSON 文件。
|
||||
func writeJSON(path string, value interface{}) error {
|
||||
data, err := json.MarshalIndent(value, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = append(data, '\n')
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, data, 0644)
|
||||
}
|
||||
59
internal/syncer/snapshot/store_test.go
Normal file
59
internal/syncer/snapshot/store_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestFileStoreReadWrite 验证目录树快照可以稳定往返。
|
||||
func TestFileStoreReadWrite(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
|
||||
documentRecord, err := NewRecord("documents", "doc-1", map[string]interface{}{
|
||||
"uuid": "doc-1",
|
||||
"updated_at": time.Date(2026, 3, 29, 10, 0, 0, 0, time.UTC).Format(time.RFC3339),
|
||||
"title": "hello",
|
||||
}, map[string][]byte{
|
||||
"content.md": []byte("world"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("build document record: %v", err)
|
||||
}
|
||||
|
||||
themeRecord, err := NewRecord("themes", "theme-1", map[string]interface{}{
|
||||
"uuid": "theme-1",
|
||||
"updated_at": time.Date(2026, 3, 29, 10, 1, 0, 0, time.UTC).Format(time.RFC3339),
|
||||
"name": "dark",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("build theme record: %v", err)
|
||||
}
|
||||
|
||||
snap := New()
|
||||
snap.Resources["documents"] = []Record{documentRecord}
|
||||
snap.Resources["themes"] = []Record{themeRecord}
|
||||
|
||||
store := NewFileStore()
|
||||
if err := store.Write(context.Background(), root, snap); err != nil {
|
||||
t.Fatalf("write snapshot: %v", err)
|
||||
}
|
||||
|
||||
loaded, err := store.Read(context.Background(), root)
|
||||
if err != nil {
|
||||
t.Fatalf("read snapshot: %v", err)
|
||||
}
|
||||
|
||||
originalDigest, err := Digest(snap)
|
||||
if err != nil {
|
||||
t.Fatalf("digest original snapshot: %v", err)
|
||||
}
|
||||
loadedDigest, err := Digest(loaded)
|
||||
if err != nil {
|
||||
t.Fatalf("digest loaded snapshot: %v", err)
|
||||
}
|
||||
|
||||
if originalDigest != loadedDigest {
|
||||
t.Fatalf("expected digests to match, got %s != %s", originalDigest, loadedDigest)
|
||||
}
|
||||
}
|
||||
20
internal/syncer/types.go
Normal file
20
internal/syncer/types.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package syncer
|
||||
|
||||
// Logger 描述同步模块需要的最小日志接口。
|
||||
type Logger interface {
|
||||
Debug(message string, args ...interface{})
|
||||
Info(message string, args ...interface{})
|
||||
Warning(message string, args ...interface{})
|
||||
Error(message string, args ...interface{})
|
||||
}
|
||||
|
||||
// SyncResult 描述一次同步的结果。
|
||||
type SyncResult struct {
|
||||
TargetID string
|
||||
LocalChanged bool
|
||||
RemoteChanged bool
|
||||
AppliedToLocal bool
|
||||
Published bool
|
||||
ConflictCount int
|
||||
Revision string
|
||||
}
|
||||
Reference in New Issue
Block a user