luaskills 0.4.1

LuaSkills core runtime library for loading, invoking, and managing Lua skill packages.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;

use crate::dependency::types::{DependencyScope, DependencySourceType};

/// Supported archive formats used by LuaSkills dependency packages.
/// LuaSkills 依赖包支持的归档格式。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum DependencyArchiveType {
    /// Treat the downloaded payload as one zip archive.
    /// 将下载载荷视为 zip 归档。
    Zip,
    /// Treat the downloaded payload as one tar.gz archive.
    /// 将下载载荷视为 tar.gz 归档。
    TarGz,
    /// Treat the downloaded payload as one raw single file.
    /// 将下载载荷视为单个原始文件。
    #[default]
    Raw,
}

/// One exported file rule used both for installation and existence detection.
/// 同时用于安装与存在性检测的单个导出文件规则。
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DependencyExportSpec {
    /// Relative path inside the downloaded archive or raw file payload.
    /// 下载归档或原始文件载荷内部的相对路径。
    pub archive_path: String,
    /// Relative destination path under the dependency root.
    /// 依赖根目录下的相对目标路径。
    pub target_path: String,
    /// Whether the exported file should be marked executable on Unix platforms.
    /// 是否应在 Unix 平台上把导出文件标记为可执行。
    #[serde(default)]
    pub executable: bool,
}

/// One platform-specific package record resolved before actual download.
/// 在实际下载前解析得到的单个平台包记录。
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DependencyPackageSpec {
    /// Archive format of the package payload.
    /// 包载荷对应的归档格式。
    #[serde(default)]
    pub archive_type: DependencyArchiveType,
    /// Exact GitHub asset file name used for github_release downloads.
    /// 用于 github_release 下载的精确 GitHub 资产文件名。
    #[serde(default)]
    pub asset_name: Option<String>,
    /// Exact download URL used for direct url or resolved skilllist packages.
    /// 用于直接 url 或已解析 skilllist 包的精确下载地址。
    #[serde(default)]
    pub url: Option<String>,
    /// Exported files that must be installed and later used for existence checks.
    /// 必须被安装、并在之后用于存在性检测的导出文件列表。
    #[serde(default)]
    pub exports: Vec<DependencyExportSpec>,
}

/// GitHub-release source configuration used by one dependency.
/// 单个依赖使用的 GitHub Release 来源配置。
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GithubReleaseSourceSpec {
    /// GitHub repository in `owner/repo` format.
    /// `owner
    /// repo` 形式的 GitHub 仓库标识。
    pub repo: String,
    /// Optional explicit release API URL. When omitted the host-configured GitHub API base is used.
    /// 可选的显式 release API 地址;省略时使用宿主配置的 GitHub API 基址。
    #[serde(default)]
    pub tag_api: Option<String>,
}

/// Direct-URL source configuration used by one dependency.
/// 单个依赖使用的直接 URL 来源配置。
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct UrlSourceSpec {}

/// Skill-list source configuration used by one dependency.
/// 单个依赖使用的 skilllist 来源配置。
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SkillListSourceSpec {
    /// URL of the remote skill/tool dependency list file.
    /// 远程 skill/tool 依赖列表文件的下载地址。
    pub url: String,
    /// Package key inside the downloaded list file.
    /// 下载后的列表文件内部使用的包键名。
    pub package: String,
}

/// Unified source specification used by all dependency kinds.
/// 所有依赖类型共用的统一来源描述。
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DependencySourceSpec {
    /// Upstream source type used for package resolution.
    /// 包解析时使用的上游来源类型。
    #[serde(rename = "type")]
    pub source_type: DependencySourceType,
    /// Optional GitHub-release source payload.
    /// 可选的 GitHub Release 来源载荷。
    #[serde(default)]
    pub github: Option<GithubReleaseSourceSpec>,
    /// Optional direct-URL source payload.
    /// 可选的直接 URL 来源载荷。
    #[serde(default)]
    pub url: Option<UrlSourceSpec>,
    /// Optional skilllist source payload.
    /// 可选的 skilllist 来源载荷。
    #[serde(default)]
    pub skilllist: Option<SkillListSourceSpec>,
}

/// Tool dependency declaration loaded from one skill package.
/// 从单个 skill 包加载的工具依赖声明。
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolDependencySpec {
    /// Stable dependency name.
    /// 稳定依赖名称。
    pub name: String,
    /// Optional expected version string used for display and asset interpolation.
    /// 用于展示和资产名插值的可选预期版本字符串。
    #[serde(default)]
    pub version: Option<String>,
    /// Whether the dependency is required for the skill to load.
    /// 当前依赖是否为技能加载所必需。
    #[serde(default = "default_required_dependency")]
    pub required: bool,
    /// Install scope of the current dependency. Tool dependencies default to skill-private.
    /// 当前依赖的安装作用域。工具依赖默认使用 skill 私有作用域。
    #[serde(default = "default_tool_dependency_scope")]
    pub scope: DependencyScope,
    /// Dependency source specification.
    /// 依赖来源描述。
    pub source: DependencySourceSpec,
    /// Platform-specific package descriptors.
    /// 平台对应的包描述表。
    #[serde(default)]
    pub packages: BTreeMap<String, DependencyPackageSpec>,
}

