[[Rustでテキストをside-by-side diff表示]] で作成した機能を[[クレート]]にして公開してみる。
## プロジェクト作成
libプロジェクトとしてまずは作る。
```console
cargo new --lib side-by-side-diff
```
依存関係として[[Similar]]と[[unicode-diff]]を追加。
```console
cd side-by-side-diff
cargo add similiar unicode-width
```
## 実装
`lib.rs`にコードを書く。ついでにテストコードも。
```rust
use similar::{ChangeTag, TextDiff};
use unicode_width::UnicodeWidthStr;
pub fn create_side_by_side_diff(text1: &str, text2: &str, max_width: usize) -> String {
TextDiff::from_lines(text1, text2)
.iter_all_changes()
.map(|change| {
let content = change.to_string().trim_end_matches('\n').to_string();
let width = max_width - (content.width() - content.chars().count());
match change.tag() {
ChangeTag::Delete => format!(
"{:>6} | {:<width$} | {:>6} | {:<blank_width$} |",
change.old_index().unwrap() + 1,
content,
"",
"",
blank_width = max_width,
width = width,
),
ChangeTag::Insert => format!(
"{:>6} | {:<blank_width$} | {:>6} | {:<width$} |",
"",
"",
change.new_index().unwrap() + 1,
content,
blank_width = max_width,
width = width
),
ChangeTag::Equal => format!(
"{:>6} | {token:<width$} | {:>6} | {token:<width$} |",
change.old_index().unwrap() + 1,
change.new_index().unwrap() + 1,
token = content,
width = width
),
}
})
.collect::<Vec<_>>()
.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let actual = create_side_by_side_diff(
"
あああ
bbb
ccc
ddd
いいいいいーeee
かかお
FFFFFFFFFF
ggg"
.trim_matches('\n'),
"
あああ
bbb
いいいいいーeee
かかか
ffffffffff
ggg"
.trim_matches('\n'),
20,
);
let expected = "
1 | あああ | 1 | あああ |
2 | bbb | 2 | bbb |
3 | ccc | | |
4 | ddd | | |
5 | いいいいいーeee | 3 | いいいいいーeee |
6 | かかお | | |
7 | FFFFFFFFFF | | |
| | 4 | かかか |
| | 5 | ffffffffff |
8 | ggg | 6 | ggg |
"
.trim_matches('\n');
assert_eq!(expected, actual);
}
}
```
## 公開
実装は不十分だが、今回の目的は[[クレート]]の公開実績を解除すること。なので、まずはこの状態でトライする。
<div class="link-card">
<div class="link-card-header">
<img src="https://doc.rust-lang.org/cargo/favicon.png" class="link-card-site-icon"/>
<span class="link-card-site-name">doc.rust-lang.org</span>
</div>
<div class="link-card-body">
<div class="link-card-content">
<div>
<p class="link-card-title">Publishing on crates.io - The Cargo Book</p>
</div>
<div class="link-card-description">
</div>
</div>
</div>
<a href="https://doc.rust-lang.org/cargo/reference/publishing.html"></a>
</div>
### アカウントとAPIトークンの作成
[[crates.io]]から[[GitHub]]のアカウント経由でログインする。APIトークンを作成しコピーする。
次に、コマンドラインからログインする。APIトークンを入力する。
```console
cargo login
```
`${CARGO_HOME}/.cargo/credentials`にAPIトークンが書き込まれる。
```toml
[registry]
token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```
### メタデータの入力
[[Cargo.toml]]に必要な情報を入力する。
- [x] licenseまたlicense-file
- [x] description
- [*] homepage
- [x] documentation
- [x] repository
- [x] readme
```toml
authors = ["tadashi-aikawa <
[email protected]>"]
description = "Create side-by-side diff text."
license = "MIT"
homepage = "https://github.com/tadashi-aikawa/side-by-side-diff"
documentation = "https://docs.rs/side-by-side-diff"
repository = "https://github.com/tadashi-aikawa/side-by-side-diff"
readme = "README.md"
```
### dry-run
まずはdry runをする。
```console
cargo publish --dry-run
```
### アップロード
問題なければ本実行する。
```console
cargo publish
```
エラーになった。
```
Caused by:
the remote server responded with an error: A verified email address is required to publish crates to crates.io. Visit https://crates.io/me to set and verify your email address.
```
メールアドレスからverifiedが必要らしい。
![[Pasted image 20230218195606.png]]
もう一度実行したら無事に公開された。
https://crates.io/crates/side-by-side-diff
## 利用
アップロードした[[クレート]]を利用できるか確かめてみる。
```console
cargo new --bin side-by-side-diff-use
cd side-by-side-diff-use
cargo add side-by-side-diff
```
コードを書く。
```rust
use side_by_side_diff::create_side_by_side_diff;
fn main() {
let diff = create_side_by_side_diff("aaa\niii\nuuu", "aaa\nii\nuuu", 20);
println!("{diff}");
}
```
実行する。
```console
$ cargo run
1 | aaa | 1 | aaa |
2 | iii | | |
| | 2 | ii |
3 | uuu | 3 | uuu |
```
## バージョンアップを自動化する
ローカルで以下の作業を自動化する。
- テスト
- ビルド
- バージョンのインクリメント
- タグの追加
- 変更点のコミット/プッシュ
[[Task]]を使う。[[Taskfile.yml]]を作成する。また、[[cargo-release]]もインストールされている前提。
```yaml
version: "3"
tasks:
default:
- task: help
help:
silent: true
cmds:
- task -l
ci:
desc: For CI
cmds:
- cargo test
- cargo build --release
release:
desc: |
Build
∟ [Ex] task release VERSION=1.2.3
∟ [Ex] task release VERSION=1.2.3-beta
deps:
- ci
cmds:
- cargo release version {{.VERSION}} -x --no-confirm
- task: ci
- git add .
- git commit -m "version {{.VERSION}}"
- git tag v{{.VERSION}} -m v{{.VERSION}}
- git push --tags
- git push
preconditions:
- sh: "[ {{.VERSION}} != '' ]"
msg: "VERSION is required."
```
## [[GitHub Actions]]でリリース
タグがpushされたら自動でリリースされるようにしたい。必要な作業は以下の通り。
- ログイン or 認証を通す何か
- [[cargo publishでAPIトークンを指定]]
- テスト
- ビルド
- 公開
- [[GitHub]]のReleaseページ更新
[[GitHub Actions]]のファイルを作成する。
`.github/workflows/release.yaml`
```yaml
name: "Release"
on:
push:
tags:
- "*"
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- run: cargo test
- run: cargo build --release
- run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```