default_readme
2026/9/13 15:10:16 网站建设 项目流程

default_readme

【免费下载链接】gleam⭐️ A friendly language for building type-safe, scalable systems!项目地址: https://gitcode.com/GitHub_Trending/gl/gleam

gleam add default_readme@1
import default_readme pub fn main() -> Nil { // TODO: An example of the project in use }

Further documentation can be found at https://default-readme.hexdocs.pm.

Development

gleam run # Run the project gleam test # Run the tests
可以看到它由四个典型部分组成: 1. **徽章区**:Hex 包版本徽章(`img.shields.io/hexpm/v/...`)与 Hex Docs 徽章; 2. **安装与使用示例**:`gleam add <name>@1` 安装命令,以及一个包含 `// TODO` 占位注释的 `main` 函数示例; 3. **文档链接**:指向 `<https://<name>.hexdocs.pm>` 的占位链接; 4. **Development 小节**:`gleam run` / `gleam test` 的开发命令提示。 ### 1.2 模板的源码定义 这份 README 并非散落在各处的静态文件,而是由 Gleam 编译器在运行 `gleam new` 时按模板动态生成的,定义位于 [compiler-cli/src/new.rs](https://link.gitcode.com/i/e274fc94328e741481a09c720e0a86c6) 的 `default_readme(project_name: &str)` 函数: ```rust pub fn default_readme(project_name: &str) -> String { let project_name_with_dashes = project_name.replace('_', "-"); format!( r#"# {project_name} ... ```sh gleam add {project_name}@1
import {project_name} pub fn main() -> Nil {{ // TODO: An example of the project in use }}

Further documentation can be found at https://{project_name_with_dashes}.hexdocs.pm/. ... "#, ) }

两个关键细节值得注意: - 函数接收项目名 `project_name`,并将下划线替换为短横线(`replace('_', "-")`)生成 Hex Docs 域名,因为 Hex Docs 域名约定使用短横线风格(如 `default-readme.hexdocs.pm`); - README 中所有出现项目名的位置都由占位符动态填充,因此**每个新项目的默认 README 都是"个性化"的**——这给后续的发布拦截提供了精确匹配的基础。 从 `test/publishing_default_readme/gleam.toml` 可以看到,该测试项目名为 `default_readme`、版本 `1.0.0`、依赖 `gleam_stdlib >= 0.44.0 and < 2.0.0`,是标准的库模板配置,说明这个测试场景就是一个"开箱即用、尚未打磨"的典型新项目。 --- ## 二、核心机制:`gleam publish` 如何拦截默认 README ### 2.1 校验入口 发布流程的入口是 [compiler-cli/src/publish.rs](https://link.gitcode.com/i/02ab9522ad659f7ed32e6d1ea9d0a4b5) 的 `command` 函数,它在执行任何网络操作之前,先依次做若干本地校验: ```rust let should_publish = check_for_gleam_prefix(&config)? && check_for_version_zero(&config)? && check_repo_url(&config, i_am_sure)?; check_for_invalid_readme(&config, paths)?;

其中check_for_invalid_readme就是负责 README 合法性检查的函数。注意它在确认should_publish之后才被调用,且一旦返回错误会直接中断发布流程——README 不合法时,整个发布被硬性拒绝,而不是弹窗询问

2.2 三种非法情况

check_for_invalid_readme的完整实现位于 compiler-cli/src/publish.rs:

fn check_for_invalid_readme(config: &PackageConfig, paths: &ProjectPaths) -> Result<(), Error> { let normalise = |string: String| { string .trim() .replace("\r\n", "") .replace("\n", "") .replace("\t", "") .replace(" ", "") }; let project_readme = match fs::read(paths.readme()) { Err(Error::FileIo { err: Some(message), .. }) if message.contains("No such file or directory") => { return Err(Error::CannotPublishWithInvalidReadme { reason: InvalidReadmeReason::Missing, }); } Err(error) => return Err(error), Ok(project_readme) => project_readme, }; let normalised_project_readme = normalise(project_readme); if normalised_project_readme.is_empty() { return Err(Error::CannotPublishWithInvalidReadme { reason: InvalidReadmeReason::Empty, }); } let default_readme = default_readme(config.name.as_str()); if normalised_project_readme == normalise(default_readme) { return Err(Error::CannotPublishWithInvalidReadme { reason: InvalidReadmeReason::Default, }); } Ok(()) }

这段代码揭示了三个判定分支,对应 compiler-core/src/error.rs 中定义的枚举:

pub enum InvalidReadmeReason { Missing, // 项目根本没有 README Empty, // README 存在但(归一化后)为空 Default, // README 与 gleam new 生成的默认模板完全一致 }

2.3 关键设计:归一化(normalise)后的逐字符比较

默认 README 的识别方式非常巧妙,值得深入理解:

  1. 逐字符归一化normalise闭包对 README 内容执行trim(),并移除所有换行(\r\n\n)、制表符(\t)和空格();
  2. 模板比对:把当前项目的 README 归一化后,与"用当前包名现场重新生成的默认 README"同样归一化后的结果做完全相等比较;
  3. 动态生成而非硬编码:因为默认 README 是根据包名动态生成的,所以即便你把项目重命名、换徽章链接,只要内容结构仍是模板原样,比较结果依然相等,依然会被拦截。

换句话说,只删几个空格、改一行缩进是无法绕过检查的——归一化后的比较把所有空白差异都抹平了。你必须是真正重写了 README 的内容(哪怕只是加一段实质文字),才能通过校验。

2.4 错误提示与修复建议

当判定为Default时,编译器输出的诊断信息位于 compiler-core/src/error.rs:

  • 标题:Cannot publish with default README
  • 正文:You appear to be attempting to publish a package with the default README generated by the gleam new command. That is meant as a placeholder and a published package should have its own carefully written README.
  • 提示:Update your project's README to describe it before publishing

同理,MissingEmpty两种场景分别提示 "Cannot publish with no README" / "Cannot publish with empty README",并给出 "Add a README to your project before publishing." 或 "Update your project's README to describe it before publishing" 的修复建议。


三、仓库测试如何验证这一行为

3.1 测试脚本

test/publishing_default_readme/test.sh是这个场景的自动化验证脚本:

#!/bin/sh set -eu GLEAM_COMMAND=${GLEAM_COMMAND:-"cargo run --quiet --"} g() { echo "Running: $GLEAM_COMMAND $@" $GLEAM_COMMAND "$@" } echo Resetting the build directory to get to a known state rm -fr build echo Running publish should not publish anything if yes "n" | g publish; then echo "Expected publish to fail, but it succeeded" exit 1 fi echo echo Success! 💖 echo

脚本的验证逻辑很直白:

  1. 先删除build目录,让发布流程从干净状态开始(发布前构建目录会被重置);
  2. 通过管道向gleam publish喂入"n"(回答确认提示),同时用if ... then断言:如果发布命令意外成功,测试立即失败并输出 "Expected publish to fail, but it succeeded"
  3. 只有发布被拒绝(命令非零退出)才打印Success! 💖通过测试。

3.2 测试项目的源码设计

test/publishing_default_readme/src/default_readme.gleam里特意写了一个"看起来像默认 main"但又不是默认 main 的函数,注释点明了设计意图:

import gleam/io /// This tests that a project with a default readme doesn't get published. /// pub fn main() -> Nil { greeting() first_line() second_line() } fn greeting() { io.println("Hello from default_readme!") } fn first_line() { io.println("Here we have some additional code so that this is not mistaken") } fn second_line() { io.println("for a default main project, that would be rejected as well!") }

这个设计的精妙之处在于:Gleam 的发布检查是多层次的,除了默认 README,还有"默认 main 函数"检查(compiler-cli/src/publish.rs 的check_for_default_main,它会把只有一个io.println("Hello from <name>!")且无文档注释的 main 视为脚手架残留并拒绝发布)。该测试项目特意让 main 函数包含多行打印,从而把"默认 README"这一单独的失败原因从"默认 main"中隔离出来,确保测试失败是因为 README 校验而非其他检查。

3.3 同族测试:缺失与空 README

仓库中还提供了同系列的两个对照场景:

  • test/publishing_no_readme/:验证项目完全没有 README 时发布被拒(InvalidReadmeReason::Missing);
  • test/publishing_empty_readme/:验证 README 存在但内容为空时发布被拒(InvalidReadmeReason::Empty)。

它们的test.sh脚本与上述脚本结构完全相同,三者共同构成了 README 三种非法形态的完整测试矩阵,全部断言"发布必须失败"。


四、为什么 Gleam 要强制拦截默认 README

这个机制背后是 Hex 包生态的发布质量要求。从源码注释和错误信息可以归纳出三层原因:

  1. README 是包的门面:Hex 包页面会把 README 渲染成包的主介绍页,消费者通过它判断包的用途、API 概览和安装方式。一份gleam new模板 README 只有占位信息,无法传达任何真实内容;
  2. 防止占位内容流入生态:默认 README 中的// TODO注释、模板化徽章与链接对真实用户毫无价值。允许发布这类包会污染 Hex 的包质量;
  3. 与其它"未完工"检查配套:Gleam 在发布前还会检查todo表达式(CannotPublishTodo)、echo表达式(CannotPublishEcho)、空模块(CannotPublishEmptyModules)、默认 main 函数(CannotPublishWithDefaultMain)等。默认 README 拦截正是这套"禁止发布半成品"策略的一环,与 compiler-cli/src/publish.rs 中的相关检查逻辑相互呼应。

此外,从 compiler-cli/src/publish.rs 的project_files可以看到,发布打包时 README 是必须包含在 tarball 中的文件之一(add("README")add("README.md")add("README.txt")),它随包一起分发给所有使用者——这进一步解释了为什么它的内容质量如此重要。


五、实战:发布前如何正确处理 README

5.1 标准操作流程

对开发者而言,正确处理方式是发布前重写 README。参考本仓库中其他真实包的 README(如 test/publishing_src_symlink_escape/README.md、test/erlang_shipment_no_dev_deps/ 等),一份可发布的 README 通常包含:

# your_package 你的包的一句话简介:它解决什么问题、适合什么场景。 ## 安装 ```sh gleam add your_package

快速开始

import your_package pub fn main() { your_package.say_hello() }

文档

  • 完整 API 文档见 Hex Docs
  • 高级用法示例见 examples/ 目录

许可

该包基于 Apache-2.0 许可发布。

【免费下载链接】gleam⭐️ A friendly language for building type-safe, scalable systems!项目地址: https://gitcode.com/GitHub_Trending/gl/gleam

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询