/// Lua package dependency declaration loaded from one skill package.
/// 从单个 skill 包加载的 Lua 库依赖声明。
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LuaDependencySpec {
    /// Stable dependency name.
    /// 稳定依赖名称。
    pub name: String,
    /// Optional expected version string used for display and asset interpolation.
    /// 用于展示和资产名插值的可选预期版本字符串。
    #[serde(default)]
    pub version: Option<String>,
    /// Whether the dependency is required for the skill to load.
    /// 当前依赖是否为技能加载所必需。
    #[serde(default = "default_required_dependency")]
    pub required: bool,
    /// Install scope of the current dependency. Lua dependencies default to skill-private.
    /// 当前依赖的安装作用域。Lua 依赖默认使用 skill 私有作用域。
    #[serde(default = "default_runtime_library_scope")]
    pub scope: DependencyScope,
    /// Dependency source specification.
    /// 依赖来源描述。
    pub source: DependencySourceSpec,
    /// Platform-specific package descriptors.
    /// 平台对应的包描述表。
    #[serde(default)]
    pub packages: BTreeMap<String, DependencyPackageSpec>,
}

/// FFI/native library dependency declaration loaded from one skill package.
/// 从单个 skill 包加载的 FFI/原生库依赖声明。
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FfiDependencySpec {
    /// Stable dependency name.
    /// 稳定依赖名称。
    pub name: String,
    /// Optional expected version string used for display and asset interpolation.
    /// 用于展示和资产名插值的可选预期版本字符串。
    #[serde(default)]
    pub version: Option<String>,
    /// Whether the dependency is required for the skill to load.
    /// 当前依赖是否为技能加载所必需。
    #[serde(default = "default_required_dependency")]
    pub required: bool,
    /// Install scope of the current dependency. FFI dependencies default to skill-private.
    /// 当前依赖的安装作用域。FFI 依赖默认使用 skill 私有作用域。
    #[serde(default = "default_runtime_library_scope")]
    pub scope: DependencyScope,
    /// Dependency source specification.
    /// 依赖来源描述。
    pub source: DependencySourceSpec,
    /// Platform-specific package descriptors.
    /// 平台对应的包描述表。
    #[serde(default)]
    pub packages: BTreeMap<String, DependencyPackageSpec>,
}

/// Full dependencies.yaml payload loaded from one skill package.
/// 从单个 skill 包加载的完整 dependencies.yaml 载荷。
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct SkillDependencyManifest {
    /// Tool dependencies such as rg or ast-grep.
    /// 例如 rg 或 ast-grep 一类的工具依赖。
    #[serde(default)]
    pub tool_dependencies: Vec<ToolDependencySpec>,
    /// Lua module dependencies installed under one host-managed lua root.
    /// 安装到宿主管理 Lua 根目录下的 Lua 模块依赖。
    #[serde(default)]
    pub lua_dependencies: Vec<LuaDependencySpec>,
    /// FFI/native library dependencies installed under one host-managed ffi root.
    /// 安装到宿主管理 FFI 根目录下的原生库依赖。
    #[serde(default)]
    pub ffi_dependencies: Vec<FfiDependencySpec>,
}

/// One skilllist package manifest downloaded from one remote list file.
/// 从远程列表文件下载得到的单个 skilllist 包清单。
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SkillListPackageManifest {
    /// Optional resolved version declared by the list provider.
    /// 列表提供方声明的可选已解析版本号。
    #[serde(default)]
    pub version: Option<String>,
    /// Platform-specific package descriptors resolved from the list provider.
    /// 从列表提供方解析到的平台包描述表。
    #[serde(default)]
    pub packages: BTreeMap<String, DependencyPackageSpec>,
}

/// Remote skilllist file payload that maps package keys to package manifests.
/// 把包键映射到包清单的远程 skilllist 文件载荷。
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct SkillListIndexFile {
    /// Package map resolved from the remote list file.
    /// 从远程列表文件解析得到的包映射。
    #[serde(default)]
    pub packages: BTreeMap<String, SkillListPackageManifest>,
}

/// Return the default dependency required flag.
/// 返回依赖 required 标志的默认值。
fn default_required_dependency() -> bool {
    true
}

/// Return the default install scope used by tool dependencies.
/// 返回工具依赖默认使用的安装作用域。
fn default_tool_dependency_scope() -> DependencyScope {
    DependencyScope::Skill
}

/// Return the default install scope used by Lua and FFI runtime-library dependencies.
/// 返回 Lua 与 FFI 运行时库依赖默认使用的安装作用域。
fn default_runtime_library_scope() -> DependencyScope {
    DependencyScope::Skill
}

