+76
-48
Diff
round #0
+57
-23
src/main.rs
+57
-23
src/main.rs
···
9
9
10
10
11
11
12
-
12
+
const MIN_BUBBLE_SIZE: usize = 20;
13
13
const MAX_BUBBLE_SIZE: usize = 60;
14
14
const MIN_CLONE_INNER: usize = 22;
15
+
const DOLLY_MESSAGE_TAIL_POS: &[usize] = &[3];
16
+
const DOLLYS_MESSAGE_TAIL_POS: &[usize] = &[3, 22];
15
17
16
-
const TIPS: &[&str] = &[
18
+
const MUSINGS: &[&str] = &[
17
19
"I push, therefore I baa.",
18
20
"Identity portable. Repos plural. Wool eternal.",
19
21
];
···
33
35
#[arg(long)]
34
36
clone: bool,
35
37
36
-
/// Have Dolly share a random Tangled tip.
38
+
/// Have Dolly share what's on her mind.
39
+
#[arg(long)]
40
+
muse: bool,
41
+
42
+
/// Use a thought bubble instead of speech.
37
43
#[arg(long)]
38
-
tip: bool,
44
+
think: bool,
39
45
}
40
46
41
47
fn main() -> ExitCode {
···
47
53
48
54
49
55
56
+
};
50
57
58
+
let min_inner = if cli.clone { MIN_CLONE_INNER } else { 0 };
59
+
let bubble: String = bubble::render(&message, message_width(), min_inner, cli.think);
51
60
61
+
let (art, tail_pos) = if cli.clone {
62
+
(DOLLYS, DOLLYS_MESSAGE_TAIL_POS)
63
+
} else {
64
+
(DOLLY, DOLLY_MESSAGE_TAIL_POS)
65
+
};
66
+
let tail = render_message_tail(tail_pos, cli.think);
67
+
68
+
// Lock stdout and use BufWriter to group writes to one syscall only.
69
+
let stdout = io::stdout();
70
+
let mut out = BufWriter::new(stdout.lock());
71
+
72
+
let _ = writeln!(out, "{bubble}");
73
+
let _ = out.write_all(tail.as_bytes());
74
+
let _ = out.write_all(art.as_bytes());
52
75
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
76
+
match out.flush() {
65
77
66
78
67
79
···
74
86
// Finds where the message text comes from: positional args or piped stdin,
75
87
// or falls back to default string.
76
88
fn resolve_message(cli: &Cli) -> io::Result<String> {
77
-
// --tip wins over positional/stdin: the user is explicitly asking for one.
78
-
if cli.tip {
79
-
return Ok(random_tip().to_string());
89
+
// --muse wins over positional/stdin: the user is explicitly asking for one.
90
+
if cli.muse {
91
+
return Ok(random_musing().to_string());
80
92
}
81
93
82
94
// positional args
···
100
112
Ok(buf.trim_end().to_string())
101
113
}
102
114
103
-
// Picks a tip pseudo-randomly. Uses the system clock as the seed source so we
115
+
// Picks a musing pseudo-randomly. Uses the system clock as the seed source so we
104
116
// don't pull in `rand`.
105
-
fn random_tip() -> &'static str {
106
-
if TIPS.is_empty() {
117
+
fn random_musing() -> &'static str {
118
+
if MUSINGS.is_empty() {
107
119
return "Baa!";
108
120
}
109
121
let nanos = SystemTime::now()
110
122
.duration_since(UNIX_EPOCH)
111
123
.map(|d| d.as_millis())
112
124
.unwrap_or(0);
113
-
TIPS[(nanos as usize) % TIPS.len()]
125
+
MUSINGS[(nanos as usize) % MUSINGS.len()]
126
+
}
127
+
128
+
// Renders the two-line tail connecting bubble to Dolly's head. Each anchor
129
+
// in `positions` starts a tail char at that column on line 1 and at col+1 on line 2.
130
+
fn render_message_tail(positions: &[usize], think: bool) -> String {
131
+
let ch = if think { 'o' } else { '\\' };
132
+
let mut out = String::new();
133
+
for offset in 0..2 {
134
+
let mut col = 0;
135
+
for &pos in positions {
136
+
let target = pos + offset;
137
+
for _ in col..target {
138
+
out.push(' ');
139
+
}
140
+
out.push(ch);
141
+
col = target + 1;
142
+
}
143
+
out.push('\n');
144
+
}
145
+
out
114
146
}
115
147
116
148
// Computes the wrap width for the message inside the bubble.
149
+
fn message_width() -> usize {
150
+
let term: usize = terminal_size()
+9
-7
README.md
+9
-7
README.md
···
17
17
18
18
19
19
20
+
You'll need a Rust toolchain. Install via [rustup](https://rustup.rs) if you don't have one.
20
21
21
-
22
-
23
-
24
-
25
-
26
-
22
+
```sh
23
+
git clone https://tangled.org/paeth.xyz/dollysay dollysay
24
+
cd dollysay
25
+
cargo install --path .
26
+
```
27
27
28
28
29
29
···
33
33
dollysay hello # speak a message
34
34
echo "baa" | dollysay # read from stdin
35
35
dollysay --clone "we are many" # two Dollys, one bubble
36
-
dollysay --tip # hear what Dolly has to say
36
+
dollysay --think # render the message as a thought bubble
37
+
dollysay --muse # hear what Dolly has to say
37
38
```
38
39
39
40
Run `dollysay --help` for the full flag list.
···
47
48
## TODO
48
49
49
50
- Add Dolly moods!
51
+
- add `--deepthink` mode?
+1
-3
dolly.txt
+1
-3
dolly.txt
-2
dollys.txt
-2
dollys.txt
+9
-13
src/bubble.rs
+9
-13
src/bubble.rs
···
2
2
3
3
// Builds the speech bubble (without trailing newline) and returns
4
4
// it as one String.
5
-
pub fn render(text: &str, max_width: usize, min_width: usize) -> String {
5
+
pub fn render(text: &str, max_width: usize, min_width: usize, think: bool) -> String {
6
6
let wrapped_text: Vec<String> = textwrap::wrap(text, max_width)
7
7
.into_iter()
8
8
.map(|cow| cow.into_owned())
···
27
27
out.push('\n');
28
28
29
29
if wrapped_text.len() == 1 {
30
-
// Single-line:
31
-
// _______________________
32
-
// < this is a single line >
33
-
// -----------------------
34
-
push_bubble_line(&mut out, &wrapped_text[0], inner, '<', '>');
30
+
// Single-line: < text > for speech, ( text ) for thought.
31
+
let (l, r) = if think { ('(', ')') } else { ('<', '>') };
32
+
push_bubble_line(&mut out, &wrapped_text[0], inner, l, r);
35
33
} else {
36
-
// Multi-line:
37
-
// ___________________________________________________________
38
-
// / this is a multi line text that will require rounding the \
39
-
// | speech bubble corners. since it is more than two lines it |
40
-
// \ will need to be extended with walls. /
41
-
// -----------------------------------------------------------
34
+
// Multi-line: rounded speech corners + | walls,
35
+
// or all parens for thought (cowthink convention).
42
36
let last = wrapped_text.len() - 1;
43
37
for (i, line) in wrapped_text.iter().enumerate() {
44
-
let (l, r) = if i == 0 {
38
+
let (l, r) = if think {
39
+
('(', ')')
40
+
} else if i == 0 {
45
41
('/', '\\')
46
42
} else if i == last {
47
43
('\\', '/')
History
1 round
0 comments
paeth.xyz
submitted
#0
5 commits
expand
collapse
rename --tip to --muse and update accordingly
update README
fix dolly art
add --think mode, move message tail rendering
update README with repo url and --think
merge conflicts detected
expand
collapse
expand
collapse
- src/main.rs:13