tangled
alpha
login
or
join now
da157.id
/
porcelain
0
fork
atom
cli dollcode encoder and decoder
0
fork
atom
overview
issues
pulls
pipelines
init
0xda157
3 months ago
44e35950
+439
10 changed files
expand all
collapse all
unified
split
.gitignore
Cargo.lock
Cargo.toml
LICENSE
README.md
flake.lock
flake.nix
package.nix
src
lib.rs
main.rs
+1
.gitignore
···
1
1
+
/target
+132
Cargo.lock
···
1
1
+
# This file is automatically @generated by Cargo.
2
2
+
# It is not intended for manual editing.
3
3
+
version = 4
4
4
+
5
5
+
[[package]]
6
6
+
name = "anstream"
7
7
+
version = "0.6.21"
8
8
+
source = "registry+https://github.com/rust-lang/crates.io-index"
9
9
+
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
10
10
+
dependencies = [
11
11
+
"anstyle",
12
12
+
"anstyle-parse",
13
13
+
"anstyle-query",
14
14
+
"anstyle-wincon",
15
15
+
"colorchoice",
16
16
+
"is_terminal_polyfill",
17
17
+
"utf8parse",
18
18
+
]
19
19
+
20
20
+
[[package]]
21
21
+
name = "anstyle"
22
22
+
version = "1.0.13"
23
23
+
source = "registry+https://github.com/rust-lang/crates.io-index"
24
24
+
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
25
25
+
26
26
+
[[package]]
27
27
+
name = "anstyle-parse"
28
28
+
version = "0.2.7"
29
29
+
source = "registry+https://github.com/rust-lang/crates.io-index"
30
30
+
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
31
31
+
dependencies = [
32
32
+
"utf8parse",
33
33
+
]
34
34
+
35
35
+
[[package]]
36
36
+
name = "anstyle-query"
37
37
+
version = "1.1.5"
38
38
+
source = "registry+https://github.com/rust-lang/crates.io-index"
39
39
+
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
40
40
+
dependencies = [
41
41
+
"windows-sys",
42
42
+
]
43
43
+
44
44
+
[[package]]
45
45
+
name = "anstyle-wincon"
46
46
+
version = "3.0.11"
47
47
+
source = "registry+https://github.com/rust-lang/crates.io-index"
48
48
+
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
49
49
+
dependencies = [
50
50
+
"anstyle",
51
51
+
"once_cell_polyfill",
52
52
+
"windows-sys",
53
53
+
]
54
54
+
55
55
+
[[package]]
56
56
+
name = "clap"
57
57
+
version = "4.5.51"
58
58
+
source = "registry+https://github.com/rust-lang/crates.io-index"
59
59
+
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
60
60
+
dependencies = [
61
61
+
"clap_builder",
62
62
+
]
63
63
+
64
64
+
[[package]]
65
65
+
name = "clap_builder"
66
66
+
version = "4.5.51"
67
67
+
source = "registry+https://github.com/rust-lang/crates.io-index"
68
68
+
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
69
69
+
dependencies = [
70
70
+
"anstream",
71
71
+
"anstyle",
72
72
+
"clap_lex",
73
73
+
"strsim",
74
74
+
]
75
75
+
76
76
+
[[package]]
77
77
+
name = "clap_lex"
78
78
+
version = "0.7.6"
79
79
+
source = "registry+https://github.com/rust-lang/crates.io-index"
80
80
+
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
81
81
+
82
82
+
[[package]]
83
83
+
name = "colorchoice"
84
84
+
version = "1.0.4"
85
85
+
source = "registry+https://github.com/rust-lang/crates.io-index"
86
86
+
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
87
87
+
88
88
+
[[package]]
89
89
+
name = "is_terminal_polyfill"
90
90
+
version = "1.70.2"
91
91
+
source = "registry+https://github.com/rust-lang/crates.io-index"
92
92
+
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
93
93
+
94
94
+
[[package]]
95
95
+
name = "once_cell_polyfill"
96
96
+
version = "1.70.2"
97
97
+
source = "registry+https://github.com/rust-lang/crates.io-index"
98
98
+
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
99
99
+
100
100
+
[[package]]
101
101
+
name = "porcelain"
102
102
+
version = "0.1.0"
103
103
+
dependencies = [
104
104
+
"clap",
105
105
+
]
106
106
+
107
107
+
[[package]]
108
108
+
name = "strsim"
109
109
+
version = "0.11.1"
110
110
+
source = "registry+https://github.com/rust-lang/crates.io-index"
111
111
+
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
112
112
+
113
113
+
[[package]]
114
114
+
name = "utf8parse"
115
115
+
version = "0.2.2"
116
116
+
source = "registry+https://github.com/rust-lang/crates.io-index"
117
117
+
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
118
118
+
119
119
+
[[package]]
120
120
+
name = "windows-link"
121
121
+
version = "0.2.1"
122
122
+
source = "registry+https://github.com/rust-lang/crates.io-index"
123
123
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
124
124
+
125
125
+
[[package]]
126
126
+
name = "windows-sys"
127
127
+
version = "0.61.2"
128
128
+
source = "registry+https://github.com/rust-lang/crates.io-index"
129
129
+
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
130
130
+
dependencies = [
131
131
+
"windows-link",
132
132
+
]
+13
Cargo.toml
···
1
1
+
[package]
2
2
+
name = "porcelain"
3
3
+
version = "0.1.0"
4
4
+
license = "MIT"
5
5
+
authors = ["0xda157 <da157@voidq.com"]
6
6
+
description = "cli dollcode encoder and decoder"
7
7
+
readme = "README.md"
8
8
+
homepage = "https://codeberg.org/da157/porcelain"
9
9
+
repository = "https://codeberg.org/da157/porcelain"
10
10
+
edition = "2024"
11
11
+
12
12
+
[dependencies]
13
13
+
clap = { version = "4.5.51", features = ["cargo"] }
+22
LICENSE
···
1
1
+
The MIT License (MIT)
2
2
+
3
3
+
Copyright © 0xda157 2025
4
4
+
5
5
+
6
6
+
Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
+
of this software and associated documentation files (the “Software”), to deal
8
8
+
in the Software without restriction, including without limitation the rights
9
9
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
10
+
copies of the Software, and to permit persons to whom the Software is
11
11
+
furnished to do so, subject to the following conditions:
12
12
+
13
13
+
The above copyright notice and this permission notice shall be included in
14
14
+
all copies or substantial portions of the Software.
15
15
+
16
16
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
17
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
18
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
19
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
20
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
21
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
22
+
THE SOFTWARE.
+59
README.md
···
1
1
+
# porcelain
2
2
+
3
3
+
cli dollcode encoder and decoder
4
4
+
5
5
+
## example usage
6
6
+
7
7
+
### decimal
8
8
+
9
9
+
```sh
10
10
+
porcelain encode 893271
11
11
+
# ▖▖▘▘▌▘▌▌▘▘▘▌▌
12
12
+
porcelain decode ▖▖▘▘▌▘▌▌▘▘▘▌▌
13
13
+
# 893271
14
14
+
```
15
15
+
16
16
+
### hex
17
17
+
18
18
+
```sh
19
19
+
porcelain encode 0xda157
20
20
+
# ▖▖▘▘▌▘▌▌▘▘▘▌▌
21
21
+
porcelain -x encode da157 # or --hex
22
22
+
# ▖▖▘▘▌▘▌▌▘▘▘▌▌
23
23
+
porcelain --hex decode ▖▖▘▘▌▘▌▌▘▘▘▌▌ # or -x
24
24
+
# da157
25
25
+
```
26
26
+
27
27
+
## packaged
28
28
+
29
29
+
run without installing
30
30
+
31
31
+
```sh
32
32
+
nix run https://codeberg.org/da157/porcelain/archive/main.tar.gz -- encode 0xda157
33
33
+
```
34
34
+
35
35
+
install onto nixos system (with flakes)
36
36
+
37
37
+
```nix
38
38
+
# flake.nix
39
39
+
{
40
40
+
inputs = {
41
41
+
# ...
42
42
+
porcelain.url = "https://codeberg.org/da157/porcelain/archive/main.tar.gz";
43
43
+
};
44
44
+
45
45
+
outputs = { nixpkgs, ... }@inputs: {
46
46
+
nixosConfigurations.default = nixpkgs.lib.nixosSystem {
47
47
+
modules = [ ./configuration.nix ];
48
48
+
specialArgs = { inherit inputs; };
49
49
+
};
50
50
+
}
51
51
+
}
52
52
+
# configuration.nix
53
53
+
{ pkgs, inputs, ... }:
54
54
+
{
55
55
+
environment.systemPackages = [
56
56
+
inputs.porcelain.packages.${stdenv.hostPlatform.system}.porcelain
57
57
+
];
58
58
+
}
59
59
+
```
+27
flake.lock
···
1
1
+
{
2
2
+
"nodes": {
3
3
+
"nixpkgs": {
4
4
+
"locked": {
5
5
+
"lastModified": 1762977756,
6
6
+
"narHash": "sha256-4PqRErxfe+2toFJFgcRKZ0UI9NSIOJa+7RXVtBhy4KE=",
7
7
+
"owner": "nixos",
8
8
+
"repo": "nixpkgs",
9
9
+
"rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55",
10
10
+
"type": "github"
11
11
+
},
12
12
+
"original": {
13
13
+
"owner": "nixos",
14
14
+
"ref": "nixos-unstable",
15
15
+
"repo": "nixpkgs",
16
16
+
"type": "github"
17
17
+
}
18
18
+
},
19
19
+
"root": {
20
20
+
"inputs": {
21
21
+
"nixpkgs": "nixpkgs"
22
22
+
}
23
23
+
}
24
24
+
},
25
25
+
"root": "root",
26
26
+
"version": 7
27
27
+
}
+29
flake.nix
···
1
1
+
{
2
2
+
description = "porcelain";
3
3
+
4
4
+
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
5
5
+
6
6
+
outputs =
7
7
+
{ nixpkgs, ... }:
8
8
+
let
9
9
+
inherit (nixpkgs) lib;
10
10
+
11
11
+
forEachPkgs = f: lib.genAttrs lib.systems.flakeExposed (system: f nixpkgs.legacyPackages.${system});
12
12
+
in
13
13
+
{
14
14
+
packages = forEachPkgs (pkgs: rec {
15
15
+
default = porcelain;
16
16
+
porcelain = pkgs.callPackage ./package.nix { };
17
17
+
});
18
18
+
19
19
+
devShells = forEachPkgs (pkgs: {
20
20
+
default = pkgs.mkShell {
21
21
+
buildInputs = with pkgs; [
22
22
+
cargo
23
23
+
rust-analyzer
24
24
+
clippy
25
25
+
];
26
26
+
};
27
27
+
});
28
28
+
};
29
29
+
}
+31
package.nix
···
1
1
+
{
2
2
+
lib,
3
3
+
rustPlatform,
4
4
+
}:
5
5
+
let
6
6
+
p = (lib.importTOML ./Cargo.toml).package;
7
7
+
in
8
8
+
rustPlatform.buildRustPackage {
9
9
+
pname = p.name;
10
10
+
inherit (p) version;
11
11
+
12
12
+
src = lib.fileset.toSource {
13
13
+
root = ./.;
14
14
+
fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ./.)) (
15
15
+
lib.fileset.unions [
16
16
+
./Cargo.toml
17
17
+
./Cargo.lock
18
18
+
./src
19
19
+
]
20
20
+
);
21
21
+
};
22
22
+
23
23
+
cargoLock.lockFile = ./Cargo.lock;
24
24
+
25
25
+
meta = {
26
26
+
inherit (p) description homepage;
27
27
+
license = lib.licenses.mit;
28
28
+
maintainers = [ lib.maintainers.da157 ];
29
29
+
mainProgram = "porcelain";
30
30
+
};
31
31
+
}
+58
src/lib.rs
···
1
1
+
pub fn dollcode_decode(input: &str) -> u64 {
2
2
+
input.chars().fold(0, |acc, c| {
3
3
+
acc * 3
4
4
+
+ match c {
5
5
+
'▖' => 1,
6
6
+
'▘' => 2,
7
7
+
'▌' => 3,
8
8
+
_ => unreachable!(),
9
9
+
}
10
10
+
})
11
11
+
}
12
12
+
13
13
+
pub fn dollcode_encode(mut input: u64) -> String {
14
14
+
let mut res = String::new();
15
15
+
16
16
+
while input > 0 {
17
17
+
input -= 1;
18
18
+
res.push(['▖', '▘', '▌'][(input % 3) as usize]);
19
19
+
input /= 3;
20
20
+
}
21
21
+
22
22
+
res.chars().rev().collect::<String>()
23
23
+
}
24
24
+
25
25
+
#[cfg(test)]
26
26
+
mod tests {
27
27
+
use crate::{dollcode_decode, dollcode_encode};
28
28
+
29
29
+
#[test]
30
30
+
fn test_encode() {
31
31
+
assert_eq!(dollcode_encode(0), "");
32
32
+
assert_eq!(dollcode_encode(1), "▖");
33
33
+
assert_eq!(dollcode_encode(2), "▘");
34
34
+
assert_eq!(dollcode_encode(3), "▌");
35
35
+
assert_eq!(dollcode_encode(4), "▖▖");
36
36
+
assert_eq!(dollcode_encode(13), "▖▖▖");
37
37
+
assert_eq!(dollcode_encode(440729), "▖▌▌▌▌▖▖▖▘▌▘▘");
38
38
+
assert_eq!(
39
39
+
dollcode_encode(u64::MAX),
40
40
+
"▖▖▖▖▘▘▖▘▌▘▘▖▘▘▖▖▘▌▌▖▖▌▌▌▖▌▖▖▌▖▌▌▖▌▌▘▖▖▘▖▌"
41
41
+
);
42
42
+
}
43
43
+
44
44
+
#[test]
45
45
+
fn test_decode() {
46
46
+
assert_eq!(dollcode_decode(""), 0);
47
47
+
assert_eq!(dollcode_decode("▖"), 1);
48
48
+
assert_eq!(dollcode_decode("▘"), 2);
49
49
+
assert_eq!(dollcode_decode("▌"), 3);
50
50
+
assert_eq!(dollcode_decode("▖▖"), 4);
51
51
+
assert_eq!(dollcode_decode("▖▖▖"), 13);
52
52
+
assert_eq!(dollcode_decode("▖▌▌▌▌▖▖▖▘▌▘▘"), 440729);
53
53
+
assert_eq!(
54
54
+
dollcode_decode("▖▖▖▖▘▘▖▘▌▘▘▖▘▘▖▖▘▌▌▖▖▌▌▌▖▌▖▖▌▖▌▌▖▌▌▘▖▖▘▖▌"),
55
55
+
u64::MAX
56
56
+
);
57
57
+
}
58
58
+
}
+67
src/main.rs
···
1
1
+
use clap::{Command, arg, command, crate_authors};
2
2
+
use porcelain::{dollcode_decode, dollcode_encode};
3
3
+
4
4
+
fn main() {
5
5
+
let matches = command!()
6
6
+
.author(crate_authors!("\n"))
7
7
+
.subcommand(
8
8
+
Command::new("encode")
9
9
+
.arg(arg!([input] "Input to encode to a dollcode").required(true)),
10
10
+
)
11
11
+
.subcommand(
12
12
+
Command::new("decode").arg(arg!([dollcode] "Dollcode to decode").required(true)),
13
13
+
)
14
14
+
.arg(
15
15
+
arg!(hex: -x --hex "Operate in hex mode"), // .conflicts_with("ascii")
16
16
+
)
17
17
+
// .arg(arg!(ascii: -a --ascii "operate in ascii mode").conflicts_with("hex"))
18
18
+
.get_matches();
19
19
+
20
20
+
if let Some(ematches) = matches.subcommand_matches("encode") {
21
21
+
let input = ematches.get_one::<String>("input").unwrap();
22
22
+
23
23
+
if matches.get_flag("ascii") {
24
24
+
return;
25
25
+
}
26
26
+
27
27
+
if let Some(s) = input.strip_prefix("0x") {
28
28
+
println!(
29
29
+
"{}",
30
30
+
dollcode_encode(u64::from_str_radix(s, 16).expect("invalid hex"))
31
31
+
);
32
32
+
return;
33
33
+
}
34
34
+
35
35
+
if matches.get_flag("hex") {
36
36
+
println!(
37
37
+
"{}",
38
38
+
dollcode_encode(u64::from_str_radix(input, 16).expect("invalid hex"))
39
39
+
);
40
40
+
return;
41
41
+
}
42
42
+
43
43
+
println!(
44
44
+
"{}",
45
45
+
dollcode_encode(
46
46
+
input
47
47
+
.parse::<u64>()
48
48
+
.expect("input must be declared as hex or be a decimial number")
49
49
+
)
50
50
+
);
51
51
+
}
52
52
+
53
53
+
if let Some(dmatches) = matches.subcommand_matches("decode") {
54
54
+
let dollcode = dmatches.get_one::<String>("dollcode").unwrap();
55
55
+
56
56
+
if matches.get_flag("ascii") {
57
57
+
return;
58
58
+
}
59
59
+
60
60
+
if matches.get_flag("hex") {
61
61
+
println!("{:X}", dollcode_decode(dollcode));
62
62
+
return;
63
63
+
}
64
64
+
65
65
+
println!("{}", dollcode_decode(dollcode));
66
66
+
}
67
67
+
}