impl SkillDependencyManifest {
    /// Load one dependency manifest from `dependencies.yaml`.
    /// 从 `dependencies.yaml` 加载一份依赖清单。
    pub fn load_from_path(path: &Path) -> Result<Self, String> {
        let yaml_text = fs::read_to_string(path)
            .map_err(|error| format!("Failed to read {}: {}", path.display(), error))?;
        serde_yaml::from_str(&yaml_text)
            .map_err(|error| format!("Failed to parse {}: {}", path.display(), error))
    }

    /// Return whether the manifest contains any declared dependency.
    /// 返回当前清单是否声明了任何依赖。
    pub fn is_empty(&self) -> bool {
        self.tool_dependencies.is_empty()
            && self.lua_dependencies.is_empty()
            && self.ffi_dependencies.is_empty()
    }
}

impl ToolDependencySpec {
    /// Return the platform package descriptor selected for one normalized platform key.
    /// 返回按标准平台键选中的平台包描述。
    pub fn package_for_platform(&self, platform_key: &str) -> Option<&DependencyPackageSpec> {
        self.packages.get(platform_key)
    }
}

impl LuaDependencySpec {
    /// Return the platform package descriptor selected for one normalized platform key.
    /// 返回按标准平台键选中的平台包描述。
    pub fn package_for_platform(&self, platform_key: &str) -> Option<&DependencyPackageSpec> {
        self.packages.get(platform_key)
    }
}

impl FfiDependencySpec {
    /// Return the platform package descriptor selected for one normalized platform key.
    /// 返回按标准平台键选中的平台包描述。
    pub fn package_for_platform(&self, platform_key: &str) -> Option<&DependencyPackageSpec> {
        self.packages.get(platform_key)
    }
}

#[cfg(test)]
mod tests {
    use super::SkillDependencyManifest;

    /// Verify that the new dependency manifest format parses tool/lua/ffi groups correctly.
    /// 验证新的依赖清单格式能正确解析 tool/lua/ffi 三个分组。
    #[test]
    fn parse_dependency_manifest_groups() {
        let yaml_text = r#"
tool_dependencies:
  - name: ast-grep
    scope: skill
    source:
      type: github_release
      github:
        repo: ast-grep/ast-grep
    packages:
      windows-x64:
        archive_type: zip
        asset_name: app-x86_64-pc-windows-msvc.zip
        exports:
          - archive_path: ast-grep.exe
            target_path: bin/ast-grep.exe
lua_dependencies:
  - name: lua-cjson
    required: false
    scope: skill
    source:
      type: url
      url: {}
    packages:
      windows-x64:
        archive_type: raw
        url: https://example.com/cjson.lua
        exports:
          - archive_path: cjson.lua
            target_path: share/lua/cjson.lua
ffi_dependencies:
  - name: example-lib
    source:
      type: skilllist
      skilllist:
        url: https://example.com/index.yaml
        package: example-lib
"#;
        let manifest: SkillDependencyManifest =
            serde_yaml::from_str(yaml_text).expect("manifest should parse");
        assert_eq!(manifest.tool_dependencies.len(), 1);
        assert_eq!(manifest.lua_dependencies.len(), 1);
        assert_eq!(manifest.ffi_dependencies.len(), 1);
        assert_eq!(
            manifest.tool_dependencies[0].scope,
            crate::dependency::types::DependencyScope::Skill
        );
        assert_eq!(
            manifest.lua_dependencies[0].scope,
            crate::dependency::types::DependencyScope::Skill
        );
        assert_eq!(
            manifest.tool_dependencies[0]
                .package_for_platform("windows-x64")
                .expect("windows package should exist")
                .exports[0]
                .target_path,
            "bin/ast-grep.exe"
        );
    }

    /// Verify that missing scope fields still fall back to the expected per-kind defaults.
    /// 验证省略 scope 字段时仍会回落到按依赖类型定义的默认作用域。
    #[test]
    fn dependency_scope_defaults_match_kind_policy() {
        let yaml_text = r#"
tool_dependencies:
  - name: rg
    source:
      type: url
      url: {}
    packages: {}
lua_dependencies:
  - name: demo-lua
    source:
      type: url
      url: {}
    packages: {}
ffi_dependencies:
  - name: demo-ffi
    source:
      type: url
      url: {}
    packages: {}
"#;
        let manifest: SkillDependencyManifest =
            serde_yaml::from_str(yaml_text).expect("manifest should parse");
        assert_eq!(
            manifest.tool_dependencies[0].scope,
            crate::dependency::types::DependencyScope::Skill
        );
        assert_eq!(
            manifest.lua_dependencies[0].scope,
            crate::dependency::types::DependencyScope::Skill
        );
        assert_eq!(
            manifest.ffi_dependencies[0].scope,
            crate::dependency::types::DependencyScope::Skill
        );
    }
}