decasify 0.8.0__tar.gz → 0.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -306,7 +306,7 @@ dependencies = [
306
306
 
307
307
  [[package]]
308
308
  name = "decasify"
309
- version = "0.8.0"
309
+ version = "0.9.0"
310
310
  dependencies = [
311
311
  "anyhow",
312
312
  "assert_cmd",
@@ -1691,6 +1691,15 @@ dependencies = [
1691
1691
  "regex",
1692
1692
  ]
1693
1693
 
1694
+ [[package]]
1695
+ name = "typst"
1696
+ version = "0.9.0"
1697
+ dependencies = [
1698
+ "anyhow",
1699
+ "decasify",
1700
+ "wasm-minimal-protocol",
1701
+ ]
1702
+
1694
1703
  [[package]]
1695
1704
  name = "unicode-bidi"
1696
1705
  version = "0.3.17"
@@ -1747,6 +1756,16 @@ version = "0.2.2"
1747
1756
  source = "registry+https://github.com/rust-lang/crates.io-index"
1748
1757
  checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
1749
1758
 
1759
+ [[package]]
1760
+ name = "venial"
1761
+ version = "0.5.0"
1762
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1763
+ checksum = "61584a325b16f97b5b25fcc852eb9550843a251057a5e3e5992d2376f3df4bb2"
1764
+ dependencies = [
1765
+ "proc-macro2",
1766
+ "quote",
1767
+ ]
1768
+
1750
1769
  [[package]]
1751
1770
  name = "vergen"
1752
1771
  version = "9.0.1"
@@ -1869,6 +1888,17 @@ version = "0.2.95"
1869
1888
  source = "registry+https://github.com/rust-lang/crates.io-index"
1870
1889
  checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
1871
1890
 
1891
+ [[package]]
1892
+ name = "wasm-minimal-protocol"
1893
+ version = "0.1.0"
1894
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1895
+ checksum = "264a7e0acbdd292aca03fee87eaea5a07647394c8985b19be27e950bde57930a"
1896
+ dependencies = [
1897
+ "proc-macro2",
1898
+ "quote",
1899
+ "venial",
1900
+ ]
1901
+
1872
1902
  [[package]]
1873
1903
  name = "winapi-util"
1874
1904
  version = "0.1.9"
@@ -1,28 +1,49 @@
1
1
  [package]
2
2
  name = "decasify"
3
- version = "0.8.0"
4
- authors = ["Caleb Maclennan <caleb@alerque.com>"]
5
- edition = "2021"
6
- rust-version = "1.73.0"
3
+ version = "0.9.0"
7
4
  description = "A CLI utility and library to cast strings to title-case according to locale specific style guides including Turkish support"
8
5
  readme = "README.md"
6
+ build = "build-aux/build.rs"
7
+ include = ["*.rs"]
8
+ edition.workspace = true
9
+ rust-version.workspace = true
10
+ authors.workspace = true
11
+ homepage.workspace = true
12
+ repository.workspace = true
13
+ license.workspace = true
14
+
15
+ [workspace.package]
16
+ version = "0.9.0"
17
+ authors = ["Caleb Maclennan <caleb@alerque.com>"]
9
18
  homepage = "https://github.com/alerque/decasify"
10
19
  repository = "https://github.com/alerque/decasify"
11
20
  license = "LGPL-3.0-only"
12
- build = "build-aux/build.rs"
13
- include = ["*.rs"]
21
+ edition = "2021"
22
+ rust-version = "1.73.0"
14
23
 
15
- [lib]
16
- name = "decasify"
17
- crate-type = ["rlib", "cdylib"]
24
+ [workspace]
25
+ resolver = "2"
18
26
 
19
27
  [[bin]]
20
28
  name = "decasify"
21
29
  required-features = ["cli"]
22
30
 
31
+ [lib]
32
+ name = "decasify"
33
+ crate-type = ["rlib", "cdylib"]
34
+
35
+ [profile.release]
36
+ lto = true
37
+
38
+ [profile.typst]
39
+ inherits = "release"
40
+ opt-level = "z"
41
+ strip = true
42
+
23
43
  [features]
24
44
  default = []
25
45
  full = ["cli", "bash", "elvish", "fish", "manpage", "powershell", "zsh"]
46
+ modules = ["luamodule", "pythonmodule", "wasm"]
26
47
  completions = ["cli", "dep:clap_complete"]
27
48
  cli = ["dep:clap"]
28
49
  bash = ["completions"]
@@ -31,7 +52,7 @@ fish = ["completions"]
31
52
  manpage = ["dep:clap_mangen"]
32
53
  powershell = ["completions"]
33
54
  zsh = ["completions"]
34
- luamodule = []
55
+ luamodule = ["mlua"]
35
56
  lua54 = ["luamodule", "mlua/lua54"]
36
57
  lua53 = ["luamodule", "mlua/lua53"]
37
58
  lua52 = ["luamodule", "mlua/lua52"]
@@ -41,8 +62,9 @@ pythonmodule = ["dep:pyo3"]
41
62
  unstable-trait = []
42
63
  wasm = ["dep:wasm-bindgen"]
43
64
 
44
- [profile.release]
45
- lto = true
65
+ [workspace.dependencies.decasify]
66
+ path = "."
67
+ version = "0.9.0"
46
68
 
47
69
  [dependencies]
48
70
  regex = "1.11"
@@ -51,54 +73,54 @@ strum = "0.26"
51
73
  strum_macros = "0.26"
52
74
  unicode_titlecase = "2.4"
53
75
 
54
- [dependencies.clap]
55
- version = "4.5"
56
- optional = true
57
- features = ["derive", "wrap_help"]
76
+ [dependencies.clap]
77
+ version = "4.5"
78
+ optional = true
79
+ features = ["derive", "wrap_help"]
58
80
 
59
- [dependencies.mlua]
60
- version = "0.10.0"
61
- optional = true
62
- features = ["module"]
81
+ [dependencies.mlua]
82
+ version = "0.10.0"
83
+ optional = true
84
+ features = ["module"]
63
85
 
64
- [dependencies.pyo3]
65
- version = "0.22"
66
- optional = true
67
- features = [ "extension-module" ]
86
+ [dependencies.pyo3]
87
+ version = "0.22"
88
+ optional = true
89
+ features = ["extension-module"]
68
90
 
69
- [dependencies.titlecase]
70
- version = "3.3"
71
- features = [ "perf" ]
91
+ [dependencies.titlecase]
92
+ version = "3.3"
93
+ features = ["perf"]
72
94
 
73
- [dependencies.wasm-bindgen]
74
- version = "0.2"
75
- optional = true
95
+ [dependencies.wasm-bindgen]
96
+ version = "0.2"
97
+ optional = true
76
98
 
77
99
  [build-dependencies]
78
100
  snafu = "0.8"
79
101
  strum = "0.26"
80
102
  strum_macros = "0.26"
81
103
 
82
- [build-dependencies.clap_complete]
83
- version = "4.5"
84
- optional = true
104
+ [build-dependencies.clap_complete]
105
+ version = "4.5"
106
+ optional = true
85
107
 
86
- [build-dependencies.clap_mangen]
87
- version = "0.2"
88
- optional = true
108
+ [build-dependencies.clap_mangen]
109
+ version = "0.2"
110
+ optional = true
89
111
 
90
- [build-dependencies.clap]
91
- version = "4.5"
92
- optional = true
93
- features = ["derive"]
112
+ [build-dependencies.clap]
113
+ version = "4.5"
114
+ optional = true
115
+ features = ["derive"]
94
116
 
95
- [build-dependencies.anyhow]
96
- version = "1.0"
117
+ [build-dependencies.anyhow]
118
+ version = "1.0"
97
119
 
98
- [build-dependencies.vergen-gix]
99
- version = "1.0"
100
- default-features = false
101
- features = [ "build", "cargo", "rustc" ]
120
+ [build-dependencies.vergen-gix]
121
+ version = "1.0"
122
+ default-features = false
123
+ features = ["build", "cargo", "rustc"]
102
124
 
103
125
  [dev-dependencies]
104
126
  assert_cmd = "2.0"
@@ -107,6 +129,22 @@ predicates = "3.1"
107
129
  [lints.rust]
108
130
  unexpected_cfgs = { level = "warn", check-cfg = ['cfg(build)'] }
109
131
 
132
+ [package.metadata.bacon]
133
+ default_job = "build"
134
+
135
+ [package.metadata.bacon.jobs.build]
136
+ command = ["cargo", "build", "--features", "full", "--color", "always"]
137
+
138
+ [package.metadata.bacon.jobs.build-modules]
139
+ command = [
140
+ "cargo",
141
+ "build",
142
+ "--features",
143
+ "modules,luajit",
144
+ "--color",
145
+ "always",
146
+ ]
147
+
110
148
  [package.metadata.docs.rs]
111
149
  features = ["luamodule", "luajit", "pythonmodule", "wasm", "unstable-trait"]
112
150
  rustdoc-args = ["--cfg", "docsrs"]
@@ -114,10 +152,10 @@ rustdoc-args = ["--cfg", "docsrs"]
114
152
  [package.metadata.git-cliff.git]
115
153
  protect_breaking_commits = true
116
154
  commit_parsers = [
117
- { message = "^feat", group = "<!-- 0 -->Features" },
118
- { message = "^fix", group = "<!-- 1 -->Bug Fixes" },
119
- { message = "^perf", group = "<!-- 2 -->Performance" },
120
- { message = ".*", skip = true },
155
+ { message = "^feat", group = "<!-- 0 -->Features" },
156
+ { message = "^fix", group = "<!-- 1 -->Bug Fixes" },
157
+ { message = "^perf", group = "<!-- 2 -->Performance" },
158
+ { message = ".*", skip = true },
121
159
  ]
122
160
  commit_preprocessors = [
123
161
  { pattern = '.*', replace_command = 'typos --quiet --write-changes -' },
@@ -125,9 +163,7 @@ commit_preprocessors = [
125
163
 
126
164
  [package.metadata.typos.default]
127
165
  locale = "en-us"
128
- extend-ignore-identifiers-re = [
129
- "[bB][aA][zZ]"
130
- ]
166
+ extend-ignore-identifiers-re = ["[bB][aA][zZ]"]
131
167
 
132
168
  [package.metadata.typos.default.extend-words]
133
169
  runing = "running"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: decasify
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Natural Language :: English
@@ -33,7 +33,7 @@ Project-URL: Source Code, https://github.com/alerque/decasify
33
33
  [![PyPi (latest)](https://img.shields.io/pypi/v/decasify?logo=python&color=blue)](https://pypi.org/project/decasify)
34
34
  [![NPM Version](https://img.shields.io/npm/v/decasify?logo=npm&color=blue)](https://www.npmjs.com/package/decasify)
35
35
 
36
- A CLI utility, Rust crate, Lua rock, Python module, JavaScript module, Neovim plugin, and SILE package to cast strings to title-case (and other cases) according to locale specific style guides including Turkish support.
36
+ A CLI utility, Rust crate, Lua rock, Python module, JavaScript module, Neovim plugin, SILE package, and Typst package to cast strings to title-case (and other cases) according to locale specific style guides including Turkish support.
37
37
 
38
38
  This project was born out of frustration with authors and editors leaving ALL CAPS TITLES in Markdown sources.
39
39
  No tooling I could find properly supported casting these to title-cased strings (which are more versatile for typesetting purposes).
@@ -47,7 +47,7 @@ Where possible the APIs currently default to English rules and (for English) the
47
47
 
48
48
  The Turkish style follows the Turkish Language Institute's [guidelines][tdk].
49
49
 
50
- For English, three style guides are known: Associated Press (AP), Chicago Manual of Style (CMOS), and John Grubber's Daring Fireball (Gruber).
50
+ For English, three style guides are known: Associated Press (AP), Chicago Manual of Style (CMOS), and John Gruber's Daring Fireball (Gruber).
51
51
  The Gruber style is by far the most complete, being implemented by the [titlecase crate][titlecase_crate].
52
52
  The CMOS style handles a number of parts of speech but has punctuation related issues.
53
53
  The AP style is largely unimplemented.
@@ -77,7 +77,8 @@ Foo BAR and Baz: An Alter Ego
77
77
 
78
78
  ### Installation
79
79
 
80
- To install, first check your distro for packages, e.g. for [Arch Linux](https://archlinux.org/packages/extra/x86_64/decasify/) just install via `pacman -S decasify`.
80
+ <a href="https://repology.org/project/decasify/versions"><img src="https://repology.org/badge/vertical-allrepos/decasify.svg" align="right" alt="Packaging status"></a>
81
+ To install, first check your distro for packages, e.g. for [Arch Linux](https://archlinux.org/packages/extra/x86_64/decasify/) just install via `pacman -S decasify` or for [Homebrew](https://formulae.brew.sh/formula/decasify) via `brew install decasify`.
81
82
 
82
83
  Otherwise for many platforms you can run it directly or install it to a shell using Nix Flakes:
83
84
 
@@ -113,7 +114,7 @@ In your `Cargo.toml` file.
113
114
 
114
115
  ```toml
115
116
  [dependencies]
116
- decasify = "0.8"
117
+ decasify = "0.9"
117
118
  ```
118
119
 
119
120
  Then use the crate functions and types in your project something like this:
@@ -236,6 +237,19 @@ Loading it in a SILE document uses the usual `\use[module=package.decasify]` (se
236
237
  Once loaded the package exposes a `\decasify{}` function that can take any combination of `case`, `locale`, and `style` settings and applies the appropriate transformation to the content.
237
238
  By default it will track the language of the document content.
238
239
 
240
+ ## Use as a Typst package
241
+
242
+ [Typst](https://typst.app/) has its own [package registry](https://typst.app/universe/).
243
+ The [decasify](https://typst.app/universe/package/decasify) package can be added to your project with an import line.
244
+ The exact version must be specified explicitly:
245
+
246
+ ```typst
247
+ #import "@preview/decasify:0.8.0": *
248
+ ```
249
+
250
+ Specific functions for each case should be available throughout the document.
251
+ See the Typst package [readme](typst/README.md) or the [package listing on Typst universe](https://typst.app/universe/package/decasify) for more details.
252
+
239
253
  [rock]: http://luarocks.org/modules/alerque/decasify
240
254
  [rock.sile]: http://luarocks.org/modules/alerque/decasify.sile
241
255
 
@@ -11,7 +11,7 @@
11
11
  [![PyPi (latest)](https://img.shields.io/pypi/v/decasify?logo=python&color=blue)](https://pypi.org/project/decasify)
12
12
  [![NPM Version](https://img.shields.io/npm/v/decasify?logo=npm&color=blue)](https://www.npmjs.com/package/decasify)
13
13
 
14
- A CLI utility, Rust crate, Lua rock, Python module, JavaScript module, Neovim plugin, and SILE package to cast strings to title-case (and other cases) according to locale specific style guides including Turkish support.
14
+ A CLI utility, Rust crate, Lua rock, Python module, JavaScript module, Neovim plugin, SILE package, and Typst package to cast strings to title-case (and other cases) according to locale specific style guides including Turkish support.
15
15
 
16
16
  This project was born out of frustration with authors and editors leaving ALL CAPS TITLES in Markdown sources.
17
17
  No tooling I could find properly supported casting these to title-cased strings (which are more versatile for typesetting purposes).
@@ -25,7 +25,7 @@ Where possible the APIs currently default to English rules and (for English) the
25
25
 
26
26
  The Turkish style follows the Turkish Language Institute's [guidelines][tdk].
27
27
 
28
- For English, three style guides are known: Associated Press (AP), Chicago Manual of Style (CMOS), and John Grubber's Daring Fireball (Gruber).
28
+ For English, three style guides are known: Associated Press (AP), Chicago Manual of Style (CMOS), and John Gruber's Daring Fireball (Gruber).
29
29
  The Gruber style is by far the most complete, being implemented by the [titlecase crate][titlecase_crate].
30
30
  The CMOS style handles a number of parts of speech but has punctuation related issues.
31
31
  The AP style is largely unimplemented.
@@ -55,7 +55,8 @@ Foo BAR and Baz: An Alter Ego
55
55
 
56
56
  ### Installation
57
57
 
58
- To install, first check your distro for packages, e.g. for [Arch Linux](https://archlinux.org/packages/extra/x86_64/decasify/) just install via `pacman -S decasify`.
58
+ <a href="https://repology.org/project/decasify/versions"><img src="https://repology.org/badge/vertical-allrepos/decasify.svg" align="right" alt="Packaging status"></a>
59
+ To install, first check your distro for packages, e.g. for [Arch Linux](https://archlinux.org/packages/extra/x86_64/decasify/) just install via `pacman -S decasify` or for [Homebrew](https://formulae.brew.sh/formula/decasify) via `brew install decasify`.
59
60
 
60
61
  Otherwise for many platforms you can run it directly or install it to a shell using Nix Flakes:
61
62
 
@@ -91,7 +92,7 @@ In your `Cargo.toml` file.
91
92
 
92
93
  ```toml
93
94
  [dependencies]
94
- decasify = "0.8"
95
+ decasify = "0.9"
95
96
  ```
96
97
 
97
98
  Then use the crate functions and types in your project something like this:
@@ -214,5 +215,18 @@ Loading it in a SILE document uses the usual `\use[module=package.decasify]` (se
214
215
  Once loaded the package exposes a `\decasify{}` function that can take any combination of `case`, `locale`, and `style` settings and applies the appropriate transformation to the content.
215
216
  By default it will track the language of the document content.
216
217
 
218
+ ## Use as a Typst package
219
+
220
+ [Typst](https://typst.app/) has its own [package registry](https://typst.app/universe/).
221
+ The [decasify](https://typst.app/universe/package/decasify) package can be added to your project with an import line.
222
+ The exact version must be specified explicitly:
223
+
224
+ ```typst
225
+ #import "@preview/decasify:0.8.0": *
226
+ ```
227
+
228
+ Specific functions for each case should be available throughout the document.
229
+ See the Typst package [readme](typst/README.md) or the [package listing on Typst universe](https://typst.app/universe/package/decasify) for more details.
230
+
217
231
  [rock]: http://luarocks.org/modules/alerque/decasify
218
232
  [rock.sile]: http://luarocks.org/modules/alerque/decasify.sile
@@ -3,20 +3,27 @@
3
3
 
4
4
  use regex::Regex;
5
5
  use std::{borrow::Cow, fmt, fmt::Display, str::FromStr};
6
+ use unicode_titlecase::StrTitleCase;
6
7
 
7
8
  use snafu::prelude::*;
8
9
 
10
+ #[derive(Clone, Debug)]
11
+ #[non_exhaustive]
12
+ pub struct Chunk {
13
+ pub segments: Vec<Segment>,
14
+ }
15
+
9
16
  #[derive(Clone, Debug, PartialEq)]
10
17
  #[non_exhaustive]
11
18
  pub enum Segment {
12
19
  Separator(String),
13
- Word(String),
20
+ Word(Word),
14
21
  }
15
22
 
16
- #[derive(Clone, Debug)]
23
+ #[derive(Clone, Debug, PartialEq)]
17
24
  #[non_exhaustive]
18
- pub struct Chunk {
19
- pub segments: Vec<Segment>,
25
+ pub struct Word {
26
+ pub word: String,
20
27
  }
21
28
 
22
29
  #[derive(Snafu)]
@@ -42,7 +49,9 @@ fn split_chunk(s: &str) -> Chunk {
42
49
  if let Some(m) = capture.name("separator") {
43
50
  segments.push(Segment::Separator(m.as_str().to_string()));
44
51
  } else if let Some(m) = capture.name("word") {
45
- segments.push(Segment::Word(m.as_str().to_string()));
52
+ segments.push(Segment::Word(Word {
53
+ word: m.as_str().to_owned(),
54
+ }));
46
55
  }
47
56
  }
48
57
  Chunk { segments }
@@ -79,13 +88,13 @@ impl FromStr for Chunk {
79
88
  }
80
89
  }
81
90
 
82
- impl Display for Segment {
83
- fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
84
- let _ = match self {
85
- Segment::Separator(string) => fmt.write_str(string),
86
- Segment::Word(string) => fmt.write_str(string),
87
- };
88
- Ok(())
91
+ impl From<Chunk> for String {
92
+ fn from(c: Chunk) -> Self {
93
+ let mut s = String::new();
94
+ for segment in c.segments {
95
+ s.push_str(segment.to_string().as_ref());
96
+ }
97
+ s
89
98
  }
90
99
  }
91
100
 
@@ -97,3 +106,56 @@ impl Display for Chunk {
97
106
  Ok(())
98
107
  }
99
108
  }
109
+
110
+ impl Display for Segment {
111
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
112
+ match self {
113
+ Segment::Separator(string) => fmt.write_str(string)?,
114
+ Segment::Word(word) => fmt.write_str(word.to_string().as_ref())?,
115
+ };
116
+ Ok(())
117
+ }
118
+ }
119
+
120
+ impl Word {
121
+ pub fn to_lowercase(&self) -> String {
122
+ self.word.to_lowercase()
123
+ }
124
+ pub fn to_uppercase(&self) -> String {
125
+ self.word.to_uppercase()
126
+ }
127
+ }
128
+
129
+ impl From<String> for Word {
130
+ fn from(word: String) -> Self {
131
+ Self { word }
132
+ }
133
+ }
134
+
135
+ impl StrTitleCase for Word {
136
+ fn to_titlecase(&self) -> String {
137
+ self.word.to_titlecase()
138
+ }
139
+ fn to_titlecase_lower_rest(&self) -> String {
140
+ self.word.to_titlecase_lower_rest()
141
+ }
142
+ fn to_titlecase_tr_or_az(&self) -> String {
143
+ self.word.to_titlecase_tr_or_az()
144
+ }
145
+ fn to_titlecase_tr_or_az_lower_rest(&self) -> String {
146
+ self.word.to_titlecase_tr_or_az_lower_rest()
147
+ }
148
+ fn starts_titlecase(&self) -> bool {
149
+ self.word.starts_titlecase()
150
+ }
151
+ fn starts_titlecase_rest_lower(&self) -> bool {
152
+ self.word.starts_titlecase_rest_lower()
153
+ }
154
+ }
155
+
156
+ impl Display for Word {
157
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
158
+ fmt.write_str(self.word.as_ref())?;
159
+ Ok(())
160
+ }
161
+ }
@@ -1,7 +1,7 @@
1
1
  // SPDX-FileCopyrightText: © 2023 Caleb Maclennan <caleb@alerque.com>
2
2
  // SPDX-License-Identifier: LGPL-3.0-only
3
3
 
4
- use crate::content::{Chunk, Segment};
4
+ use crate::content::{Chunk, Segment, Word};
5
5
  use crate::types::StyleGuide;
6
6
 
7
7
  use regex::Regex;
@@ -20,32 +20,32 @@ pub fn titlecase(chunk: Chunk, style: StyleGuide) -> String {
20
20
 
21
21
  fn titlecase_ap(chunk: Chunk) -> String {
22
22
  eprintln!("AP style guide not implemented, string returned as-is!");
23
- chunk.to_string()
23
+ chunk.into()
24
24
  }
25
25
 
26
26
  fn titlecase_cmos(chunk: Chunk) -> String {
27
- let mut done_first = false;
28
27
  let mut chunk = chunk.clone();
29
- let mut segments = chunk.segments.iter_mut().peekable();
30
- while let Some(segment) = segments.next() {
31
- if let Segment::Word(s) = segment {
32
- *s = if !done_first {
33
- done_first = true;
34
- s.to_string().to_titlecase_lower_rest()
35
- } else if segments.peek().is_none() {
36
- // TODO: I think a bug is hiding here since peek() might give use a separator
37
- // that happens to be a trailing trivia. We need a custom iterator or peeker
38
- // that knows how to answer about first/last *word* segments.
39
- s.to_string().to_titlecase_lower_rest()
40
- } else {
41
- match is_reserved(s.to_string()) {
42
- true => s.to_string().to_lowercase(),
43
- false => s.to_string().to_titlecase_lower_rest(),
44
- }
45
- }
46
- }
28
+ let mut words = chunk
29
+ .segments
30
+ .iter_mut()
31
+ .filter_map(|segment| match segment {
32
+ Segment::Word(word) => Some(word),
33
+ _ => None,
34
+ })
35
+ .peekable();
36
+ if let Some(word) = words.next() {
37
+ word.word = word.to_titlecase_lower_rest();
38
+ }
39
+ while let Some(word) = words.next() {
40
+ word.word = match words.peek().is_none() {
41
+ true => word.to_titlecase_lower_rest(),
42
+ false => match is_reserved(word) {
43
+ true => word.to_lowercase(),
44
+ false => word.to_titlecase_lower_rest(),
45
+ },
46
+ };
47
47
  }
48
- chunk.to_string()
48
+ chunk.into()
49
49
  }
50
50
 
51
51
  fn titlecase_gruber(chunk: Chunk) -> String {
@@ -61,11 +61,11 @@ fn titlecase_gruber(chunk: Chunk) -> String {
61
61
  } else {
62
62
  String::from("")
63
63
  };
64
- let titilized = gruber_titlecase(&chunk.to_string());
64
+ let titilized = gruber_titlecase(chunk.to_string().as_ref());
65
65
  format!("{}{}{}", leading_trivia, titilized, trailing_trivia)
66
66
  }
67
67
 
68
- fn is_reserved(word: String) -> bool {
68
+ fn is_reserved(word: &Word) -> bool {
69
69
  let word = word.to_lowercase();
70
70
  let word = word.as_str();
71
71
  let article = Regex::new(r"^(a|an|the)$").unwrap();
@@ -77,35 +77,35 @@ fn is_reserved(word: String) -> bool {
77
77
  pub fn lowercase(chunk: Chunk) -> String {
78
78
  let mut chunk = chunk.clone();
79
79
  chunk.segments.iter_mut().for_each(|segment| {
80
- if let Segment::Word(s) = segment {
81
- *s = s.to_string().to_lowercase()
80
+ if let Segment::Word(word) = segment {
81
+ word.word = word.to_lowercase()
82
82
  }
83
83
  });
84
- chunk.to_string()
84
+ chunk.into()
85
85
  }
86
86
 
87
87
  pub fn uppercase(chunk: Chunk) -> String {
88
88
  let mut chunk = chunk.clone();
89
89
  chunk.segments.iter_mut().for_each(|segment| {
90
- if let Segment::Word(s) = segment {
91
- *s = s.to_string().to_uppercase()
90
+ if let Segment::Word(word) = segment {
91
+ word.word = word.to_uppercase()
92
92
  }
93
93
  });
94
- chunk.to_string()
94
+ chunk.into()
95
95
  }
96
96
 
97
97
  pub fn sentencecase(chunk: Chunk) -> String {
98
98
  let mut chunk = chunk.clone();
99
99
  let mut done_first = false;
100
100
  chunk.segments.iter_mut().for_each(|segment| {
101
- if let Segment::Word(s) = segment {
102
- *s = if !done_first {
101
+ if let Segment::Word(word) = segment {
102
+ word.word = if !done_first {
103
103
  done_first = true;
104
- s.to_string().to_titlecase_lower_rest()
104
+ word.to_titlecase_lower_rest()
105
105
  } else {
106
- s.to_string().to_lowercase()
106
+ word.to_lowercase()
107
107
  }
108
108
  }
109
109
  });
110
- chunk.to_string()
110
+ chunk.into()
111
111
  }
@@ -1,7 +1,7 @@
1
1
  // SPDX-FileCopyrightText: © 2023 Caleb Maclennan <caleb@alerque.com>
2
2
  // SPDX-License-Identifier: LGPL-3.0-only
3
3
 
4
- use crate::content::{Chunk, Segment};
4
+ use crate::content::{Chunk, Segment, Word};
5
5
  use crate::types::StyleGuide;
6
6
 
7
7
  use regex::Regex;
@@ -20,63 +20,63 @@ fn titlecase_tdk(chunk: Chunk) -> String {
20
20
  let mut chunk = chunk.clone();
21
21
  let mut done_first = false;
22
22
  chunk.segments.iter_mut().for_each(|segment| {
23
- if let Segment::Word(s) = segment {
24
- *s = if !done_first {
23
+ if let Segment::Word(word) = segment {
24
+ word.word = if !done_first {
25
25
  done_first = true;
26
- s.to_string().to_titlecase_tr_or_az_lower_rest()
26
+ word.to_titlecase_tr_or_az_lower_rest()
27
27
  } else {
28
- match is_reserved(s.to_string()) {
29
- true => s.to_string().to_lowercase_tr_az(),
30
- false => s.to_titlecase_tr_or_az_lower_rest(),
28
+ match is_reserved(word) {
29
+ true => word.word.to_lowercase_tr_az(),
30
+ false => word.word.to_titlecase_tr_or_az_lower_rest(),
31
31
  }
32
32
  }
33
33
  }
34
34
  });
35
- chunk.to_string()
35
+ chunk.into()
36
36
  }
37
37
 
38
- fn is_reserved(word: String) -> bool {
39
- let baglac = Regex::new(
40
- r"^([Vv][Ee]|[İi][Ll][Ee]|[Yy][Aa]|[Vv][Ee]|[Yy][Aa][Hh][Uu][Tt]|[Kk][İi]|[Dd][AaEe])$",
41
- )
42
- .unwrap();
38
+ fn is_reserved(word: &Word) -> bool {
39
+ let word = word.to_string();
40
+ let word = word.as_ref();
41
+ let baglac =
42
+ Regex::new(r"^([Vv][Ee]|[İi][Ll][Ee]|[Yy][Aa]|[Yy][Aa][Hh][Uu][Tt]|[Kk][İi]|[Dd][AaEe])$")
43
+ .unwrap();
43
44
  let soruek = Regex::new(r"^([Mm][İiIıUuÜü])([Dd][İiIıUuÜü][Rr]([Ll][AaEe][Rr])?|[Ss][İiIıUuÜü][Nn]|[Yy][İiIıUuÜü][Zz]|[Ss][İiIıUuÜü][Nn][İiIıUuÜü][Zz]|[Ll][AaEe][Rr])?$").unwrap();
44
- let word = word.as_str();
45
45
  baglac.is_match(word) || soruek.is_match(word)
46
46
  }
47
47
 
48
48
  pub fn lowercase(chunk: Chunk) -> String {
49
49
  let mut chunk = chunk.clone();
50
50
  chunk.segments.iter_mut().for_each(|segment| {
51
- if let Segment::Word(s) = segment {
52
- *s = s.to_string().to_lowercase_tr_az()
51
+ if let Segment::Word(word) = segment {
52
+ word.word = word.word.to_lowercase_tr_az()
53
53
  }
54
54
  });
55
- chunk.to_string()
55
+ chunk.into()
56
56
  }
57
57
 
58
58
  pub fn uppercase(chunk: Chunk) -> String {
59
59
  let mut chunk = chunk.clone();
60
60
  chunk.segments.iter_mut().for_each(|segment| {
61
- if let Segment::Word(s) = segment {
62
- *s = s.to_string().to_uppercase_tr_az()
61
+ if let Segment::Word(word) = segment {
62
+ word.word = word.word.to_uppercase_tr_az()
63
63
  }
64
64
  });
65
- chunk.to_string()
65
+ chunk.into()
66
66
  }
67
67
 
68
68
  pub fn sentencecase(chunk: Chunk) -> String {
69
69
  let mut chunk = chunk.clone();
70
70
  let mut done_first = false;
71
71
  chunk.segments.iter_mut().for_each(|segment| {
72
- if let Segment::Word(s) = segment {
73
- *s = if !done_first {
72
+ if let Segment::Word(word) = segment {
73
+ word.word = if !done_first {
74
74
  done_first = true;
75
- s.to_string().to_titlecase_tr_or_az_lower_rest()
75
+ word.word.to_titlecase_tr_or_az_lower_rest()
76
76
  } else {
77
- s.to_string().to_lowercase_tr_az()
77
+ word.word.to_lowercase_tr_az()
78
78
  }
79
79
  }
80
80
  });
81
- chunk.to_string()
81
+ chunk.into()
82
82
  }
@@ -84,8 +84,8 @@ impl FromStr for Locale {
84
84
  type Err = Error;
85
85
  fn from_str(s: &str) -> Result<Self> {
86
86
  match s.to_ascii_lowercase().as_str() {
87
- "en" | "English" | "en_en" => Ok(Locale::EN),
88
- "tr" | "Turkish" | "tr_tr" | "türkçe" => Ok(Locale::TR),
87
+ "en" | "english" | "en_en" => Ok(Locale::EN),
88
+ "tr" | "turkish" | "tr_tr" | "türkçe" => Ok(Locale::TR),
89
89
  input => LocaleSnafu { input }.fail()?,
90
90
  }
91
91
  }
@@ -109,6 +109,13 @@ impl From<&String> for Locale {
109
109
  }
110
110
  }
111
111
 
112
+ impl From<&[u8]> for Locale {
113
+ fn from(s: &[u8]) -> Self {
114
+ let s = String::from_utf8(s.to_vec()).unwrap();
115
+ Self::from_str(s.as_ref()).unwrap()
116
+ }
117
+ }
118
+
112
119
  impl FromStr for Case {
113
120
  type Err = Error;
114
121
  fn from_str(s: &str) -> Result<Self> {
@@ -140,6 +147,13 @@ impl From<&String> for Case {
140
147
  }
141
148
  }
142
149
 
150
+ impl From<&[u8]> for Case {
151
+ fn from(s: &[u8]) -> Self {
152
+ let s = String::from_utf8(s.to_vec()).unwrap();
153
+ Self::from_str(s.as_ref()).unwrap()
154
+ }
155
+ }
156
+
143
157
  impl FromStr for StyleGuide {
144
158
  type Err = Error;
145
159
  fn from_str(s: &str) -> Result<Self> {
@@ -174,6 +188,13 @@ impl From<&String> for StyleGuide {
174
188
  }
175
189
  }
176
190
 
191
+ impl From<&[u8]> for StyleGuide {
192
+ fn from(s: &[u8]) -> Self {
193
+ let s = String::from_utf8(s.to_vec()).unwrap();
194
+ Self::from_str(s.as_ref()).unwrap()
195
+ }
196
+ }
197
+
177
198
  impl From<Option<StyleGuide>> for StyleGuide {
178
199
  fn from(style: Option<StyleGuide>) -> Self {
179
200
  match style {
@@ -65,6 +65,24 @@ case!(
65
65
  "a b c"
66
66
  );
67
67
 
68
+ case!(
69
+ trivia_en,
70
+ Case::Title,
71
+ Locale::EN,
72
+ StyleGuide::LanguageDefault,
73
+ " foo bar ",
74
+ " Foo Bar "
75
+ );
76
+
77
+ case!(
78
+ trivia_tr,
79
+ Case::Title,
80
+ Locale::TR,
81
+ StyleGuide::LanguageDefault,
82
+ " foo bar ",
83
+ " Foo Bar "
84
+ );
85
+
68
86
  macro_rules! titlecase {
69
87
  ($name:ident, $locale:expr, $style:expr, $input:expr, $expected:expr) => {
70
88
  #[test]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes