mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-10 01:21:16 -03:00
Compare commits
691 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efb0223da1 | ||
|
|
b6f30f11e4 | ||
|
|
fb29c85a62 | ||
|
|
e071de3b68 | ||
|
|
ed6fe3f315 | ||
|
|
4f539dffaf | ||
|
|
d885e0efd7 | ||
|
|
330e897acc | ||
|
|
b638aa198f | ||
|
|
fd44c23678 | ||
|
|
f84179f8fe | ||
|
|
71d6ec4ab9 | ||
|
|
683e4c8d15 | ||
|
|
d7cc3c7bb6 | ||
|
|
9370830733 | ||
|
|
161f31f42b | ||
|
|
5998421410 | ||
|
|
5b1e163f22 | ||
|
|
7c5fc85d96 | ||
|
|
2f9f46b2a5 | ||
|
|
4b069b51e7 | ||
|
|
398fc17b81 | ||
|
|
12fa0d8b3d | ||
|
|
0441bdc634 | ||
|
|
d0e47cf58a | ||
|
|
d35aa3860a | ||
|
|
5971e79c3f | ||
|
|
dad660cda5 | ||
|
|
8328e53050 | ||
|
|
4aadeea184 | ||
|
|
df5067cc1c | ||
|
|
3d708d6fc1 | ||
|
|
a564238d82 | ||
|
|
64443aa173 | ||
|
|
d2c2b23d1f | ||
|
|
4e3898d0d7 | ||
|
|
3ad45d8fb1 | ||
|
|
39bd54cb49 | ||
|
|
281399561b | ||
|
|
e5f57b1daf | ||
|
|
6c04a72697 | ||
|
|
1034945690 | ||
|
|
e2b18fc5b6 | ||
|
|
319b093ef8 | ||
|
|
ab2678082e | ||
|
|
81e8eebd8d | ||
|
|
2b41f132be | ||
|
|
688d1954a8 | ||
|
|
96695a2859 | ||
|
|
f2b0706494 | ||
|
|
c91bfba08c | ||
|
|
cc40fa4a4c | ||
|
|
ff6ee65deb | ||
|
|
1771a325aa | ||
|
|
58648054c0 | ||
|
|
27fb4d6731 | ||
|
|
6701b7f6c8 | ||
|
|
e175a317af | ||
|
|
7b98a275fe | ||
|
|
b78dc4fbec | ||
|
|
12e97ea7fc | ||
|
|
af8594c611 | ||
|
|
006fa86ef4 | ||
|
|
9b04300dc3 | ||
|
|
c80496fad1 | ||
|
|
ca56949028 | ||
|
|
fa74d0fe54 | ||
|
|
59f3719e95 | ||
|
|
170c171e85 | ||
|
|
c33ca660e3 | ||
|
|
f7c336021b | ||
|
|
523e25df17 | ||
|
|
c8b28d4d24 | ||
|
|
ba35214e1e | ||
|
|
d05d8557a7 | ||
|
|
a3dc57873c | ||
|
|
0c078c179d | ||
|
|
ca443e2e54 | ||
|
|
63c3306e6c | ||
|
|
923d0b7974 | ||
|
|
52998635f9 | ||
|
|
1ccf4ad480 | ||
|
|
23b5b01242 | ||
|
|
ca2b5dc40b | ||
|
|
0dfe06f4c9 | ||
|
|
4e47f47d85 | ||
|
|
f3e43e932f | ||
|
|
1dfc75bb9c | ||
|
|
fa33f6f0e0 | ||
|
|
31363120aa | ||
|
|
2304077e0d | ||
|
|
86c052b6ba | ||
|
|
68472da48a | ||
|
|
4b172fc735 | ||
|
|
944ab91fab | ||
|
|
34535fcb61 | ||
|
|
9e4eb37696 | ||
|
|
dda76d7f18 | ||
|
|
fdb1d95521 | ||
|
|
937f3bc6cb | ||
|
|
ebc32adc09 | ||
|
|
a4b6348315 | ||
|
|
b21a4a7197 | ||
|
|
0cd227533f | ||
|
|
5eb7687a64 | ||
|
|
8d6426295e | ||
|
|
85e76ba356 | ||
|
|
fee4288122 | ||
|
|
413246a93d | ||
|
|
3cb939c9a8 | ||
|
|
4790a444d8 | ||
|
|
da924927a0 | ||
|
|
29ff2fdd43 | ||
|
|
732c04420b | ||
|
|
947abd7464 | ||
|
|
12cfe59578 | ||
|
|
4b60d18b44 | ||
|
|
dd8e59db03 | ||
|
|
47a3757f73 | ||
|
|
f278c29733 | ||
|
|
2bab8b5830 | ||
|
|
1dac221684 | ||
|
|
8dbbe71bc6 | ||
|
|
d9d9eced98 | ||
|
|
64a829f0df | ||
|
|
440e7fcbc1 | ||
|
|
52495c8124 | ||
|
|
467b03d715 | ||
|
|
b5c40478f6 | ||
|
|
1286745e78 | ||
|
|
b99ae291d6 | ||
|
|
8ae71c80f4 | ||
|
|
cf6170200c | ||
|
|
c13038b968 | ||
|
|
816077281d | ||
|
|
78ea24a262 | ||
|
|
6a5b9bcde1 | ||
|
|
b7b786aabf | ||
|
|
dc63b7bb20 | ||
|
|
9c819c020e | ||
|
|
dc9b1141c8 | ||
|
|
3d364478ee | ||
|
|
faf331fdad | ||
|
|
47b6c0aec2 | ||
|
|
eb478bfc3e | ||
|
|
63cf79f5f6 | ||
|
|
ff284d642e | ||
|
|
cc64da62a9 | ||
|
|
a974fe990f | ||
|
|
39239724ec | ||
|
|
524a7bac6e | ||
|
|
d649c2aab4 | ||
|
|
30e6aa85e2 | ||
|
|
abd7442521 | ||
|
|
f5c48038b5 | ||
|
|
895a6e7034 | ||
|
|
2193e88423 | ||
|
|
86c3778c2a | ||
|
|
d2653b7cac | ||
|
|
cf16949ce7 | ||
|
|
85311546de | ||
|
|
c44aa32a15 | ||
|
|
65bc9b9e3e | ||
|
|
8125f78a84 | ||
|
|
f0f48b4859 | ||
|
|
a009f87630 | ||
|
|
edb66d4d4e | ||
|
|
f3f675b4cc | ||
|
|
434610494f | ||
|
|
3cce1f3f4c | ||
|
|
a5bde7954e | ||
|
|
99d63c21f1 | ||
|
|
c3e3658157 | ||
|
|
8d92016e72 | ||
|
|
f6a72b4e19 | ||
|
|
2f9c2df10d | ||
|
|
0367aaea7d | ||
|
|
e25b4b6f05 | ||
|
|
90cbfd288e | ||
|
|
68453843d4 | ||
|
|
fb57f95391 | ||
|
|
1ed276292b | ||
|
|
09e46b00cc | ||
|
|
695bc293a9 | ||
|
|
344ff7be88 | ||
|
|
014e3b3aff | ||
|
|
68c7baff90 | ||
|
|
6eaad2cd80 | ||
|
|
b321e38f5a | ||
|
|
a32dd63163 | ||
|
|
ef90afa5b9 | ||
|
|
7bd37dfe55 | ||
|
|
14ce56d2a5 | ||
|
|
01ee6f968d | ||
|
|
7f6dcde5e0 | ||
|
|
34fc573668 | ||
|
|
93cbf2a0e8 | ||
|
|
8194c6eb79 | ||
|
|
3194572156 | ||
|
|
e635816b7f | ||
|
|
2b3ecf22da | ||
|
|
c7ecc3bd78 | ||
|
|
828a20ef30 | ||
|
|
7f5692dfd3 | ||
|
|
454939d5ab | ||
|
|
8756bc3afb | ||
|
|
272f5dda83 | ||
|
|
dde33bab7e | ||
|
|
63f642c9dd | ||
|
|
146384abc6 | ||
|
|
8561008513 | ||
|
|
88d01f7eb8 | ||
|
|
4467822f6e | ||
|
|
5fe1cfb895 | ||
|
|
484032fa9e | ||
|
|
fdd10ba9b2 | ||
|
|
8679464689 | ||
|
|
9ea760c401 | ||
|
|
d36c53c6e2 | ||
|
|
b047450cd0 | ||
|
|
0b93989080 | ||
|
|
6b4ab05cbc | ||
|
|
b5c367f8bf | ||
|
|
a6959aba2a | ||
|
|
0a07e8dbdf | ||
|
|
b2f350d235 | ||
|
|
d1407cfde6 | ||
|
|
f076aa1637 | ||
|
|
96602f5096 | ||
|
|
a36fc0b316 | ||
|
|
d7f413bee9 | ||
|
|
81e9e0bd5c | ||
|
|
5c042575b0 | ||
|
|
a0d8d27f45 | ||
|
|
d1d4cbf3f8 | ||
|
|
7f41c8ba1f | ||
|
|
c8989e1849 | ||
|
|
ae4e258884 | ||
|
|
3f0b4d38ff | ||
|
|
e9379904fb | ||
|
|
116671c577 | ||
|
|
53e1718a68 | ||
|
|
eab84c896e | ||
|
|
ddf99b7063 | ||
|
|
bcda4c5c4d | ||
|
|
1244a47d86 | ||
|
|
4279a4f879 | ||
|
|
efebb7bcdb | ||
|
|
226e818c25 | ||
|
|
888e6d97f9 | ||
|
|
eb7ea0ef9b | ||
|
|
4e41d142fd | ||
|
|
a893dd10f4 | ||
|
|
cba82a3c64 | ||
|
|
6e8c32eb12 | ||
|
|
8ef9864c0c | ||
|
|
786ac339b8 | ||
|
|
dbb6ae6cf5 | ||
|
|
c4aa03a1fd | ||
|
|
e9340a3c43 | ||
|
|
143a53d9c3 | ||
|
|
a282acf083 | ||
|
|
cbd5d7640a | ||
|
|
6f262afe8e | ||
|
|
310eba7156 | ||
|
|
0223edc639 | ||
|
|
6d0bb4a6b8 | ||
|
|
7992fda9fe | ||
|
|
bf5fa4f681 | ||
|
|
735f3ae6ad | ||
|
|
131febed2a | ||
|
|
61dec20abd | ||
|
|
c0bb0d6584 | ||
|
|
c5e4fed021 | ||
|
|
1df7a8ba29 | ||
|
|
121b8fffa6 | ||
|
|
9c4190e40a | ||
|
|
f000149837 | ||
|
|
f6f50df43d | ||
|
|
29160a1592 | ||
|
|
fcdcae72c5 | ||
|
|
971e0b7d37 | ||
|
|
d6ac8a48c0 | ||
|
|
2ba031677f | ||
|
|
c15ea5d1e6 | ||
|
|
78f3c95641 | ||
|
|
149fec8f02 | ||
|
|
1b0fa8f804 | ||
|
|
c38dd1f420 | ||
|
|
b9b32ad157 | ||
|
|
45ac6472e2 | ||
|
|
9235c5de6c | ||
|
|
6c091dbaf4 | ||
|
|
3e7e57945c | ||
|
|
23ce9de1c3 | ||
|
|
6aee7bf378 | ||
|
|
5b360238b2 | ||
|
|
5b44c9668f | ||
|
|
d01a403c65 | ||
|
|
b65649725e | ||
|
|
89c2f1bd6b | ||
|
|
3478f78a05 | ||
|
|
19cedb01bc | ||
|
|
cb24c4a863 | ||
|
|
93478e7c51 | ||
|
|
4eac5f4d9d | ||
|
|
e76370b3b7 | ||
|
|
1e3153c3fb | ||
|
|
d0a95e4fde | ||
|
|
7174ebbb4b | ||
|
|
e81cec1633 | ||
|
|
02c04550fd | ||
|
|
b0bfc7758a | ||
|
|
cbf21b13d7 | ||
|
|
14f3c3e13e | ||
|
|
05ea6e9e66 | ||
|
|
74d2e6d5d8 | ||
|
|
1b8f9c2b03 | ||
|
|
491158dfad | ||
|
|
7fa08e31c6 | ||
|
|
7ac9ce7ffb | ||
|
|
197779697d | ||
|
|
96fabe4b29 | ||
|
|
4a4d35b625 | ||
|
|
92739a06e2 | ||
|
|
244e9586ca | ||
|
|
13a2ccae66 | ||
|
|
ed34845b10 | ||
|
|
74b104c9f6 | ||
|
|
1f8cdf85b6 | ||
|
|
dc02a8ce35 | ||
|
|
65e726324a | ||
|
|
fe3c42af9e | ||
|
|
28c7e7173f | ||
|
|
a897a26daa | ||
|
|
ffce214362 | ||
|
|
bdb8e4847f | ||
|
|
9750d8c3ad | ||
|
|
dadec16661 | ||
|
|
b5cd2763db | ||
|
|
a08dd70354 | ||
|
|
5b39d1fc6a | ||
|
|
38513de954 | ||
|
|
cc1ae25c3a | ||
|
|
002fa0e791 | ||
|
|
0589de7523 | ||
|
|
4e7e0139fd | ||
|
|
c044e1d433 | ||
|
|
fb0edf564e | ||
|
|
969ce68d5d | ||
|
|
6ef2e04518 | ||
|
|
bc84fe9407 | ||
|
|
38e8416da5 | ||
|
|
cf0e07aecd | ||
|
|
8e014bbf97 | ||
|
|
f9013ad7a6 | ||
|
|
8d2d50573a | ||
|
|
d25965afba | ||
|
|
6f895935a9 | ||
|
|
70ebc969f9 | ||
|
|
6d01138797 | ||
|
|
779f1371a1 | ||
|
|
5d24a846e3 | ||
|
|
c4f1c25a87 | ||
|
|
7d754b2865 | ||
|
|
232e38f105 | ||
|
|
725afc71fd | ||
|
|
6d60eaf04e | ||
|
|
7c1d8cf4f1 | ||
|
|
e1a0df68fc | ||
|
|
14832117b6 | ||
|
|
56deaafb34 | ||
|
|
b8b36f3293 | ||
|
|
a9ab708494 | ||
|
|
a33363902c | ||
|
|
3ca3d10e28 | ||
|
|
05c236481f | ||
|
|
25e2bdb7de | ||
|
|
12bd4cf9e9 | ||
|
|
0d79681070 | ||
|
|
47d7f52149 | ||
|
|
000a5cc80a | ||
|
|
66f51f5166 | ||
|
|
7360deee74 | ||
|
|
e0916e793b | ||
|
|
f43a79bc13 | ||
|
|
fb56a6a54d | ||
|
|
f6936f222a | ||
|
|
553aa40b34 | ||
|
|
ac9c339e5a | ||
|
|
0ae3b33f2f | ||
|
|
6dbe5637ef | ||
|
|
09d8570922 | ||
|
|
4ab433418e | ||
|
|
73e3b47ebd | ||
|
|
bb7dce941a | ||
|
|
55e9255264 | ||
|
|
cf53ef02c4 | ||
|
|
c8ea418154 | ||
|
|
44920b4be8 | ||
|
|
b980e50acf | ||
|
|
e78e3f16e1 | ||
|
|
714c2f7373 | ||
|
|
f1036beb1f | ||
|
|
bd4e55b149 | ||
|
|
22e5e63a65 | ||
|
|
414dba994f | ||
|
|
934def3b75 | ||
|
|
74c2837d87 | ||
|
|
40c480c2cf | ||
|
|
ad82c7ebf1 | ||
|
|
86b126628c | ||
|
|
24ee3afdae | ||
|
|
08a9f5e683 | ||
|
|
ffb09a429d | ||
|
|
87048330b7 | ||
|
|
c2f7c5fbeb | ||
|
|
4fde7e4f2f | ||
|
|
3369cf9a70 | ||
|
|
ff5e83cd42 | ||
|
|
193a564c8f | ||
|
|
296e2369d7 | ||
|
|
16cb7cc550 | ||
|
|
c86b8c8130 | ||
|
|
1b1badfd02 | ||
|
|
b7f990fe4a | ||
|
|
e639efb77c | ||
|
|
41c2f8b9e0 | ||
|
|
78b33eb47b | ||
|
|
7e1164762c | ||
|
|
8a9203e8e3 | ||
|
|
99cae405ca | ||
|
|
f11c9c5105 | ||
|
|
f8f98d2581 | ||
|
|
e7648662ae | ||
|
|
69c4021646 | ||
|
|
d0084cb9df | ||
|
|
5f59582e63 | ||
|
|
c89c516467 | ||
|
|
40bf0e0ce9 | ||
|
|
07e3e924c3 | ||
|
|
f2bb5b5d7f | ||
|
|
3bd6771ae7 | ||
|
|
527176ca97 | ||
|
|
0cd8d64607 | ||
|
|
68e408b930 | ||
|
|
74b03de449 | ||
|
|
ac1c77d499 | ||
|
|
c7aaf4249a | ||
|
|
11dda63f7b | ||
|
|
04b799c0c5 | ||
|
|
44f9363e39 | ||
|
|
43513d3fca | ||
|
|
e41f2f6588 | ||
|
|
17d870a842 | ||
|
|
ab02558181 | ||
|
|
fa1a128aeb | ||
|
|
1f0a7e7697 | ||
|
|
cce788388f | ||
|
|
a93c24b084 | ||
|
|
6a2f531f9b | ||
|
|
eda1694581 | ||
|
|
1854d03b95 | ||
|
|
2180777f73 | ||
|
|
f2fc779c94 | ||
|
|
3a024b6d8f | ||
|
|
33d9957bcb | ||
|
|
28c0a8bc81 | ||
|
|
77471c500e | ||
|
|
98eaef2778 | ||
|
|
78f4541116 | ||
|
|
2a3fe73a6d | ||
|
|
9c3cb154d3 | ||
|
|
4e68a36141 | ||
|
|
b62fa06753 | ||
|
|
17129f64db | ||
|
|
524c73d8b7 | ||
|
|
78e91e6bc4 | ||
|
|
9d260cac0b | ||
|
|
01d91dd65c | ||
|
|
69d96e1e66 | ||
|
|
1ab12dadac | ||
|
|
a7dc701f26 | ||
|
|
590ad9cd93 | ||
|
|
cbca5177ea | ||
|
|
e3105bee39 | ||
|
|
0778919e7f | ||
|
|
e36684786f | ||
|
|
8fcd6524db | ||
|
|
23fb01f921 | ||
|
|
14915108d1 | ||
|
|
20cc07c5cd | ||
|
|
2f74785620 | ||
|
|
03894fea3c | ||
|
|
f407ca18a4 | ||
|
|
07394a1621 | ||
|
|
8bf416cd64 | ||
|
|
1d2a5997cc | ||
|
|
53e6758cc3 | ||
|
|
2df1fa90f1 | ||
|
|
a454d53c28 | ||
|
|
6d5948d814 | ||
|
|
18061ad177 | ||
|
|
8eb59bc500 | ||
|
|
ddb3046ccd | ||
|
|
5e3bc86268 | ||
|
|
9dfb5705c5 | ||
|
|
66ea0f78be | ||
|
|
c99c84558f | ||
|
|
d35edcf5fa | ||
|
|
8718d62b11 | ||
|
|
939fa25a5b | ||
|
|
93eb9ee4d8 | ||
|
|
154c095e66 | ||
|
|
50a97856dc | ||
|
|
02cb93311a | ||
|
|
49b3721b75 | ||
|
|
db18515aed | ||
|
|
2708191d3c | ||
|
|
16612c6e49 | ||
|
|
40d45ee21e | ||
|
|
3df3b52b18 | ||
|
|
efbf8b0203 | ||
|
|
c84c006f42 | ||
|
|
97e0eda477 | ||
|
|
f1d78103e4 | ||
|
|
7072eec225 | ||
|
|
9b1cda9025 | ||
|
|
2c06731a14 | ||
|
|
b14692cb95 | ||
|
|
253f6726c0 | ||
|
|
36db3b7f3f | ||
|
|
385cdef89b | ||
|
|
72870d8331 | ||
|
|
081c469f6f | ||
|
|
1affa7a16e | ||
|
|
701c5da823 | ||
|
|
860f75ee97 | ||
|
|
334710ebf7 | ||
|
|
b15da3e118 | ||
|
|
f1a9b9bf43 | ||
|
|
abffe983f8 | ||
|
|
b363e4afe7 | ||
|
|
b60582ff75 | ||
|
|
92dae88f62 | ||
|
|
f7d9c92820 | ||
|
|
860bba759d | ||
|
|
564ef66665 | ||
|
|
c8642efeb0 | ||
|
|
309bb778af | ||
|
|
36c340b40f | ||
|
|
a79c54be66 | ||
|
|
143c8c4ffd | ||
|
|
116bcdac28 | ||
|
|
4762d6a0a7 | ||
|
|
6e00deffd0 | ||
|
|
34f3e5ef23 | ||
|
|
b9dfbcee13 | ||
|
|
de2ac37c92 | ||
|
|
a194a557c5 | ||
|
|
8e34dc4cdb | ||
|
|
bbb2f0de8d | ||
|
|
4c3fcc7b16 | ||
|
|
f33fef3ca3 | ||
|
|
10537df82d | ||
|
|
8af6b07d07 | ||
|
|
c014c95e1b | ||
|
|
bd86f53b9f | ||
|
|
042117ee30 | ||
|
|
38e633d49b | ||
|
|
ce286010a8 | ||
|
|
ef18b6684b | ||
|
|
ab984f98ab | ||
|
|
cfadb2de36 | ||
|
|
3d0b378c40 | ||
|
|
aa56359834 | ||
|
|
a0a60c2d29 | ||
|
|
324223ddff | ||
|
|
58e7a50de8 | ||
|
|
739b82c34d | ||
|
|
b5bf9d17e3 | ||
|
|
917fb024ea | ||
|
|
7a05ea0f93 | ||
|
|
5d75c47fcc | ||
|
|
c98fd886fd | ||
|
|
94fdb36f6b | ||
|
|
d1a40ace7d | ||
|
|
dd4d69a288 | ||
|
|
e16ea8df11 | ||
|
|
80e1942980 | ||
|
|
99109278a6 | ||
|
|
f924a880c8 | ||
|
|
5683d26d24 | ||
|
|
740aef06df | ||
|
|
557f6d1743 | ||
|
|
8d257f5c57 | ||
|
|
d880a14b1a | ||
|
|
d4fcc00821 | ||
|
|
6d8bb292ec | ||
|
|
c638401469 | ||
|
|
5930574d8a | ||
|
|
fdef7c8689 | ||
|
|
5c36a1be1b | ||
|
|
14f747019b | ||
|
|
d7d5d2a9be | ||
|
|
750955171a | ||
|
|
36fd93215b | ||
|
|
6e7353170a | ||
|
|
62cc117c12 | ||
|
|
af00695383 | ||
|
|
85ac91eb2b | ||
|
|
d1ed582919 | ||
|
|
06a14c4a76 | ||
|
|
400d5281f4 | ||
|
|
50778670fb | ||
|
|
9037cd779d | ||
|
|
c23a4cbd9f | ||
|
|
5d8f7801f7 | ||
|
|
756134cf2b | ||
|
|
c16677fd6f | ||
|
|
13bc514aa6 | ||
|
|
1c3403825c | ||
|
|
6f1ac7c949 | ||
|
|
f5d3fd8a82 | ||
|
|
0a23a78523 | ||
|
|
725cf33f1a | ||
|
|
2d6db3f980 | ||
|
|
41b9584bb3 | ||
|
|
c915435417 | ||
|
|
afcde1222b | ||
|
|
a3cb512628 | ||
|
|
fc71ba07da | ||
|
|
9c867225ee | ||
|
|
972355e2fc | ||
|
|
8f4c80699f | ||
|
|
e79b00d9d1 | ||
|
|
2f6b1eaaf9 | ||
|
|
3546ffa3ef | ||
|
|
30f96860a7 | ||
|
|
41d50f1a71 | ||
|
|
1e9c80f34c | ||
|
|
b88d2ed812 | ||
|
|
92dd37d3c7 | ||
|
|
f24cc6a8fc | ||
|
|
3117a488ec | ||
|
|
185b91de13 | ||
|
|
e20024f0f0 | ||
|
|
501ec1905e | ||
|
|
7ebd2011ff | ||
|
|
8004f354aa | ||
|
|
77e1aead40 | ||
|
|
e48a88a4b3 | ||
|
|
1154d9f663 | ||
|
|
810a707069 | ||
|
|
7fa9e9bfb9 | ||
|
|
48b0e7e695 | ||
|
|
848fa57144 | ||
|
|
4101e831af | ||
|
|
eb803ba6a7 | ||
|
|
248a8e7c54 | ||
|
|
2fa8c8cd7f | ||
|
|
8d5f5586dc | ||
|
|
c5bc7bd5f9 | ||
|
|
2b3bd29588 | ||
|
|
0be3f9e57e | ||
|
|
2524ece2cc | ||
|
|
b975472828 | ||
|
|
20427ff1f6 | ||
|
|
5b3b825ab2 | ||
|
|
ccd3348eed | ||
|
|
845b9be1f5 | ||
|
|
400f2b130a | ||
|
|
362f7cedf6 | ||
|
|
2c959469f0 | ||
|
|
6c34bcf8f6 | ||
|
|
f510b62b7f | ||
|
|
b31387416d | ||
|
|
941a6cb434 | ||
|
|
931072f5d1 | ||
|
|
f4f9db73da | ||
|
|
9ef3f30c56 | ||
|
|
d19c927760 | ||
|
|
22e5b21f10 | ||
|
|
f0d2444769 | ||
|
|
7975060e4a | ||
|
|
354dc3d272 | ||
|
|
7640e95bd7 | ||
|
|
767115a93d | ||
|
|
f0c8788a52 | ||
|
|
a3cbb01b27 | ||
|
|
d630b4ae8a |
@@ -1,8 +1,2 @@
|
||||
# This file is _not_ included in the tarballs for now
|
||||
# Binary builds on Linux packaging infrastructure need to overwrite it to make `cargo vendor` work
|
||||
# Releases and development builds made using OBS/Launchpad will _not_ reflect the contents of this
|
||||
# file
|
||||
|
||||
[resolver]
|
||||
# Make cargo 1.84+ respect MSRV (rust-version in Cargo.toml)
|
||||
incompatible-rust-versions = "fallback"
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
|
||||
@@ -29,6 +29,7 @@ freebsd_task:
|
||||
freebsd_instance:
|
||||
image: freebsd-15-0-release-amd64-ufs # updatecli.d/cirrus-freebsd.yml
|
||||
tests_script:
|
||||
- pkg update
|
||||
- pkg install -y cmake-core devel/pcre2 devel/ninja gettext git-lite lang/rust misc/py-pexpect
|
||||
# libclang.so is a required build dependency for rust-c++ ffi bridge
|
||||
- pkg install -y llvm
|
||||
|
||||
@@ -9,7 +9,7 @@ trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 100
|
||||
|
||||
[{Makefile,*.in}]
|
||||
[{Makefile,{BSD,GNU}makefile}]
|
||||
indent_style = tab
|
||||
|
||||
[*.{md,rst}]
|
||||
@@ -21,7 +21,7 @@ indent_size = 4
|
||||
[build_tools/release.sh]
|
||||
max_line_length = 72
|
||||
|
||||
[Dockerfile]
|
||||
[Vagrantfile]
|
||||
indent_size = 2
|
||||
|
||||
[share/{completions,functions}/**.fish]
|
||||
@@ -29,3 +29,6 @@ max_line_length = unset
|
||||
|
||||
[{COMMIT_EDITMSG,git-revise-todo,*.jjdescription}]
|
||||
max_line_length = 72
|
||||
|
||||
[*.{toml,yml}]
|
||||
indent_size = 2
|
||||
|
||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -18,9 +18,6 @@
|
||||
/.github/* export-ignore
|
||||
/.builds export-ignore
|
||||
/.builds/* export-ignore
|
||||
# to make cargo vendor work correctly
|
||||
/.cargo/ export-ignore
|
||||
/.cargo/config.toml export-ignore
|
||||
|
||||
# for linguist, which drives GitHub's language statistics
|
||||
alpine.js linguist-vendored
|
||||
|
||||
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,11 +1,8 @@
|
||||
## Description
|
||||
|
||||
Talk about your changes here.
|
||||
|
||||
Fixes issue #
|
||||
|
||||
## TODOs:
|
||||
<!-- Just check off what what we know been done so far. We can help you with this stuff. -->
|
||||
<!-- Check off what what has been done so far. -->
|
||||
- [ ] If addressing an issue, a commit message mentions `Fixes issue #<issue-number>`
|
||||
- [ ] Changes to fish usage are reflected in user documentation/manpages.
|
||||
- [ ] Tests have been added for regressions fixed
|
||||
- [ ] User-visible changes noted in CHANGELOG.rst <!-- Don't document changes for completions inside CHANGELOG.rst, there are lot of such edits -->
|
||||
- [ ] User-visible changes noted in CHANGELOG.rst <!-- Usually skipped for changes to completions -->
|
||||
|
||||
2
.github/actions/rust-toolchain/action.yml
vendored
2
.github/actions/rust-toolchain/action.yml
vendored
@@ -25,7 +25,7 @@ runs:
|
||||
set -x
|
||||
toolchain=$(
|
||||
case "$toolchain_channel" in
|
||||
(stable) echo 1.92 ;; # updatecli.d/rust.yml
|
||||
(stable) echo 1.95 ;; # updatecli.d/rust.yml
|
||||
(msrv) echo 1.85 ;; # updatecli.d/rust.yml
|
||||
(*)
|
||||
printf >&2 "error: unsupported toolchain channel %s" "$toolchain_channel"
|
||||
|
||||
2
.github/workflows/autolabel_prs.yml
vendored
2
.github/workflows/autolabel_prs.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
steps:
|
||||
- name: Set label and milestone
|
||||
id: set-label-milestone
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8, build_tools/update-dependencies.sh
|
||||
with:
|
||||
script: |
|
||||
const completionsLabel = 'completions';
|
||||
|
||||
8
.github/workflows/build_docker_images.yml
vendored
8
.github/workflows/build_docker_images.yml
vendored
@@ -37,10 +37,10 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
-
|
||||
name: Login to Container registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0, build_tools/update-dependencies.sh
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -48,14 +48,14 @@ jobs:
|
||||
-
|
||||
name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0, build_tools/update-dependencies.sh
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.NAMESPACE }}/${{ matrix.target }}
|
||||
flavor: |
|
||||
latest=true
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0, build_tools/update-dependencies.sh
|
||||
with:
|
||||
context: docker/context
|
||||
push: true
|
||||
|
||||
32
.github/workflows/development-builds.yml
vendored
Normal file
32
.github/workflows/development-builds.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Linux development builds
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- buildscript
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment: linux-development
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
- name: Update package database
|
||||
run: sudo apt-get update
|
||||
- name: Install deps
|
||||
run: sudo apt install debhelper devscripts dpkg-dev
|
||||
- name: Create tarball and source packages
|
||||
run: |
|
||||
version=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
|
||||
mkdir /tmp/gpg
|
||||
echo "$SIGNING_GPG_KEY" > /tmp/gpg/signing-gpg-key
|
||||
mkdir /tmp/fish-built
|
||||
FISH_ARTEFACT_PATH=/tmp/fish-built ./build_tools/make_tarball.sh
|
||||
FISH_ARTEFACT_PATH=/tmp/fish-built DEB_SIGN_KEYFILE=/tmp/gpg/signing-gpg-key ./build_tools/make_linux_packages.sh $version
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: linux-source-packages
|
||||
path: |
|
||||
/tmp/fish-built
|
||||
! /tmp/fish-built/fish-*/* # don't include the unpacked source directory
|
||||
6
.github/workflows/lint-dependencies.yml
vendored
6
.github/workflows/lint-dependencies.yml
vendored
@@ -16,9 +16,9 @@ jobs:
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: EmbarkStudios/cargo-deny-action@44db170f6a7d12a6e90340e9e0fca1f650d34b14 # v2.0.15, build_tools/update-dependencies.sh
|
||||
with:
|
||||
command: check licenses
|
||||
arguments: --all-features --locked --exclude-dev
|
||||
rust-version: 1.92 # updatecli.d/rust.yml
|
||||
rust-version: 1.95 # updatecli.d/rust.yml
|
||||
|
||||
42
.github/workflows/lint.yml
vendored
42
.github/workflows/lint.yml
vendored
@@ -9,19 +9,40 @@ jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: ./.github/actions/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
- name: install dependencies
|
||||
run: pip install ruff
|
||||
- name: build fish
|
||||
run: cargo build
|
||||
run: cargo build --bin fish_indent
|
||||
- name: check format
|
||||
run: PATH="target/debug:$PATH" build_tools/style.fish --all --check
|
||||
run: PATH="target/debug:$PATH" cargo xtask format --all --check
|
||||
- name: check rustfmt
|
||||
run: find build.rs crates src -type f -name '*.rs' | xargs rustfmt --check
|
||||
|
||||
shellcheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: ./.github/actions/rust-toolchain@stable
|
||||
- name: Update package database
|
||||
run: sudo apt-get update
|
||||
- name: Install shellcheck
|
||||
run: sudo apt install shellcheck
|
||||
- name: shellcheck
|
||||
run: cargo xtask shellcheck
|
||||
|
||||
po_files_up_to_date:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: ./.github/actions/rust-toolchain@stable
|
||||
- name: Install deps
|
||||
uses: ./.github/actions/install-dependencies
|
||||
- name: Check PO files
|
||||
run: cargo xtask gettext check
|
||||
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -32,25 +53,36 @@ jobs:
|
||||
features: ""
|
||||
- rust_version: "stable"
|
||||
features: "--no-default-features"
|
||||
- rust_version: "stable"
|
||||
features: "--all-features"
|
||||
- rust_version: "msrv"
|
||||
features: ""
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: ./.github/actions/rust-toolchain
|
||||
with:
|
||||
toolchain_channel: ${{ matrix.rust_version }}
|
||||
components: clippy
|
||||
- name: Update package database
|
||||
run: sudo apt-get update
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt install gettext
|
||||
- name: Patch Cargo.toml to deny unknown lints
|
||||
run: |
|
||||
if [ "${{ matrix.rust_version }}" = stable ]; then
|
||||
sed -i /^rust.unknown_lints/d Cargo.toml
|
||||
fi
|
||||
- name: cargo clippy
|
||||
run: cargo clippy --workspace --all-targets ${{ matrix.features }} -- --deny=warnings
|
||||
|
||||
rustdoc:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: ./.github/actions/rust-toolchain@stable
|
||||
- name: Update package database
|
||||
run: sudo apt-get update
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt install gettext
|
||||
|
||||
2
.github/workflows/lockthreads.yml
vendored
2
.github/workflows/lockthreads.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
pull-requests: write # for dessant/lock-threads to lock PRs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4
|
||||
- uses: dessant/lock-threads@f5f995c727ac99a91dec92781a8e34e7c839a65e # v6.0.0, build_tools/update-dependencies.sh
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '365'
|
||||
|
||||
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
name: Pre-release checks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
with:
|
||||
# Workaround for https://github.com/actions/checkout/issues/882
|
||||
ref: ${{ inputs.version }}
|
||||
@@ -36,10 +36,12 @@ jobs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
tarball-name: ${{ steps.version.outputs.tarball-name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
with:
|
||||
# Workaround for https://github.com/actions/checkout/issues/882
|
||||
ref: ${{ inputs.version }}
|
||||
- name: Update package database
|
||||
run: sudo apt-get update
|
||||
- name: Install dependencies
|
||||
run: sudo apt install cmake gettext ninja-build python3-pip
|
||||
- uses: ./.github/actions/install-sphinx
|
||||
@@ -61,7 +63,7 @@ jobs:
|
||||
sed -n 2p "$relnotes" | grep -q '^$'
|
||||
sed -i 1,2d "$relnotes"
|
||||
- name: Upload tarball artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0, build_tools/update-dependencies.sh
|
||||
with:
|
||||
name: source-tarball
|
||||
path: |
|
||||
@@ -74,7 +76,7 @@ jobs:
|
||||
name: Build single-file fish for Linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
with:
|
||||
# Workaround for https://github.com/actions/checkout/issues/882
|
||||
ref: ${{ inputs.version }}
|
||||
@@ -82,6 +84,8 @@ jobs:
|
||||
uses: ./.github/actions/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-unknown-linux-musl,aarch64-unknown-linux-musl
|
||||
- name: Update package database
|
||||
run: sudo apt-get update
|
||||
- name: Install dependencies
|
||||
run: sudo apt install crossbuild-essential-arm64 gettext musl-tools
|
||||
- uses: ./.github/actions/install-sphinx
|
||||
@@ -100,7 +104,7 @@ jobs:
|
||||
tar -cazf fish-$(git describe)-linux-$arch.tar.xz \
|
||||
-C target/$arch-unknown-linux-musl/release fish
|
||||
done
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0, build_tools/update-dependencies.sh
|
||||
with:
|
||||
name: Static builds for Linux
|
||||
path: fish-${{ inputs.version }}-linux-*.tar.xz
|
||||
@@ -114,19 +118,19 @@ jobs:
|
||||
name: Create release draft
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
with:
|
||||
# Workaround for https://github.com/actions/checkout/issues/882
|
||||
ref: ${{ inputs.version }}
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0, build_tools/update-dependencies.sh
|
||||
with:
|
||||
merge-multiple: true
|
||||
path: /tmp/artifacts
|
||||
- name: List artifacts
|
||||
run: find /tmp/artifacts -type f
|
||||
- name: Create draft release
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0, build_tools/update-dependencies.sh
|
||||
with:
|
||||
tag_name: ${{ inputs.version }}
|
||||
name: fish ${{ inputs.version }}
|
||||
@@ -142,7 +146,7 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
environment: macos-codesign
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
with:
|
||||
# Workaround for https://github.com/actions/checkout/issues/882
|
||||
ref: ${{ inputs.version }}
|
||||
|
||||
44
.github/workflows/test.yml
vendored
44
.github/workflows/test.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: ./.github/actions/rust-toolchain@oldest-supported
|
||||
- name: Install deps
|
||||
uses: ./.github/actions/install-dependencies
|
||||
@@ -32,30 +32,23 @@ jobs:
|
||||
- name: make fish_run_tests
|
||||
run: |
|
||||
make -C build VERBOSE=1 fish_run_tests
|
||||
- name: translation updates
|
||||
run: |
|
||||
# Generate PO files. This should not result it a change in the repo if all translations are
|
||||
# up to date.
|
||||
# Ensure that fish is available as an executable.
|
||||
PATH="$PWD/build:$PATH" build_tools/update_translations.fish
|
||||
# Show diff output. Fail if there is any.
|
||||
git --no-pager diff --exit-code || { echo 'There are uncommitted changes after regenerating the gettext PO files. Make sure to update them via `build_tools/update_translations.fish` after changing source files.'; exit 1; }
|
||||
|
||||
ubuntu-32bit-static-pcre2:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: ./.github/actions/rust-toolchain@oldest-supported
|
||||
with:
|
||||
targets: "i586-unknown-linux-gnu"
|
||||
- name: Update package database
|
||||
run: sudo apt-get update
|
||||
- name: Install deps
|
||||
uses: ./.github/actions/install-dependencies
|
||||
with:
|
||||
include_pcre: false
|
||||
include_sphinx: false
|
||||
- name: Install g++-multilib
|
||||
run: |
|
||||
sudo apt install g++-multilib
|
||||
run: sudo apt install g++-multilib
|
||||
- name: cmake
|
||||
env:
|
||||
CFLAGS: "-m32"
|
||||
@@ -80,13 +73,15 @@ jobs:
|
||||
RUSTFLAGS: "-Zsanitizer=address"
|
||||
# RUSTFLAGS: "-Zsanitizer=memory -Zsanitizer-memory-track-origins"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
# All -Z options require running nightly
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
# ASAN uses `cargo build -Zbuild-std` which requires the rust-src component
|
||||
# this is comma-separated
|
||||
components: rust-src
|
||||
- name: Update package database
|
||||
run: sudo apt-get update
|
||||
- name: Install deps
|
||||
uses: ./.github/actions/install-dependencies
|
||||
with:
|
||||
@@ -128,7 +123,7 @@ jobs:
|
||||
# of crates.io, so give this a try. It's also sometimes significantly faster on all platforms.
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: ./.github/actions/rust-toolchain@oldest-supported
|
||||
- name: Install deps
|
||||
run: |
|
||||
@@ -155,22 +150,25 @@ jobs:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: msys2/setup-msys2@v2
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2, build_tools/update-dependencies.sh
|
||||
- uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2.30.0, build_tools/update-dependencies.sh
|
||||
with:
|
||||
update: true
|
||||
msystem: MSYS
|
||||
id: msys2
|
||||
- name: Install deps
|
||||
# Not using setup-msys2 `install` option to make it easier to copy/paste
|
||||
run: |
|
||||
pacman --noconfirm -S --needed git rust
|
||||
pacman --noconfirm -S --needed git rust python3 diffutils tmux
|
||||
- name: rebase
|
||||
env:
|
||||
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
|
||||
shell: cmd
|
||||
run: |
|
||||
"%MSYS2_LOCATION%\usr\bin\dash" /usr/bin/rebaseall -p -v
|
||||
- name: cargo build
|
||||
run: |
|
||||
cargo build
|
||||
- name: smoketest
|
||||
# We can't run `build_tools/check.sh` yet, there are just too many failures
|
||||
# so this is just a quick check to make sure that fish can swim
|
||||
- name: tests
|
||||
run: |
|
||||
set -x
|
||||
[ "$(target/debug/fish.exe -c 'echo (math 1 + 1)')" = 2 ]
|
||||
cargo test
|
||||
cargo xtask check
|
||||
|
||||
53
.gitignore
vendored
53
.gitignore
vendored
@@ -20,7 +20,6 @@
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
!tests/*.out
|
||||
*.out
|
||||
*.pch
|
||||
*.slo
|
||||
@@ -36,46 +35,31 @@
|
||||
Desktop.ini
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
__pycache__/
|
||||
|
||||
.directory
|
||||
.fuse_hidden*
|
||||
|
||||
|
||||
# Directories that only contain transitory files from building and testing.
|
||||
/doc/
|
||||
/share/man/
|
||||
/share/doc/
|
||||
/test/
|
||||
/user_doc/
|
||||
|
||||
# File names that can appear in the project root that represent artifacts from
|
||||
# building and testing.
|
||||
/FISH-BUILD-VERSION-FILE
|
||||
/command_list.txt
|
||||
/command_list_toc.txt
|
||||
/compile_commands.json
|
||||
/doc.h
|
||||
# Artifacts from in-tree builds ("cmake .").
|
||||
/build.ninja
|
||||
/cargo/
|
||||
/CMakeCache.txt
|
||||
/CMakeFiles/
|
||||
/cmake_install.cmake
|
||||
/fish
|
||||
/fish.pc
|
||||
/fish_indent
|
||||
/fish_key_reader
|
||||
/fish_tests
|
||||
/lexicon.txt
|
||||
/lexicon_filter
|
||||
/toc.txt
|
||||
/version
|
||||
fish-build-version-witness.txt
|
||||
__pycache__
|
||||
/fish-localization-map-cache/
|
||||
/fish.pc
|
||||
/fish.pc.noversion
|
||||
/.ninja_log
|
||||
|
||||
# File names that can appear below the project root that represent artifacts
|
||||
# from building and testing.
|
||||
/doc_src/commands.hdr
|
||||
/doc_src/index.hdr
|
||||
/po/*.gmo
|
||||
/share/__fish_build_paths.fish
|
||||
/share/pkgconfig
|
||||
/tests/*.tmp.*
|
||||
/tests/.last-check-all-files
|
||||
/.venv/
|
||||
|
||||
# xcode
|
||||
## Build generated
|
||||
@@ -83,24 +67,19 @@ __pycache__
|
||||
*.xccheckout
|
||||
*.xcscmblueprin
|
||||
.vscode
|
||||
/DerivedData/
|
||||
/build/
|
||||
/DerivedData/
|
||||
/tags
|
||||
xcuserdata/
|
||||
/xcuserdata/
|
||||
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
/target/
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# Generated by clangd
|
||||
/.cache
|
||||
/.cache/
|
||||
|
||||
# JetBrains editors.
|
||||
.idea/
|
||||
|
||||
182
CHANGELOG.rst
182
CHANGELOG.rst
@@ -1,3 +1,177 @@
|
||||
fish 4.7.1 (released May 08, 2026)
|
||||
==================================
|
||||
|
||||
This release fixes a regression in 4.7.0 that caused the web config (``fish_config``) to fail to start (:issue:`12717`).
|
||||
|
||||
fish 4.7.0 (released May 05, 2026)
|
||||
==================================
|
||||
|
||||
Deprecations and removed features
|
||||
---------------------------------
|
||||
- The default theme (i.e. the ``fish_color_*`` variables) is no longer set in non-interactive shells.
|
||||
|
||||
Interactive improvements
|
||||
------------------------
|
||||
- :doc:`prompt_pwd <cmds/prompt_pwd>` now strips control characters.
|
||||
- Repaint events (as triggered by changes to color variables or by event handlers running ``commandline -f repaint``) no longer reset the completion pager and other transient UI states (:issue:`12683`).
|
||||
- :envvar:`fish_color_valid_path` now respects background and underline colors (:issue:`12622`).
|
||||
- :doc:`funced <cmds/funced>` will no longer lose work if there are parse errors multiple times without new changes to the file.
|
||||
- Fixed a case where directory completions were sorted in a surprising order (:issue:`12695`).
|
||||
- When at the command token, the :kbd:`alt-o` binding will now open read-only files too (:issue:`12671`).
|
||||
- Private mode in-memory history (``set fish_history``) is no longer shared with :doc:`builtin read <cmds/read>` (:issue:`12662`).
|
||||
|
||||
Other improvements
|
||||
------------------
|
||||
- History is no longer corrupted with NUL bytes when fish receives SIGTERM or SIGHUP (:issue:`10300`).
|
||||
- :doc:`fish_update_completions <cmds/fish_update_completions>` now handles groff ``\X'...'`` device control escapes, fixing completion generation for man pages produced by help2man 1.50 and later (such as coreutils 9.10).
|
||||
- Removing history entries via the :doc:`web-based config <cmds/fish_config>` is more intuitive.
|
||||
- If :envvar:`XDG_DATA_DIRS` is empty, the default value is assumed, which means that fish will now also use configuration from paths like ``$PREFIX/share/fish/vendor_completions.d`` (:issue:`11349`).
|
||||
- Some internal file descriptors were moved to number 10 or higher, to reduce risk of clashes with those used by the user in scripts.
|
||||
- The wording of error messages has been made consistent, especially for builtin subcommands (:issue:`12556`).
|
||||
|
||||
For distributors and developers
|
||||
-------------------------------
|
||||
- When the default global config directory (``$PREFIX/etc/fish``) exists but has been overridden via ``-DCMAKE_INSTALL_SYSCONFDIR``, fish will now respect that override (:issue:`10748`).
|
||||
- ``build_tools/update_translations.fish`` has been replaced by ``cargo xtask gettext {check,new,update}`` (:issue:`12676`).
|
||||
- ``cargo xtask shellcheck`` to lint shell-scripts.
|
||||
|
||||
Regression fixes:
|
||||
-----------------
|
||||
- (from 4.6) Vi mode ``dl`` (:issue:`12461`).
|
||||
- (from 4.6) Backspace after newline (:issue:`12583`).
|
||||
- (from 4.3.3) Long options were spuriously completed after typing short options (85e76ba3561).
|
||||
- (from 3.2) ``nosuchcommand || echo hello`` executes the right hand side again (:issue:`12654`).
|
||||
|
||||
fish 4.6.0 (released March 28, 2026)
|
||||
====================================
|
||||
|
||||
Notable improvements and fixes
|
||||
------------------------------
|
||||
- New Spanish translations (:issue:`12489`).
|
||||
- New Japanese translations (:issue:`12499`).
|
||||
|
||||
Deprecations and removed features
|
||||
---------------------------------
|
||||
- The default width for emoji is switched from 1 to 2, improving the experience for users connecting to old systems from modern desktops. Users of old desktops who notice that lines containing emoji are misaligned can set ``$fish_emoji_width`` back to 1 (:issue:`12562`).
|
||||
|
||||
Interactive improvements
|
||||
------------------------
|
||||
- The tab completion pager now left-justifies the description of each column (:issue:`12546`).
|
||||
- fish now supports the ``SHELL_PROMPT_PREFIX``, ``SHELL_PROMPT_SUFFIX``, and ``SHELL_WELCOME`` environment variables. The prefix and suffix are automatically prepended and appended to the left prompt, and the welcome message is displayed on startup after the greeting.
|
||||
These variables are set by systemd's ``run0`` for example (:issue:`10924`).
|
||||
|
||||
Improved terminal support
|
||||
-------------------------
|
||||
- ``set_color`` is able to turn off italics, reverse mode, strikethrough and underline individually (e.g. ``--italics=off``).
|
||||
- ``set_color`` learned the foreground (``--foreground`` or ``-f``) and reset (``--reset``) options.
|
||||
- An error caused by slow terminal responses at macOS startup has been addressed (:issue:`12571`).
|
||||
|
||||
Other improvements
|
||||
------------------
|
||||
- Signals like ``SIGWINCH`` (as sent on terminal resize) no longer interrupt builtin output (:issue:`12496`).
|
||||
- For compatibility with Bash, fish now accepts ``|&`` as alternate spelling of ``&|``, for piping both standard output and standard error (:issue:`11516`).
|
||||
- ``fish_indent`` now preserves comments and newlines immediately preceding a brace block (``{ }``) (:issue:`12505`).
|
||||
- A crash when suspending certain pipelines with :kbd:`ctrl-z` has been fixed (:issue:`12301`).
|
||||
|
||||
For distributors and developers
|
||||
-------------------------------
|
||||
- ``cargo xtask`` subcommands no longer panic on test failures.
|
||||
|
||||
Regression fixes:
|
||||
-----------------
|
||||
- (from 4.5.0) Intermediate ``⏎`` artifact when redrawing prompt (:issue:`12476`).
|
||||
- (from 4.4.0) ``history`` honors explicitly specified ``--color=`` again (:issue:`12512`).
|
||||
- (from 4.4.0) Vi mode ``dl`` and ``dh`` (:issue:`12461`).
|
||||
- (from 4.3.0) Error completing of commands starting with ``-`` (:issue:`12522`).
|
||||
|
||||
fish 4.5.0 (released February 17, 2026)
|
||||
=======================================
|
||||
|
||||
This is mostly a patch release for Vi mode regressions in 4.4.0 but other minor behavior changes are included as well.
|
||||
|
||||
Interactive improvements
|
||||
------------------------
|
||||
- :kbd:`ctrl-l` no longer cancels history search (:issue:`12436`).
|
||||
- History search cursor positioning now works correctly with characters of arbitrary width.
|
||||
|
||||
Deprecations and removed features
|
||||
---------------------------------
|
||||
- fish no longer reads the terminfo database to alter behaviour based on the :envvar:`TERM` environment variable, and does not depend on ncurses or terminfo. The ``ignore-terminfo`` feature flag, introduced and enabled by default in fish 4.1, is now permanently enabled. fish may no longer work correctly on Data General Dasher D220 and Wyse WY-350 terminals, but should continue to work on all known terminal emulators released in the 21st century.
|
||||
|
||||
Regression fixes:
|
||||
-----------------
|
||||
- (from 4.4.0) Vi mode ``d,f`` key binding did not work (:issue:`12417`).
|
||||
- (from 4.4.0) Vi mode ``c,w`` key binding wrongly deleted trailing spaces (:issue:`12443`).
|
||||
- (from 4.4.0) Vi mode crash on ``c,i,w`` after accepting autosuggestion (:issue:`12430`).
|
||||
- (from 4.4.0) ``fish_vi_key_bindings`` called with a mode argument produced an error (:issue:`12413`).
|
||||
- (from 4.0.0) Build on Illumos (:issue:`12410`).
|
||||
|
||||
fish 4.4.0 (released February 03, 2026)
|
||||
=======================================
|
||||
|
||||
Deprecations and removed features
|
||||
---------------------------------
|
||||
- The default fossil prompt has been disabled (:issue:`12342`).
|
||||
|
||||
Interactive improvements
|
||||
------------------------
|
||||
- The ``bind`` builtin lists mappings from all modes if ``--mode`` is not provided (:issue:`12214`).
|
||||
- Line-wise autosuggestions that don't start a command are no longer shown (739b82c34db, 58e7a50de8a).
|
||||
- Builtin ``history`` now assumes that :envvar:`PAGER` supports ANSI color sequences.
|
||||
- fish now clears the terminal's ``FLUSHO`` flag when acquiring control of the terminal, to fix an issue caused by pressing :kbd:`ctrl-o` on macOS (:issue:`12304`).
|
||||
|
||||
New or improved bindings
|
||||
------------------------
|
||||
- Vi mode word movements (``w``, ``W``, ``e``, and ``E``) are now largely in line with Vim. The only exception is that underscores are treated as word separators (:issue:`12269`).
|
||||
- New special input functions to support these movements: ``forward-word-vi``, ``kill-word-vi``, ``forward-bigword-vi``, ``kill-bigword-vi``, ``forward-word-end``, ``backward-word-end``, ``forward-bigword-end``, ``backward-bigword-end``, ``kill-a-word``, ``kill-inner-word``, ``kill-a-bigword``, and ``kill-inner-bigword``.
|
||||
- Vi mode key bindings now support counts for movement and deletion commands (e.g. `d3w` or `3l`), via a new operator mode (:issue:`2192`).
|
||||
- New ``catppuccin-*`` color themes.
|
||||
|
||||
Improved terminal support
|
||||
-------------------------
|
||||
- ``set_color`` learned the strikethrough (``--strikethrough`` or ``-s``) modifier.
|
||||
|
||||
For distributors and developers
|
||||
-------------------------------
|
||||
- The CMake option ``WITH_GETTEXT`` has been renamed to ``WITH_MESSAGE_LOCALIZATION``, to reflect that it toggles localization independently of the backend used in the implementation.
|
||||
- New ``cargo xtask`` commands can replace some CMake workflows.
|
||||
|
||||
Regression fixes:
|
||||
-----------------
|
||||
- (from 4.1.0) Crash when autosuggesting Unicode characters with nontrivial lowercase mapping (:issue:`12326`, 78f4541116e).
|
||||
- (from 4.3.0) Glitch on ``read --prompt-str ""`` (:issue:`12296`).
|
||||
|
||||
fish 4.3.3 (released January 07, 2026)
|
||||
======================================
|
||||
|
||||
This release fixes the following problems identified in fish 4.3.0:
|
||||
|
||||
- Selecting a completion could insert only part of the token (:issue:`12249`).
|
||||
- Glitch with soft-wrapped autosuggestions and :doc:`fish_right_prompt <cmds/fish_right_prompt>` (:issue:`12255`).
|
||||
- Spurious echo in tmux when typing a command really fast (:issue:`12261`).
|
||||
- ``tomorrow`` theme always using the light variant (:issue:`12266`).
|
||||
- ``fish_config theme choose`` sometimes not shadowing themes set by e.g. webconfig (:issue:`12278`).
|
||||
- The sample prompts and themes are correctly installed (:issue:`12241`).
|
||||
- Last line of command output could be hidden when missing newline (:issue:`12246`).
|
||||
|
||||
Other improvements include:
|
||||
|
||||
- The ``abbr``, ``bind``, ``complete``, ``functions``, ``history`` and ``type`` commands now support a ``--color`` option to control syntax highlighting in their output. Valid values are ``auto`` (default), ``always``, or ``never``.
|
||||
- Existing file paths in redirection targets such as ``> file.txt`` are now highlighted using :envvar:`fish_color_valid_path`, indicating that ``file.txt`` will be clobbered (:issue:`12260`).
|
||||
|
||||
fish 4.3.2 (released December 30, 2025)
|
||||
=======================================
|
||||
|
||||
This release fixes the following problems identified in 4.3.0:
|
||||
|
||||
- Pre-built macOS packages failed to start due to a ``Malformed Mach-O file`` error (:issue:`12224`).
|
||||
- ``extra_functionsdir`` (usually ``vendor_functions.d``) and friends were not used (:issue:`12226`).
|
||||
- Sample config file ``~/.config/fish/config.fish/`` and config directories ``~/.config/fish/conf.d/``, ``~/.config/fish/completions/`` and ``~/.config/fish/functions/`` were recreated on every startup instead of only the first time fish runs on a system (:issue:`12230`).
|
||||
- Spurious echo of ``^[[I`` in some scenarios (:issue:`12232`).
|
||||
- Infinite prompt redraw loop on some prompts (:issue:`12233`).
|
||||
- The removal of pre-built HTML docs from tarballs revealed that cross compilation is broken because we use ``${CMAKE_BINARY_DIR}/fish_indent`` for building HTML docs.
|
||||
As a workaround, the new CMake build option ``FISH_INDENT_FOR_BUILDING_DOCS`` can be set to the path of a runnable ``fish_indent`` binary.
|
||||
|
||||
fish 4.3.1 (released December 28, 2025)
|
||||
=======================================
|
||||
|
||||
@@ -10,7 +184,7 @@ fish 4.3.0 (released December 28, 2025)
|
||||
|
||||
Deprecations and removed features
|
||||
---------------------------------
|
||||
- fish no longer sets :ref:`universal variables <variables-universal>` by default, making the configuration easier to understand.
|
||||
- fish no longer sets user-facing :ref:`universal variables <variables-universal>` by default, making the configuration easier to understand.
|
||||
Specifically, the ``fish_color_*``, ``fish_pager_color_*`` and ``fish_key_bindings`` variables are now set in the global scope by default.
|
||||
After upgrading to 4.3.0, fish will (once and never again) migrate these universals to globals set at startup in the
|
||||
``~/.config/fish/conf.d/fish_frozen_theme.fish`` and
|
||||
@@ -37,7 +211,7 @@ Interactive improvements
|
||||
- Completion accuracy was improved for file paths containing ``=`` or ``:`` (:issue:`5363`).
|
||||
- Prefix-matching completions are now shown even if they don't match the case typed by the user (:issue:`7944`).
|
||||
- On Cygwin/MSYS, command name completion will favor the non-exe name (``foo``) unless the user started typing the extension.
|
||||
- When using the exe name (``foo.exe``), fish will use to the description and completions for ``foo`` if there are none for ``foo.exe``.
|
||||
- When using the exe name (``foo.exe``), fish will use the description and completions for ``foo`` if there are none for ``foo.exe``.
|
||||
- Autosuggestions now also show soft-wrapped portions (:issue:`12045`).
|
||||
|
||||
New or improved bindings
|
||||
@@ -52,8 +226,6 @@ Improved terminal support
|
||||
- The working directory is now reported on every fresh prompt (via OSC 7), fixing scenarios where a child process (like ``ssh``) left behind a stale working directory (:issue:`12191`).
|
||||
- OSC 133 prompt markers now also mark the prompt end, which improves shell integration with terminals like iTerm2 (:issue:`11837`).
|
||||
- Operating-system-specific key bindings are now decided based on the :ref:`terminal's host OS <status-terminal-os>`.
|
||||
- Focus reporting is enabled unconditionally, not just inside tmux.
|
||||
To use it, define functions that handle the ``fish_focus_in`` or ``fish_focus_out`` :ref:`events <event>`.
|
||||
- New :ref:`feature flag <featureflags>` ``omit-term-workarounds`` can be turned on to prevent fish from trying to work around some incompatible terminals.
|
||||
|
||||
For distributors and developers
|
||||
@@ -183,8 +355,6 @@ This release fixes the following regressions identified in 4.1.0:
|
||||
fish 4.1.0 (released September 27, 2025)
|
||||
========================================
|
||||
|
||||
.. ignore for 4.1: 10929 10940 10948 10955 10965 10975 10989 10990 10998 11028 11052 11055 11069 11071 11079 11092 11098 11104 11106 11110 11140 11146 11148 11150 11214 11218 11259 11288 11299 11328 11350 11373 11395 11417 11419
|
||||
|
||||
Notable improvements and fixes
|
||||
------------------------------
|
||||
- Compound commands (``begin; echo 1; echo 2; end``) can now be written using braces (``{ echo1; echo 2 }``), like in other shells.
|
||||
|
||||
@@ -24,12 +24,12 @@ include(cmake/Rust.cmake)
|
||||
# Work around issue where archive-built libs go in the wrong place.
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
|
||||
|
||||
# Set up the machinery around FISH-BUILD-VERSION-FILE
|
||||
# This defines the FBVF variable.
|
||||
include(Version)
|
||||
find_program(SPHINX_EXECUTABLE NAMES sphinx-build
|
||||
HINTS
|
||||
$ENV{SPHINX_DIR}
|
||||
PATH_SUFFIXES bin
|
||||
DOC "Sphinx documentation generator")
|
||||
|
||||
# Set up the docs.
|
||||
include(cmake/Docs.cmake)
|
||||
|
||||
# Tell Cargo where our build directory is so it can find Cargo.toml.
|
||||
set(VARS_FOR_CARGO
|
||||
@@ -53,36 +53,36 @@ add_definitions(-DCMAKE_SOURCE_DIR="${REAL_CMAKE_SOURCE_DIR}")
|
||||
|
||||
set(build_types Release RelWithDebInfo Debug "")
|
||||
if(NOT "${CMAKE_BUILD_TYPE}" IN_LIST build_types)
|
||||
message(WARNING "Unsupported build type ${CMAKE_BUILD_TYPE}. If this doesn't build, try one of Release, RelWithDebInfo or Debug")
|
||||
message(WARNING "Unsupported build type ${CMAKE_BUILD_TYPE}. If this doesn't build, try one of Release, RelWithDebInfo or Debug")
|
||||
endif()
|
||||
|
||||
add_custom_target(
|
||||
fish ALL
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}" -E
|
||||
env ${VARS_FOR_CARGO}
|
||||
${Rust_CARGO}
|
||||
build --bin fish
|
||||
$<$<CONFIG:Release>:--release>
|
||||
$<$<CONFIG:RelWithDebInfo>:--profile=release-with-debug>
|
||||
--target ${Rust_CARGO_TARGET}
|
||||
--no-default-features
|
||||
--features=${FISH_CARGO_FEATURES}
|
||||
${CARGO_FLAGS}
|
||||
&&
|
||||
"${CMAKE_COMMAND}" -E
|
||||
copy "${rust_target_dir}/${rust_profile}/fish" "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
USES_TERMINAL
|
||||
fish ALL
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}" -E
|
||||
env ${VARS_FOR_CARGO}
|
||||
${Rust_CARGO}
|
||||
build --bin fish
|
||||
$<$<CONFIG:Release>:--release>
|
||||
$<$<CONFIG:RelWithDebInfo>:--profile=release-with-debug>
|
||||
--target ${Rust_CARGO_TARGET}
|
||||
--no-default-features
|
||||
--features=${FISH_CARGO_FEATURES}
|
||||
${CARGO_FLAGS}
|
||||
&&
|
||||
"${CMAKE_COMMAND}" -E
|
||||
copy "${rust_target_dir}/${rust_profile}/fish" "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
USES_TERMINAL
|
||||
)
|
||||
|
||||
function(CREATE_LINK target)
|
||||
add_custom_target(
|
||||
${target} ALL
|
||||
DEPENDS fish
|
||||
COMMAND ln -f fish ${target}
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
add_custom_target(
|
||||
${target} ALL
|
||||
DEPENDS fish
|
||||
COMMAND ln -f fish ${target}
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
endfunction(CREATE_LINK)
|
||||
|
||||
# Define fish_indent.
|
||||
@@ -91,6 +91,9 @@ create_link(fish_indent)
|
||||
# Define fish_key_reader.
|
||||
create_link(fish_key_reader)
|
||||
|
||||
# Set up the docs.
|
||||
include(cmake/Docs.cmake)
|
||||
|
||||
# Set up tests.
|
||||
include(cmake/Tests.cmake)
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ Guidelines
|
||||
In short:
|
||||
|
||||
- Be conservative in what you need (keep to the agreed minimum supported Rust version, limit new dependencies)
|
||||
- Use automated tools to help you (``build_tools/check.sh``)
|
||||
- Use automated tools to help you (``cargo xtask check``)
|
||||
|
||||
Commit History
|
||||
==============
|
||||
@@ -105,21 +105,24 @@ Contributing documentation
|
||||
==========================
|
||||
|
||||
The documentation is stored in ``doc_src/``, and written in ReStructured Text and built with Sphinx.
|
||||
The builtins and various functions shipped with fish are documented in ``doc_src/cmds/``.
|
||||
|
||||
To build it locally, run either::
|
||||
To build an HTML version of the docs locally, run::
|
||||
|
||||
sphinx-build -j auto -b html doc_src/ /tmp/fish-doc/
|
||||
cargo xtask html-docs
|
||||
|
||||
which will output HTML docs to /tmp/fish-doc.
|
||||
You can open it in a browser and see that it looks okay.
|
||||
|
||||
Alternatively, you can use::
|
||||
will output to ``target/fish-docs/html`` or, if you use CMake::
|
||||
|
||||
cmake --build build -t sphinx-docs
|
||||
|
||||
which outputs to build/user_doc/html/.
|
||||
will output to ``build/cargo/fish-docs/html/``. You can also run ``sphinx-build`` directly, which allows choosing the output directory::
|
||||
|
||||
sphinx-build -j auto -b html doc_src/ /tmp/fish-doc/
|
||||
|
||||
will output HTML docs to ``/tmp/fish-doc``.
|
||||
|
||||
After building them, you can open the HTML docs in a browser and see that it looks okay.
|
||||
|
||||
The builtins and various functions shipped with fish are documented in doc_src/cmds/.
|
||||
|
||||
Code Style
|
||||
==========
|
||||
@@ -130,12 +133,12 @@ For formatting, we use:
|
||||
- ``fish_indent`` (shipped with fish) for fish script
|
||||
- ``ruff format`` for Python
|
||||
|
||||
To reformat files, there is a script
|
||||
To reformat files, there is an xtask
|
||||
|
||||
::
|
||||
|
||||
build_tools/style.fish --all
|
||||
build_tools/style.fish somefile.rs some.fish
|
||||
cargo xtask format --all
|
||||
cargo xtask format somefile.rs some.fish
|
||||
|
||||
Fish Script Style Guide
|
||||
-----------------------
|
||||
@@ -245,7 +248,7 @@ In this example we're in the root of the workspace and have run ``cargo build``
|
||||
|
||||
To run all tests and linters, use::
|
||||
|
||||
build_tools/check.sh
|
||||
cargo xtask check
|
||||
|
||||
Contributing Translations
|
||||
=========================
|
||||
@@ -268,13 +271,14 @@ Adding translations for a new language
|
||||
--------------------------------------
|
||||
|
||||
Creating new translations requires the Gettext tools.
|
||||
More specifically, you will need ``msguniq`` and ``msgmerge`` for creating translations for a new
|
||||
language.
|
||||
To create a new translation, run::
|
||||
More specifically, you will need ``msguniq``, ``msgmerge``, and ``msgattrib``
|
||||
for creating translations for a new language.
|
||||
To create a PO file for a new language ``ll_CC``, run::
|
||||
|
||||
build_tools/update_translations.fish localization/po/ll_CC.po
|
||||
cargo xtask gettext new ll_CC
|
||||
|
||||
This will create a new PO file containing all messages available for translation.
|
||||
This will create a new PO file in ``localization/po/``
|
||||
containing all messages available for translation.
|
||||
If the file already exists, it will be updated.
|
||||
|
||||
After modifying a PO file, you can recompile fish, and it will integrate the modifications you made.
|
||||
@@ -344,10 +348,12 @@ Modifications to strings in source files
|
||||
----------------------------------------
|
||||
|
||||
If a string changes in the sources, the old translations will no longer work.
|
||||
They will be preserved in the PO files, but commented-out (starting with ``#~``).
|
||||
If you add/remove/change a translatable strings in a source file,
|
||||
run ``build_tools/update_translations.fish`` to propagate this to all translation files (``localization/po/*.po``).
|
||||
run ``cargo xtask gettext update`` to propagate this to all translation files (``localization/po/*.po``).
|
||||
This is only relevant for developers modifying the source files of fish or fish scripts.
|
||||
Note translations for messages which are no longer present in the sources will be deleted from the PO files.
|
||||
If the source string changed in a way which should not affect translations,
|
||||
consider updating the ``msgid`` in the PO files such that translations are preserved.
|
||||
|
||||
Setting Code Up For Translations
|
||||
--------------------------------
|
||||
|
||||
580
Cargo.lock
generated
580
Cargo.lock
generated
@@ -4,9 +4,9 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -17,6 +17,68 @@ version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "assert_matches"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
@@ -40,9 +102,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.12.0"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
||||
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
@@ -50,9 +112,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.41"
|
||||
version = "1.2.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
|
||||
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -72,6 +134,52 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
@@ -82,10 +190,35 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
@@ -122,6 +255,12 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -146,29 +285,36 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.4"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "fish"
|
||||
version = "4.3.1"
|
||||
version = "4.7.1"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"errno",
|
||||
"fish-build-helper",
|
||||
"fish-build-man-pages",
|
||||
"fish-color",
|
||||
"fish-common",
|
||||
"fish-fallback",
|
||||
"fish-feature-flags",
|
||||
"fish-gettext",
|
||||
"fish-gettext-extraction",
|
||||
"fish-gettext-mo-file-parser",
|
||||
"fish-printf",
|
||||
"fish-tempfile",
|
||||
"fish-wchar",
|
||||
"fish-util",
|
||||
"fish-wcstringutil",
|
||||
"fish-wgetopt",
|
||||
"fish-widecharwidth",
|
||||
"fish-widestring",
|
||||
"itertools",
|
||||
"libc",
|
||||
"lru",
|
||||
"macro_rules_attribute",
|
||||
@@ -176,15 +322,15 @@ dependencies = [
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"pcre2",
|
||||
"phf_codegen 0.12.1",
|
||||
"phf_codegen",
|
||||
"portable-atomic",
|
||||
"rand 0.9.2",
|
||||
"rand",
|
||||
"rsconf",
|
||||
"rust-embed",
|
||||
"rustc_version",
|
||||
"serial_test",
|
||||
"terminfo",
|
||||
"strum_macros",
|
||||
"unix_path",
|
||||
"widestring",
|
||||
"xterm-color",
|
||||
]
|
||||
|
||||
@@ -203,13 +349,25 @@ dependencies = [
|
||||
"rsconf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fish-color"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"fish-common",
|
||||
"fish-widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fish-common"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fish-build-helper",
|
||||
"fish-feature-flags",
|
||||
"fish-widestring",
|
||||
"libc",
|
||||
"nix",
|
||||
"once_cell",
|
||||
"rsconf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -218,21 +376,26 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"fish-build-helper",
|
||||
"fish-common",
|
||||
"fish-wchar",
|
||||
"fish-widecharwidth",
|
||||
"fish-widestring",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"rsconf",
|
||||
"widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fish-feature-flags"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"fish-widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fish-gettext"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"fish-gettext-maps",
|
||||
"once_cell",
|
||||
"phf 0.12.1",
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -250,8 +413,8 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"fish-build-helper",
|
||||
"fish-gettext-mo-file-parser",
|
||||
"phf 0.12.1",
|
||||
"phf_codegen 0.12.1",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"rsconf",
|
||||
]
|
||||
|
||||
@@ -263,6 +426,7 @@ version = "0.0.0"
|
||||
name = "fish-printf"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"libc",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
@@ -274,15 +438,37 @@ name = "fish-tempfile"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"rand 0.9.2",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fish-wchar"
|
||||
name = "fish-util"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"fish-common",
|
||||
"widestring",
|
||||
"errno",
|
||||
"fish-widestring",
|
||||
"libc",
|
||||
"nix",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fish-wcstringutil"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"fish-build-helper",
|
||||
"fish-fallback",
|
||||
"fish-widestring",
|
||||
"rsconf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fish-wgetopt"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"fish-wcstringutil",
|
||||
"fish-widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -290,22 +476,25 @@ name = "fish-widecharwidth"
|
||||
version = "0.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
name = "fish-widestring"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"unicode-width",
|
||||
"widestring",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.9"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
@@ -313,9 +502,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
@@ -336,9 +525,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.16"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
|
||||
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
@@ -349,15 +538,52 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"globset",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"same-file",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.34"
|
||||
@@ -370,15 +596,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.177"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.10"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
||||
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
@@ -395,15 +621,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.13.0"
|
||||
version = "0.16.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465"
|
||||
checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
@@ -430,17 +656,11 @@ version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
checksum = "225e7cfe711e0ba79a68baeddb2982723e4235247aefce1482f2f16c27865b66"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
@@ -448,16 +668,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -473,6 +683,12 @@ version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
@@ -530,76 +746,38 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.3"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
|
||||
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7"
|
||||
dependencies = [
|
||||
"phf_shared 0.12.1",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.3"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
|
||||
checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
|
||||
dependencies = [
|
||||
"phf_generator 0.11.3",
|
||||
"phf_shared 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efbdcb6f01d193b17f0b9c3360fa7e0e620991b193ff08702f78b3ce365d7e61"
|
||||
dependencies = [
|
||||
"phf_generator 0.12.1",
|
||||
"phf_shared 0.12.1",
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.3"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.3",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b"
|
||||
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"phf_shared 0.12.1",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.3"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981"
|
||||
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
@@ -612,9 +790,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
@@ -627,18 +805,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.41"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -649,15 +827,6 @@ version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
@@ -665,7 +834,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core 0.9.3",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -675,20 +844,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.3",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
@@ -708,7 +871,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"getrandom 0.2.17",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
@@ -732,18 +895,18 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "rsconf"
|
||||
version = "0.2.2"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd2af859f1af0401e7fc7577739c87b0d239d8a5da400d717183bca92336bcdc"
|
||||
checksum = "06cbd984e96cc891aa018958ac3d09986c0ea7635eedfff670b99a90970f159f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.9.0"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca"
|
||||
checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
@@ -752,9 +915,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "8.9.0"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2"
|
||||
checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -766,15 +929,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "8.9.0"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475"
|
||||
checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
|
||||
dependencies = [
|
||||
"globset",
|
||||
"sha2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
@@ -805,6 +977,12 @@ version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
@@ -836,9 +1014,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serial_test"
|
||||
version = "3.2.0"
|
||||
version = "3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9"
|
||||
checksum = "0d0b343e184fc3b7bb44dff0705fffcf4b3756ba6aff420dddd8b24ca145e555"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
@@ -848,9 +1026,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serial_test_derive"
|
||||
version = "3.2.0"
|
||||
version = "3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef"
|
||||
checksum = "6f50427f258fb77356e4cd4aa0e87e2bd2c66dbcee41dc405282cae2bfc26c83"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -870,9 +1048,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shellexpand"
|
||||
version = "3.1.1"
|
||||
version = "3.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb"
|
||||
checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
]
|
||||
@@ -885,9 +1063,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
@@ -896,42 +1074,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.107"
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminfo"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"nom",
|
||||
"phf 0.11.3",
|
||||
"phf_codegen 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.17"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.17"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -946,9 +1130,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.20"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
@@ -977,6 +1161,12 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ace0b4755d0a2959962769239d56267f8a024fef2d9b32666b3dcd0946b0906"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
@@ -1044,26 +1234,42 @@ version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anyhow",
|
||||
"clap",
|
||||
"fish-build-helper",
|
||||
"fish-common",
|
||||
"fish-tempfile",
|
||||
"fish-widestring",
|
||||
"ignore",
|
||||
"pcre2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xterm-color"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4de5f056fb9dc8b7908754867544e26145767187aaac5a98495e88ad7cb8a80f"
|
||||
checksum = "7008a9d8ba97a7e47d9b2df63fcdb8dade303010c5a7cd5bf2469d4da6eba673"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.27"
|
||||
version = "0.8.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||
checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.27"
|
||||
version = "0.8.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||
checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
131
Cargo.toml
131
Cargo.toml
@@ -11,62 +11,79 @@ repository = "https://github.com/fish-shell/fish-shell"
|
||||
license = "GPL-2.0-only AND LGPL-2.0-or-later AND MIT AND PSF-2.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
anstyle = "1.0.13"
|
||||
anyhow = "1.0.102"
|
||||
assert_matches = "1.5.0"
|
||||
bitflags = "2.5.0"
|
||||
cc = "1.0.94"
|
||||
cfg-if = "1.0.3"
|
||||
clap = { version = "4.5.54", features = ["derive"] }
|
||||
errno = "0.3.0"
|
||||
fish-build-helper = { path = "crates/build-helper" }
|
||||
fish-build-man-pages = { path = "crates/build-man-pages" }
|
||||
fish-color = { path = "crates/color" }
|
||||
fish-common = { path = "crates/common" }
|
||||
fish-fallback = { path = "crates/fallback" }
|
||||
fish-feature-flags = { path = "crates/feature-flags" }
|
||||
fish-gettext = { path = "crates/gettext" }
|
||||
fish-gettext-extraction = { path = "crates/gettext-extraction" }
|
||||
fish-gettext-maps = { path = "crates/gettext-maps" }
|
||||
fish-gettext-mo-file-parser = { path = "crates/gettext-mo-file-parser" }
|
||||
fish-printf = { path = "crates/printf", features = ["widestring"] }
|
||||
fish-tempfile = { path = "crates/tempfile" }
|
||||
fish-wchar = { path = "crates/wchar" }
|
||||
fish-util = { path = "crates/util" }
|
||||
fish-wcstringutil = { path = "crates/wcstringutil" }
|
||||
fish-widecharwidth = { path = "crates/widecharwidth" }
|
||||
fish-widestring = { path = "crates/widestring" }
|
||||
fish-wgetopt = { path = "crates/wgetopt" }
|
||||
ignore = "0.4.25"
|
||||
itertools = "0.14.0"
|
||||
libc = "0.2.177"
|
||||
# lru pulls in hashbrown by default, which uses a faster (though less DoS resistant) hashing algo.
|
||||
# disabling default features uses the stdlib instead, but it doubles the time to rewrite the history
|
||||
# files as of 22 April 2024.
|
||||
lru = "0.13.0"
|
||||
nix = { version = "0.30.1", default-features = false, features = [
|
||||
"event",
|
||||
"inotify",
|
||||
"resource",
|
||||
"fs",
|
||||
"term",
|
||||
lru = "0.16.2"
|
||||
nix = { version = "0.31.1", default-features = false, features = [
|
||||
"event",
|
||||
"fs",
|
||||
"inotify",
|
||||
"hostname",
|
||||
"resource",
|
||||
"process",
|
||||
"signal",
|
||||
"term",
|
||||
"user",
|
||||
] }
|
||||
num-traits = "0.2.19"
|
||||
once_cell = "1.19.0"
|
||||
pcre2 = { git = "https://github.com/fish-shell/rust-pcre2", tag = "0.2.9-utf32", default-features = false, features = [
|
||||
"utf32",
|
||||
"utf32",
|
||||
] }
|
||||
phf = { version = "0.12", default-features = false }
|
||||
phf_codegen = { version = "0.12" }
|
||||
phf = { version = "0.13", default-features = false }
|
||||
phf_codegen = "0.13"
|
||||
portable-atomic = { version = "1", default-features = false, features = [
|
||||
"fallback",
|
||||
"fallback",
|
||||
] }
|
||||
proc-macro2 = "1.0"
|
||||
rand = { version = "0.9.2", default-features = false, features = [
|
||||
"small_rng",
|
||||
"thread_rng",
|
||||
"small_rng",
|
||||
"thread_rng",
|
||||
] }
|
||||
rsconf = "0.2.2"
|
||||
rust-embed = { version = "8.9.0", features = [
|
||||
"deterministic-timestamps",
|
||||
"include-exclude",
|
||||
"interpolate-folder-path",
|
||||
regex = "1.12.3"
|
||||
rsconf = "0.3.0"
|
||||
rust-embed = { version = "8.11.0", features = [
|
||||
"deterministic-timestamps",
|
||||
"include-exclude",
|
||||
"interpolate-folder-path",
|
||||
] }
|
||||
rustc_version = "0.4.1"
|
||||
serial_test = { version = "3", default-features = false }
|
||||
# We need 0.9.0 specifically for some crash fixes.
|
||||
terminfo = "0.9.0"
|
||||
strum_macros = "0.28.0"
|
||||
widestring = "1.2.0"
|
||||
unicode-segmentation = "1.12.0"
|
||||
unicode-width = "0.2.0"
|
||||
unix_path = "1.0.1"
|
||||
walkdir = "2.5.0"
|
||||
xterm-color = "1.0.1"
|
||||
|
||||
[profile.release]
|
||||
@@ -79,7 +96,7 @@ debug = true
|
||||
|
||||
[package]
|
||||
name = "fish"
|
||||
version = "4.3.1"
|
||||
version = "4.7.1"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
default-run = "fish"
|
||||
@@ -88,19 +105,26 @@ homepage = "https://fishshell.com"
|
||||
readme = "README.rst"
|
||||
|
||||
[dependencies]
|
||||
assert_matches.workspace = true
|
||||
bitflags.workspace = true
|
||||
cfg-if.workspace = true
|
||||
errno.workspace = true
|
||||
fish-build-helper.workspace = true
|
||||
fish-build-man-pages = { workspace = true, optional = true }
|
||||
fish-color.workspace = true
|
||||
fish-common.workspace = true
|
||||
fish-fallback.workspace = true
|
||||
fish-feature-flags.workspace = true
|
||||
fish-gettext = { workspace = true, optional = true }
|
||||
fish-gettext-extraction = { workspace = true, optional = true }
|
||||
fish-printf.workspace = true
|
||||
fish-tempfile.workspace = true
|
||||
fish-wchar.workspace = true
|
||||
fish-util.workspace = true
|
||||
fish-wcstringutil.workspace = true
|
||||
fish-wgetopt.workspace = true
|
||||
fish-widecharwidth.workspace = true
|
||||
fish-widestring.workspace = true
|
||||
itertools.workspace = true
|
||||
libc.workspace = true
|
||||
lru.workspace = true
|
||||
macro_rules_attribute = "0.2.2"
|
||||
@@ -109,25 +133,24 @@ num-traits.workspace = true
|
||||
once_cell.workspace = true
|
||||
pcre2.workspace = true
|
||||
rand.workspace = true
|
||||
terminfo.workspace = true
|
||||
strum_macros.workspace = true
|
||||
xterm-color.workspace = true
|
||||
widestring.workspace = true
|
||||
|
||||
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
|
||||
portable-atomic.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
rust-embed = { workspace = true, features = [
|
||||
"deterministic-timestamps",
|
||||
"debug-embed",
|
||||
"include-exclude",
|
||||
"interpolate-folder-path",
|
||||
"deterministic-timestamps",
|
||||
"debug-embed",
|
||||
"include-exclude",
|
||||
"interpolate-folder-path",
|
||||
] }
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
rust-embed = { workspace = true, features = [
|
||||
"deterministic-timestamps",
|
||||
"include-exclude",
|
||||
"interpolate-folder-path",
|
||||
"deterministic-timestamps",
|
||||
"include-exclude",
|
||||
"interpolate-folder-path",
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -139,6 +162,7 @@ fish-build-helper.workspace = true
|
||||
fish-gettext-mo-file-parser.workspace = true
|
||||
phf_codegen = { workspace = true, optional = true }
|
||||
rsconf.workspace = true
|
||||
rustc_version.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
unix_path.workspace = true
|
||||
@@ -168,33 +192,46 @@ embed-manpages = ["dep:fish-build-man-pages"]
|
||||
localize-messages = ["dep:fish-gettext"]
|
||||
# This feature is used to enable extracting messages from the source code for localization.
|
||||
# It only needs to be enabled if updating these messages (and the corresponding PO files) is
|
||||
# desired. This happens when running tests via `build_tools/check.sh` and when calling
|
||||
# `build_tools/update_translations.fish`, so there should not be a need to enable it manually.
|
||||
# desired. This happens for the `gettext` xtask, which is also invoked via `cargo xtask check`.
|
||||
# There should not be a need to enable this feature manually.
|
||||
gettext-extract = ["dep:fish-gettext-extraction"]
|
||||
|
||||
# The following features are auto-detected by the build-script and should not be enabled manually.
|
||||
asan = []
|
||||
tsan = []
|
||||
|
||||
[workspace.lints]
|
||||
rust.non_camel_case_types = "allow"
|
||||
rust.non_upper_case_globals = "allow"
|
||||
rust.unknown_lints = "allow"
|
||||
rust.unknown_lints = { level = "allow", priority = -1 }
|
||||
rust.unstable_name_collisions = "allow"
|
||||
rustdoc.private_intra_doc_links = "allow"
|
||||
clippy.len_without_is_empty = "allow" # we're not a library crate
|
||||
clippy.let_and_return = "allow"
|
||||
clippy.manual_range_contains = "allow"
|
||||
clippy.map_unwrap_or = "warn"
|
||||
clippy.needless_lifetimes = "allow"
|
||||
clippy.new_without_default = "allow"
|
||||
clippy.option_map_unit_fn = "allow"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
assigning_clones = "warn"
|
||||
cloned_instead_of_copied = "warn"
|
||||
explicit_into_iter_loop = "warn"
|
||||
format_push_string = "warn"
|
||||
implicit_clone = "warn"
|
||||
len_without_is_empty = "allow" # we're not a library crate
|
||||
let_and_return = "allow"
|
||||
manual_assert = "warn"
|
||||
manual_range_contains = "allow"
|
||||
map_unwrap_or = "warn"
|
||||
mut_mut = "warn"
|
||||
needless_lifetimes = "allow"
|
||||
new_without_default = "allow"
|
||||
option_map_unit_fn = "allow"
|
||||
ptr_offset_by_literal = "warn"
|
||||
ref_option = "warn"
|
||||
semicolon_if_nothing_returned = "warn"
|
||||
stable_sort_primitive = "warn"
|
||||
str_to_string = "warn"
|
||||
unnecessary_semicolon = "warn"
|
||||
unused_trait_names = "warn"
|
||||
|
||||
# We do not want to use the e?print(ln)?! macros.
|
||||
# These lints flag their use.
|
||||
# In the future, they might change to flag other methods of printing.
|
||||
clippy.print_stdout = "deny"
|
||||
clippy.print_stderr = "deny"
|
||||
print_stdout = "deny"
|
||||
print_stderr = "deny"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
18
Dockerfile
18
Dockerfile
@@ -1,18 +0,0 @@
|
||||
FROM centos:latest
|
||||
|
||||
# Build dependency
|
||||
RUN yum update -y &&\
|
||||
yum install -y epel-release &&\
|
||||
yum install -y clang cmake3 gcc-c++ make &&\
|
||||
yum clean all
|
||||
|
||||
# Test dependency
|
||||
RUN yum install -y expect vim-common
|
||||
|
||||
ADD . /src
|
||||
WORKDIR /src
|
||||
|
||||
# Build fish
|
||||
RUN cmake3 . &&\
|
||||
make &&\
|
||||
make install
|
||||
@@ -7,7 +7,7 @@
|
||||
CMAKE ?= cmake
|
||||
|
||||
GENERATOR ?= $(shell (which ninja > /dev/null 2> /dev/null && echo Ninja) || \
|
||||
echo 'Unix Makefiles')
|
||||
echo 'Unix Makefiles')
|
||||
prefix ?= /usr/local
|
||||
PREFIX ?= $(prefix)
|
||||
|
||||
@@ -34,7 +34,7 @@ all: .begin build/fish
|
||||
.PHONY: .begin
|
||||
.begin:
|
||||
@which $(CMAKE) > /dev/null 2> /dev/null || \
|
||||
(echo 'Please install CMake and then re-run the `make` command!' 1>&2 && false)
|
||||
(echo 'Please install CMake and then re-run the `make` command!' 1>&2 && false)
|
||||
|
||||
.PHONY: build/fish
|
||||
build/fish: build/$(BUILDFILE)
|
||||
|
||||
@@ -117,7 +117,7 @@ Dependencies
|
||||
|
||||
Compiling fish requires:
|
||||
|
||||
- Rust (version 1.85 or later)
|
||||
- Rust (version 1.85 or later), including cargo
|
||||
- CMake (version 3.15 or later)
|
||||
- a C compiler (for system feature detection and the test helper binary)
|
||||
- PCRE2 (headers and libraries) - optional, this will be downloaded if missing
|
||||
@@ -158,8 +158,13 @@ In addition to the normal CMake build options (like ``CMAKE_INSTALL_PREFIX``), f
|
||||
- Rust_CARGO=path - the path to cargo. If not set, cmake will check $PATH and ~/.cargo/bin
|
||||
- Rust_CARGO_TARGET=target - the target to pass to cargo. Set this for cross-compilation.
|
||||
- WITH_DOCS=ON|OFF - whether to build the documentation. By default, this is ON when Sphinx is installed.
|
||||
- FISH_INDENT_FOR_BUILDING_DOCS - useful for cross-compilation.
|
||||
Set this to the path to the ``fish_indent`` executable to use for building HTML docs.
|
||||
By default, ``${CMAKE_BINARY_DIR}/fish_indent`` will be used.
|
||||
If that's not runnable on the compile host,
|
||||
you can build a native one with ``cargo build --bin fish_indent`` and set this to ``$PWD/target/debug/fish_indent``.
|
||||
- FISH_USE_SYSTEM_PCRE2=ON|OFF - whether to use an installed pcre2. This is normally autodetected.
|
||||
- WITH_GETTEXT=ON|OFF - whether to include translations.
|
||||
- WITH_MESSAGE_LOCALIZATION=ON|OFF - whether to include translations.
|
||||
- extra_functionsdir, extra_completionsdir and extra_confdir - to compile in an additional directory to be searched for functions, completions and configuration snippets
|
||||
|
||||
Building fish with Cargo
|
||||
|
||||
111
build.rs
111
build.rs
@@ -3,14 +3,13 @@
|
||||
workspace_root,
|
||||
};
|
||||
use rsconf::Target;
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn canonicalize<P: AsRef<Path>>(path: P) -> PathBuf {
|
||||
std::fs::canonicalize(path).unwrap()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let is_nightly =
|
||||
rustc_version::version_meta().unwrap().channel == rustc_version::Channel::Nightly;
|
||||
rsconf::declare_cfg("nightly", is_nightly);
|
||||
|
||||
setup_paths();
|
||||
|
||||
// Add our default to enable tools that don't go through CMake, like "cargo test" and the
|
||||
@@ -20,14 +19,14 @@ fn main() {
|
||||
"FISH_RESOLVED_BUILD_DIR",
|
||||
// If set by CMake, this might include symlinks. Since we want to compare this to the
|
||||
// dir fish is executed in we need to canonicalize it.
|
||||
canonicalize(fish_build_dir()).to_str().unwrap(),
|
||||
fish_build_dir().canonicalize().unwrap().to_str().unwrap(),
|
||||
);
|
||||
|
||||
// We need to canonicalize (i.e. realpath) the manifest dir because we want to be able to
|
||||
// compare it directly as a string at runtime.
|
||||
rsconf::set_env_value(
|
||||
"CARGO_MANIFEST_DIR",
|
||||
canonicalize(workspace_root()).to_str().unwrap(),
|
||||
workspace_root().canonicalize().unwrap().to_str().unwrap(),
|
||||
);
|
||||
|
||||
// Some build info
|
||||
@@ -35,13 +34,9 @@ fn main() {
|
||||
rsconf::set_env_value("BUILD_HOST_TRIPLE", &env_var("HOST").unwrap());
|
||||
rsconf::set_env_value("BUILD_PROFILE", &env_var("PROFILE").unwrap());
|
||||
|
||||
let version = &get_version(&env::current_dir().unwrap());
|
||||
// Per https://doc.rust-lang.org/cargo/reference/build-scripts.html#inputs-to-the-build-script,
|
||||
// the source directory is the current working directory of the build script
|
||||
rsconf::set_env_value("FISH_BUILD_VERSION", version);
|
||||
|
||||
// safety: single-threaded code.
|
||||
unsafe { std::env::set_var("FISH_BUILD_VERSION", version) };
|
||||
rsconf::set_env_value("FISH_BUILD_VERSION", &get_version());
|
||||
|
||||
fish_build_helper::rebuild_if_embedded_path_changed("share");
|
||||
|
||||
@@ -107,7 +102,7 @@ fn detect_cfgs(target: &mut Target) {
|
||||
target.r#if("WEXITSTATUS(0x007f) == 0x7f", &["sys/wait.h"])
|
||||
}),
|
||||
] {
|
||||
rsconf::declare_cfg(name, handler(target))
|
||||
rsconf::declare_cfg(name, handler(target));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,16 +163,14 @@ fn join_if_relative(parent_if_relative: &Path, path: String) -> PathBuf {
|
||||
}
|
||||
|
||||
let prefix = overridable_path("PREFIX", |env_prefix| {
|
||||
Some(PathBuf::from(
|
||||
env_prefix.unwrap_or("/usr/local".to_string()),
|
||||
))
|
||||
Some(PathBuf::from(env_prefix.unwrap_or("/usr/local".to_owned())))
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
overridable_path("SYSCONFDIR", |env_sysconfdir| {
|
||||
Some(join_if_relative(
|
||||
&prefix,
|
||||
env_sysconfdir.unwrap_or("/etc/".to_string()),
|
||||
env_sysconfdir.unwrap_or("/etc/".to_owned()),
|
||||
))
|
||||
});
|
||||
|
||||
@@ -198,79 +191,15 @@ fn join_if_relative(parent_if_relative: &Path, path: String) -> PathBuf {
|
||||
});
|
||||
}
|
||||
|
||||
fn get_version(src_dir: &Path) -> String {
|
||||
use std::fs::read_to_string;
|
||||
fn get_version() -> String {
|
||||
use std::process::Command;
|
||||
|
||||
if let Some(var) = env_var("FISH_BUILD_VERSION") {
|
||||
return var;
|
||||
}
|
||||
|
||||
let path = src_dir.join("version");
|
||||
if let Ok(strver) = read_to_string(path) {
|
||||
return strver;
|
||||
}
|
||||
|
||||
let args = &["describe", "--always", "--dirty=-dirty"];
|
||||
if let Ok(output) = Command::new("git").args(args).output() {
|
||||
let rev = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if !rev.is_empty() {
|
||||
// If it contains a ".", we have a proper version like "3.7",
|
||||
// or "23.2.1-1234-gfab1234"
|
||||
if rev.contains('.') {
|
||||
return rev;
|
||||
}
|
||||
// If it doesn't, we probably got *just* the commit SHA,
|
||||
// like "f1242abcdef".
|
||||
// So we prepend the crate version so it at least looks like
|
||||
// "3.8-gf1242abcdef"
|
||||
// This lacks the commit *distance*, but that can't be helped without
|
||||
// tags.
|
||||
let version = env!("CARGO_PKG_VERSION").to_owned();
|
||||
return version + "-g" + &rev;
|
||||
}
|
||||
}
|
||||
|
||||
// git did not tell us a SHA either because it isn't installed,
|
||||
// or because it refused (safe.directory applies to `git describe`!)
|
||||
// So we read the SHA ourselves.
|
||||
fn get_git_hash() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let workspace_root = workspace_root();
|
||||
let gitdir = workspace_root.join(".git");
|
||||
let jjdir = workspace_root.join(".jj");
|
||||
let commit_id = if gitdir.exists() {
|
||||
// .git/HEAD contains ref: refs/heads/branch
|
||||
let headpath = gitdir.join("HEAD");
|
||||
let headstr = read_to_string(headpath)?;
|
||||
let headref = headstr.split(' ').nth(1).unwrap().trim();
|
||||
|
||||
// .git/refs/heads/branch contains the SHA
|
||||
let refpath = gitdir.join(headref);
|
||||
// Shorten to 9 characters (what git describe does currently)
|
||||
read_to_string(refpath)?
|
||||
} else if jjdir.exists() {
|
||||
let output = Command::new("jj")
|
||||
.args([
|
||||
"log",
|
||||
"--revisions",
|
||||
"@",
|
||||
"--no-graph",
|
||||
"--ignore-working-copy",
|
||||
"--template",
|
||||
"commit_id",
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
String::from_utf8_lossy(&output.stdout).to_string()
|
||||
} else {
|
||||
return Err("did not find either of .git or .jj".into());
|
||||
};
|
||||
let refstr = &commit_id[0..9];
|
||||
let refstr = refstr.trim();
|
||||
|
||||
let version = env!("CARGO_PKG_VERSION").to_owned();
|
||||
Ok(version + "-g" + refstr)
|
||||
}
|
||||
|
||||
get_git_hash().expect("Could not get a version. Either set $FISH_BUILD_VERSION or install git.")
|
||||
String::from_utf8(
|
||||
Command::new("build_tools/git_version_gen.sh")
|
||||
.output()
|
||||
.unwrap()
|
||||
.stdout,
|
||||
)
|
||||
.unwrap()
|
||||
.trim_ascii_end()
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
@@ -8,11 +8,29 @@ if [ "$FISH_CHECK_LINT" = false ]; then
|
||||
lint=false
|
||||
fi
|
||||
|
||||
case "$(uname)" in
|
||||
MSYS*)
|
||||
is_cygwin=true
|
||||
cygwin_var=MSYS
|
||||
;;
|
||||
CYGWIN*)
|
||||
is_cygwin=true
|
||||
cygwin_var=CYGWIN
|
||||
;;
|
||||
*)
|
||||
is_cygwin=false
|
||||
;;
|
||||
esac
|
||||
|
||||
check_dependency_versions=false
|
||||
if [ "${FISH_CHECK_DEPENDENCY_VERSIONS:-false}" != false ]; then
|
||||
check_dependency_versions=true
|
||||
fi
|
||||
|
||||
green='\e[0;32m'
|
||||
yellow='\e[1;33m'
|
||||
reset='\e[m'
|
||||
|
||||
if $check_dependency_versions; then
|
||||
command -v curl
|
||||
command -v jq
|
||||
@@ -33,10 +51,16 @@ fi
|
||||
cargo() {
|
||||
subcmd=$1
|
||||
shift
|
||||
# shellcheck disable=2086
|
||||
command cargo "$subcmd" $cargo_args "$@"
|
||||
if [ -n "$FISH_CHECK_RUST_TOOLCHAIN" ]; then
|
||||
# shellcheck disable=2086
|
||||
command cargo "+$FISH_CHECK_RUST_TOOLCHAIN" "$subcmd" $cargo_args "$@"
|
||||
else
|
||||
# shellcheck disable=2086
|
||||
command cargo "$subcmd" $cargo_args "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# shellcheck disable=2317,2329
|
||||
cleanup () {
|
||||
if [ -n "$gettext_template_dir" ] && [ -e "$gettext_template_dir" ]; then
|
||||
rm -r "$gettext_template_dir"
|
||||
@@ -66,6 +90,7 @@ fi
|
||||
|
||||
gettext_template_dir=$(mktemp -d)
|
||||
(
|
||||
# shellcheck disable=2030
|
||||
export FISH_GETTEXT_EXTRACTION_DIR="$gettext_template_dir"
|
||||
cargo build --workspace --all-targets --features=gettext-extract
|
||||
)
|
||||
@@ -73,17 +98,60 @@ if $lint; then
|
||||
if command -v cargo-deny >/dev/null; then
|
||||
cargo deny --all-features --locked --exclude-dev check licenses
|
||||
fi
|
||||
PATH="$build_dir:$PATH" "$workspace_root/build_tools/style.fish" --all --check
|
||||
for features in "" --no-default-features; do
|
||||
|
||||
if command -v shellcheck >/dev/null || { test -n "$CI" && ! $is_cygwin; }; then
|
||||
cargo xtask shellcheck
|
||||
fi
|
||||
|
||||
PATH="$build_dir:$PATH" cargo xtask format --all --check
|
||||
for features in "" --no-default-features --all-features; do
|
||||
cargo clippy --workspace --all-targets $features
|
||||
done
|
||||
|
||||
cargo xtask gettext --rust-extraction-dir="$gettext_template_dir" check
|
||||
fi
|
||||
cargo test --no-default-features --workspace --all-targets
|
||||
|
||||
# When running `cargo test`, some binaries (e.g. `fish_gettext_extraction`)
|
||||
# are dynamically linked against Rust's `std-xxx.dll` instead of being
|
||||
# statically link as they usually are.
|
||||
# On Cygwin, `PATH`is not properly updated to point to the `std-xxx.dll`
|
||||
# location, so we have to do it manually.
|
||||
# See:
|
||||
# - https://github.com/rust-lang/rust/issues/149050
|
||||
# - https://github.com/msys2/MSYS2-packages/issues/5784
|
||||
(
|
||||
if $is_cygwin; then
|
||||
PATH="$PATH:$(rustc --print target-libdir)"
|
||||
export PATH
|
||||
fi
|
||||
cargo test --no-default-features --workspace --all-targets
|
||||
)
|
||||
cargo test --doc --workspace
|
||||
|
||||
if $lint; then
|
||||
cargo doc --workspace --no-deps
|
||||
fi
|
||||
FISH_GETTEXT_EXTRACTION_DIR=$gettext_template_dir "$workspace_root/tests/test_driver.py" "$build_dir"
|
||||
|
||||
# Using "()" not "{}" because we do want a subshell (for the export)
|
||||
system_tests() (
|
||||
# shellcheck disable=2163
|
||||
[ -n "$*" ] && export "$@"
|
||||
"$workspace_root/tests/test_driver.py" "$build_dir"
|
||||
)
|
||||
|
||||
if $is_cygwin; then
|
||||
# shellcheck disable=2059
|
||||
printf "=== Running ${green}integration tests ${yellow}with${green} symlinks${reset}\n"
|
||||
system_tests "$cygwin_var"=winsymlinks
|
||||
|
||||
# shellcheck disable=2059
|
||||
printf "=== Running ${green}integration tests ${yellow}without${green} symlinks${reset}\n"
|
||||
system_tests "$cygwin_var"=
|
||||
else
|
||||
# shellcheck disable=2059
|
||||
printf "=== Running ${green}integration tests${reset}\n"
|
||||
system_tests
|
||||
fi
|
||||
|
||||
exit
|
||||
}
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
#!/usr/bin/env fish
|
||||
#
|
||||
# Tool to generate gettext messages template file.
|
||||
# Writes to stdout.
|
||||
# Intended to be called from `update_translations.fish`.
|
||||
|
||||
argparse use-existing-template= -- $argv
|
||||
or exit $status
|
||||
|
||||
begin
|
||||
# Write header. This is required by msguniq.
|
||||
# Note that this results in the file being overwritten.
|
||||
# This is desired behavior, to get rid of the results of prior invocations
|
||||
# of this script.
|
||||
begin
|
||||
echo 'msgid ""'
|
||||
echo 'msgstr ""'
|
||||
echo '"Content-Type: text/plain; charset=UTF-8\n"'
|
||||
echo ""
|
||||
end
|
||||
|
||||
set -g workspace_root (path resolve (status dirname)/..)
|
||||
|
||||
set -l rust_extraction_dir
|
||||
if set -l --query _flag_use_existing_template
|
||||
set rust_extraction_dir $_flag_use_existing_template
|
||||
else
|
||||
set rust_extraction_dir (mktemp -d)
|
||||
# We need to build to ensure that the proc macro for extracting strings runs.
|
||||
FISH_GETTEXT_EXTRACTION_DIR=$rust_extraction_dir cargo check --features=gettext-extract
|
||||
or exit 1
|
||||
end
|
||||
|
||||
function mark_section
|
||||
set -l section_name $argv[1]
|
||||
echo 'msgid "fish-section-'$section_name'"'
|
||||
echo 'msgstr ""'
|
||||
echo ''
|
||||
end
|
||||
|
||||
mark_section tier1-from-rust
|
||||
|
||||
# Get rid of duplicates and sort.
|
||||
find $rust_extraction_dir -type f -exec cat {} + | msguniq --no-wrap --sort-output
|
||||
or exit 1
|
||||
|
||||
if not set -l --query _flag_use_existing_template
|
||||
rm -r $rust_extraction_dir
|
||||
end
|
||||
|
||||
function extract_fish_script_messages_impl
|
||||
set -l regex $argv[1]
|
||||
set -e argv[1]
|
||||
# Using xgettext causes more trouble than it helps.
|
||||
# This is due to handling of escaping in fish differing from formats xgettext understands
|
||||
# (e.g. POSIX shell strings).
|
||||
# We work around this issue by manually writing the file content.
|
||||
|
||||
# Steps:
|
||||
# 1. We extract strings to be translated from the relevant files and drop the rest. This step
|
||||
# depends on the regex matching the entire line, and the first capture group matching the
|
||||
# string.
|
||||
# 2. We unescape. This gets rid of some escaping necessary in fish strings.
|
||||
# 3. The resulting strings are sorted alphabetically. This step is optional. Not sorting would
|
||||
# result in strings from the same file appearing together. Removing duplicates is also
|
||||
# optional, since msguniq takes care of that later on as well.
|
||||
# 4. Single backslashes are replaced by double backslashes. This results in the backslashes
|
||||
# being interpreted as literal backslashes by gettext tooling.
|
||||
# 5. Double quotes are escaped, such that they are not interpreted as the start or end of
|
||||
# a msgid.
|
||||
# 6. We transform the string into the format expected in a PO file.
|
||||
cat $argv |
|
||||
string replace --filter --regex $regex '$1' |
|
||||
string unescape |
|
||||
sort -u |
|
||||
sed -E -e 's_\\\\_\\\\\\\\_g' -e 's_"_\\\\"_g' -e 's_^(.*)$_msgid "\1"\nmsgstr ""\n_'
|
||||
end
|
||||
|
||||
function extract_fish_script_messages
|
||||
set -l tier $argv[1]
|
||||
set -e argv[1]
|
||||
if not set -q argv[1]
|
||||
return
|
||||
end
|
||||
# This regex handles explicit requests to translate a message. These are more important to translate
|
||||
# than messages which should be implicitly translated.
|
||||
set -l explicit_regex '.*\( *_ (([\'"]).+?(?<!\\\\)\\2) *\).*'
|
||||
mark_section "$tier-from-script-explicitly-added"
|
||||
extract_fish_script_messages_impl $explicit_regex $argv
|
||||
|
||||
# This regex handles descriptions for `complete` and `function` statements. These messages are not
|
||||
# particularly important to translate. Hence the "implicit" label.
|
||||
set -l implicit_regex '^(?:\s|and |or )*(?:complete|function).*? (?:-d|--description) (([\'"]).+?(?<!\\\\)\\2).*'
|
||||
mark_section "$tier-from-script-implicitly-added"
|
||||
extract_fish_script_messages_impl $implicit_regex $argv
|
||||
end
|
||||
|
||||
set -g share_dir $workspace_root/share
|
||||
|
||||
set -l tier1 $share_dir/config.fish
|
||||
set -l tier2
|
||||
set -l tier3
|
||||
|
||||
for file in $share_dir/completions/*.fish $share_dir/functions/*.fish
|
||||
# set -l tier (string match -r '^# localization: .*' <$file)
|
||||
set -l tier (string replace -rf -m1 \
|
||||
'^# localization: (.*)$' '$1' <$file)
|
||||
if set -q tier[1]
|
||||
switch "$tier"
|
||||
case tier1 tier2 tier3
|
||||
set -a $tier $file
|
||||
case 'skip*'
|
||||
case '*'
|
||||
echo >&2 "$file:1 unexpected localization tier: $tier"
|
||||
exit 1
|
||||
end
|
||||
continue
|
||||
end
|
||||
set -l dirname (path basename (path dirname $file))
|
||||
set -l command_name (path basename --no-extension $file)
|
||||
if test $dirname = functions &&
|
||||
string match -q -- 'fish_*' $command_name
|
||||
set -a tier1 $file
|
||||
continue
|
||||
end
|
||||
if test $dirname != completions
|
||||
echo >&2 "$file:1 missing localization tier for function file"
|
||||
exit 1
|
||||
end
|
||||
if test -e $workspace_root/doc_src/cmds/$command_name.rst
|
||||
set -a tier1 $file
|
||||
else
|
||||
set -a tier3 $file
|
||||
end
|
||||
end
|
||||
|
||||
extract_fish_script_messages tier1 $tier1
|
||||
extract_fish_script_messages tier2 $tier2
|
||||
extract_fish_script_messages tier3 $tier3
|
||||
end |
|
||||
# At this point, all extracted strings have been written to stdout,
|
||||
# starting with the ones taken from the Rust sources,
|
||||
# followed by strings explicitly marked for translation in fish scripts,
|
||||
# and finally the strings from fish scripts which get translated implicitly.
|
||||
# Because we do not eliminate duplicates across these categories,
|
||||
# we do it here, since other gettext tools expect no duplicates.
|
||||
msguniq --no-wrap
|
||||
@@ -1,70 +1,22 @@
|
||||
#!/bin/sh
|
||||
# Originally from the git sources (GIT-VERSION-GEN)
|
||||
# Presumably (C) Junio C Hamano <junkio@cox.net>
|
||||
# Reused under GPL v2.0
|
||||
# Modified for fish by David Adam <zanchey@ucc.gu.uwa.edu.au>
|
||||
|
||||
set -e
|
||||
|
||||
# Find the fish directory as two levels up from script directory.
|
||||
FISH_BASE_DIR="$( cd "$( dirname "$( dirname "$0" )" )" && pwd )"
|
||||
DEF_VER=unknown
|
||||
git_permission_failed=0
|
||||
|
||||
# First see if there is a version file (included in release tarballs),
|
||||
# then try git-describe, then default.
|
||||
if test -f version
|
||||
then
|
||||
VN=$(cat version) || VN="$DEF_VER"
|
||||
else
|
||||
if VN=$(git -C "$FISH_BASE_DIR" describe --always --dirty 2>/dev/null); then
|
||||
:
|
||||
version=$(
|
||||
awk <"$FISH_BASE_DIR/Cargo.toml" -F'"' '$1 == "version = " { print $2 }'
|
||||
)
|
||||
if git_version=$(
|
||||
GIT_CEILING_DIRECTORIES=$FISH_BASE_DIR/.. \
|
||||
git -C "$FISH_BASE_DIR" describe --always --dirty 2>/dev/null); then
|
||||
if [ "$git_version" = "${git_version#"$version"}" ]; then
|
||||
version=$version-g$git_version
|
||||
else
|
||||
if test $? = 128; then
|
||||
# Current git versions return status 128
|
||||
# when run in a repo owned by another user.
|
||||
# Even for describe and everything.
|
||||
# This occurs for `sudo make install`.
|
||||
git_permission_failed=1
|
||||
fi
|
||||
VN="$DEF_VER"
|
||||
version=$git_version
|
||||
fi
|
||||
fi
|
||||
|
||||
# If the first param is --stdout, then output to stdout and exit.
|
||||
if test "$1" = '--stdout'
|
||||
then
|
||||
echo $VN
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Set the output directory as either the first param or cwd.
|
||||
test -n "$1" && OUTPUT_DIR=$1/ || OUTPUT_DIR=
|
||||
FBVF="${OUTPUT_DIR}FISH-BUILD-VERSION-FILE"
|
||||
|
||||
if test "$VN" = unknown && test -r "$FBVF" && test "$git_permission_failed" = 1
|
||||
then
|
||||
# HACK: Git failed, so we keep the current version file.
|
||||
# This helps in case you built fish as a normal user
|
||||
# and then try to `sudo make install` it.
|
||||
date +%s > "${OUTPUT_DIR}"fish-build-version-witness.txt
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if test -r "$FBVF"
|
||||
then
|
||||
VC=$(cat "$FBVF")
|
||||
else
|
||||
VC="unset"
|
||||
fi
|
||||
|
||||
# Maybe output the FBVF
|
||||
# It looks like "2.7.1-621-ga2f065e6"
|
||||
test "$VN" = "$VC" || {
|
||||
echo >&2 "$VN"
|
||||
echo "$VN" >"$FBVF"
|
||||
}
|
||||
|
||||
# Output the fish-build-version-witness.txt
|
||||
# See https://cmake.org/cmake/help/v3.4/policy/CMP0058.html
|
||||
date +%s > "${OUTPUT_DIR}"fish-build-version-witness.txt
|
||||
echo "$version"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# LSAN can detect leaks tracing back to __asan::AsanThread::ThreadStart (probably caused by our
|
||||
# threads not exiting before their TLS dtors are called). Just ignore it.
|
||||
leak:AsanThread
|
||||
|
||||
83
build_tools/make_linux_packages.sh
Executable file
83
build_tools/make_linux_packages.sh
Executable file
@@ -0,0 +1,83 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script takes a source tarball (from build_tools/make_tarball.sh) and a vendor tarball (from
|
||||
# build_tools/make_vendor_tarball.sh, generated if not present), and produces:
|
||||
# * Appropriately-named symlinks to look like a Debian package
|
||||
# * Debian .changes and .dsc files with plain names ($version-1) and supported Ubuntu prefixes
|
||||
# ($version-1~somedistro)
|
||||
# * An RPM spec file
|
||||
# By default, input and output files go in ~/fish_built, but this can be controlled with the
|
||||
# FISH_ARTEFACT_PATH environment variable.
|
||||
|
||||
{
|
||||
|
||||
set -e
|
||||
|
||||
version=$1
|
||||
[ -n "$version" ] || { echo "Version number required as argument" >&2; exit 1; }
|
||||
|
||||
|
||||
[ -n "$DEB_SIGN_KEYID$DEB_SIGN_KEYFILE" ] ||
|
||||
echo "Warning: neither DEB_SIGN_KEYID or DEB_SIGN_KEYFILE environment variables are set; you
|
||||
will need a signing key for the author of the most recent debian/changelog entry." >&2
|
||||
|
||||
workpath=${FISH_ARTEFACT_PATH:-~/fish_built}
|
||||
source_tarball="$workpath"/fish-"$version".tar.xz
|
||||
vendor_tarball="$workpath"/fish-"$version"-vendor.tar.xz
|
||||
|
||||
[ -e "$source_tarball" ] || { echo "Missing source tarball, expected at $source_tarball" >&2; exit 1; }
|
||||
cd "$workpath"
|
||||
|
||||
# Unpack the sources
|
||||
tar xf "$source_tarball"
|
||||
sourcepath="$workpath"/fish-"$version"
|
||||
|
||||
# Generate the vendor tarball if it is not already present
|
||||
[ -e "$vendor_tarball" ] || (cd "$sourcepath"; build_tools/make_vendor_tarball.sh;)
|
||||
|
||||
# This step requires network access, so do it early in case it fails
|
||||
# sh has no real array support
|
||||
ubuntu_versions=$(uv run --script "$sourcepath"/build_tools/supported_ubuntu_versions.py)
|
||||
|
||||
# Write the specfile
|
||||
[ -e "$workpath"/fish.spec ] && { echo "Cowardly refusing to overwite an existing fish.spec" >&2;
|
||||
exit 1; }
|
||||
rpmversion=$(echo "$version" |sed -e 's/-/+/' -e 's/-/./g')
|
||||
sed -e "s/@version@/$version/g" -e "s/@rpmversion@/$rpmversion/g" \
|
||||
< "$sourcepath"/fish.spec.in > "$workpath"/fish.spec
|
||||
|
||||
# Make the symlinks for Debian
|
||||
ln -s "$source_tarball" "$workpath"/fish_"$version".orig.tar.xz
|
||||
ln -s "$vendor_tarball" "$workpath"/fish_"$version".orig-cargo-vendor.tar.xz
|
||||
|
||||
# Set up the Debian source tree
|
||||
cd "$sourcepath"
|
||||
mkdir cargo-vendor
|
||||
tar -C cargo-vendor -x -f "$vendor_tarball"
|
||||
cp -r contrib/debian debian
|
||||
|
||||
# The vendor tarball contains a new .cargo/config.toml, which has the
|
||||
# vendoring overrides appended to it. dpkg-source will add this as a
|
||||
# patch using the flags in debian/
|
||||
cp cargo-vendor/.cargo/config.toml .cargo/config.toml
|
||||
|
||||
# Update the Debian changelog
|
||||
# The release scripts do this for release builds - skip if it has already been done
|
||||
if head -n1 debian/changelog | grep --invert-match --quiet --fixed-strings "$version"; then
|
||||
debchange --newversion "$version-1" --distribution unstable "Snapshot build"
|
||||
fi
|
||||
|
||||
# Builds the "plain" Debian package
|
||||
# debuild runs lintian, which takes ten minutes to run over the vendor directories
|
||||
# just use dpkg-buildpackage directly
|
||||
dpkg-buildpackage --build=source -d
|
||||
|
||||
# Build the Ubuntu packages
|
||||
# deb-reversion does not work on source packages, so do the whole thing ourselves
|
||||
for series in $ubuntu_versions; do
|
||||
sed -i -e "1 s/$version-1)/$version-1~$series)/" -e "1 s/unstable/$series/" debian/changelog
|
||||
dpkg-buildpackage --build=source -d
|
||||
sed -i -e "1 s/$version-1~$series)/$version-1)/" -e "1 s/$series/unstable/" debian/changelog
|
||||
done
|
||||
|
||||
}
|
||||
@@ -25,7 +25,7 @@ NOTARIZE=
|
||||
|
||||
ARM64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=11.0'
|
||||
X86_64_DEPLOY_TARGET='MACOSX_DEPLOYMENT_TARGET=10.12'
|
||||
cmake_args=
|
||||
cmake_args=()
|
||||
|
||||
while getopts "c:sf:i:p:e:nj:" opt; do
|
||||
case $opt in
|
||||
@@ -49,7 +49,7 @@ if [ -n "$NOTARIZE" ] && [ -z "$API_KEY_FILE" ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
VERSION=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
|
||||
VERSION=$(build_tools/git_version_gen.sh)
|
||||
|
||||
echo "Version is $VERSION"
|
||||
|
||||
@@ -82,17 +82,16 @@ do_cmake() {
|
||||
&& env DESTDIR="$PKGDIR/root/" $ARM64_DEPLOY_TARGET make install;
|
||||
}
|
||||
|
||||
# Build for x86-64 but do not install; instead we will make some fat binaries inside the root.
|
||||
# Build for x86-64 but do not install; instead we will make a fat binary inside the root.
|
||||
{ cd "$PKGDIR/build_x86_64" \
|
||||
&& do_cmake -DRust_CARGO_TARGET=x86_64-apple-darwin \
|
||||
&& env $X86_64_DEPLOY_TARGET make VERBOSE=1 -j 12; }
|
||||
|
||||
# Fatten them up.
|
||||
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
|
||||
X86_FILE="$PKGDIR/build_x86_64/$(basename "$FILE")"
|
||||
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
||||
chmod 755 "$FILE"
|
||||
done
|
||||
# Fatten it up.
|
||||
FILE=$PKGDIR/root/usr/local/bin/fish
|
||||
X86_FILE=$PKGDIR/build_x86_64/$(basename "$FILE")
|
||||
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
||||
chmod 755 "$FILE"
|
||||
|
||||
if test -n "$SIGN"; then
|
||||
echo "Signing executables"
|
||||
@@ -105,9 +104,7 @@ if test -n "$SIGN"; then
|
||||
if [ -n "$ENTITLEMENTS_FILE" ]; then
|
||||
ARGS+=(--entitlements-xml-file "$ENTITLEMENTS_FILE")
|
||||
fi
|
||||
for FILE in "$PKGDIR"/root/usr/local/bin/*; do
|
||||
(set +x; rcodesign sign "${ARGS[@]}" "$FILE")
|
||||
done
|
||||
(set +x; rcodesign sign "${ARGS[@]}" "$PKGDIR"/root/usr/local/bin/fish)
|
||||
fi
|
||||
|
||||
pkgbuild --scripts "$SRC_DIR/build_tools/osx_package_scripts" --root "$PKGDIR/root/" --identifier 'com.ridiculousfish.fish-shell-pkg' --version "$VERSION" "$PKGDIR/intermediates/fish.pkg"
|
||||
@@ -128,15 +125,13 @@ fi
|
||||
(cd "$PKGDIR/build_arm64" && env $ARM64_DEPLOY_TARGET make -j 12 fish_macapp)
|
||||
(cd "$PKGDIR/build_x86_64" && env $X86_64_DEPLOY_TARGET make -j 12 fish_macapp)
|
||||
|
||||
# Make the app's /usr/local/bin binaries universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
|
||||
# Make the app's /usr/local/bin/fish binary universal. Note fish.app/Contents/MacOS/fish already is, courtesy of CMake.
|
||||
cd "$PKGDIR/build_arm64"
|
||||
for FILE in fish.app/Contents/Resources/base/usr/local/bin/*; do
|
||||
X86_FILE="$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename "$FILE")"
|
||||
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
||||
|
||||
# macho-universal-create screws up the permissions.
|
||||
chmod 755 "$FILE"
|
||||
done
|
||||
FILE=fish.app/Contents/Resources/base/usr/local/bin/fish
|
||||
X86_FILE=$PKGDIR/build_x86_64/fish.app/Contents/Resources/base/usr/local/bin/$(basename "$FILE")
|
||||
rcodesign macho-universal-create --output "$FILE" "$FILE" "$X86_FILE"
|
||||
# macho-universal-create screws up the permissions.
|
||||
chmod 755 "$FILE"
|
||||
|
||||
if test -n "$SIGN"; then
|
||||
echo "Signing app"
|
||||
|
||||
@@ -3,21 +3,40 @@
|
||||
# Script to generate a tarball
|
||||
# Outputs to $FISH_ARTEFACT_PATH or ~/fish_built by default
|
||||
|
||||
# Exit on error
|
||||
set -e
|
||||
|
||||
# Get the version
|
||||
VERSION=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
|
||||
VERSION=$(build_tools/git_version_gen.sh)
|
||||
|
||||
prefix=fish-$VERSION
|
||||
path=${FISH_ARTEFACT_PATH:-~/fish_built}/$prefix.tar.xz
|
||||
|
||||
tmpdir=$(mktemp -d)
|
||||
manifest=$tmpdir/Cargo.toml
|
||||
lockfile=$tmpdir/Cargo.lock
|
||||
|
||||
sed "s/^version = \".*\"\$/version = \"$VERSION\"/g" Cargo.toml >"$manifest"
|
||||
awk -v version="$VERSION" '
|
||||
/^name = "fish"$/ { ok=1 }
|
||||
ok == 1 && /^version = ".*"$/ {
|
||||
ok = 2;
|
||||
$0 = "version = \"" version "\"";
|
||||
}
|
||||
{print}
|
||||
' \
|
||||
Cargo.lock >"$lockfile"
|
||||
|
||||
git archive \
|
||||
--prefix="$prefix/" \
|
||||
--add-virtual-file="$prefix/version:$VERSION" \
|
||||
--add-virtual-file="$prefix/Cargo.toml:$(cat "$manifest")" \
|
||||
--add-virtual-file="$prefix/Cargo.lock:$(cat "$lockfile")" \
|
||||
HEAD |
|
||||
xz >"$path"
|
||||
|
||||
rm "$manifest"
|
||||
rm "$lockfile"
|
||||
rmdir "$tmpdir"
|
||||
|
||||
# Output what we did, and the sha256 hash
|
||||
echo "Tarball written to $path"
|
||||
openssl dgst -sha256 "$path"
|
||||
|
||||
@@ -8,25 +8,11 @@
|
||||
# Exit on error
|
||||
set -e
|
||||
|
||||
# We need GNU tar as that supports the --mtime and --transform options
|
||||
TAR=notfound
|
||||
for try in tar gtar gnutar; do
|
||||
if $try -Pcf /dev/null --mtime now /dev/null >/dev/null 2>&1; then
|
||||
TAR=$try
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$TAR" = "notfound" ]; then
|
||||
echo 'No suitable tar (supporting --mtime) found as tar/gtar/gnutar in PATH'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the current directory, which we'll use for telling Cargo where to find the sources
|
||||
wd="$PWD"
|
||||
|
||||
# Get the version from git-describe
|
||||
VERSION=$(build_tools/git_version_gen.sh --stdout 2>/dev/null)
|
||||
VERSION=$(build_tools/git_version_gen.sh)
|
||||
|
||||
# The name of the prefix, which is the directory that you get when you untar
|
||||
prefix="fish-$VERSION"
|
||||
@@ -42,8 +28,14 @@ rm -f "$path" "$path".xz
|
||||
PREFIX_TMPDIR=$(mktemp -d)
|
||||
cd "$PREFIX_TMPDIR"
|
||||
|
||||
# Add .cargo/config.toml. This means that the caller may need to remove that file from the tarball.
|
||||
# See e4674cd7b5f (.cargo/config.toml: exclude from tarball, 2025-01-12)
|
||||
|
||||
mkdir .cargo
|
||||
cargo vendor --manifest-path "$wd/Cargo.toml" > .cargo/config.toml
|
||||
{
|
||||
cat "$wd"/.cargo/config.toml
|
||||
cargo vendor --manifest-path "$wd/Cargo.toml"
|
||||
} > .cargo/config.toml
|
||||
|
||||
tar cfvJ "$path".xz vendor .cargo
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, "Helvetica Neue", sans-serif;
|
||||
font-size: 10pt;
|
||||
}
|
||||
code, tt {
|
||||
font-family: ui-monospace, Menlo, monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
<strong>fish</strong> is a smart and user-friendly command line shell. For more information, visit <a href="https://fishshell.com">fishshell.com</a>.
|
||||
</p>
|
||||
<p>
|
||||
<strong>fish</strong> will be installed into <tt>/usr/local/</tt>, and its path will be added to <wbr><tt>/etc/shells</tt> if necessary.
|
||||
</p>
|
||||
<p>
|
||||
Your default shell will <em>not</em> be changed. To make <strong>fish</strong> your login shell after the installation, run:
|
||||
</p>
|
||||
<p>
|
||||
<code>chsh -s /usr/local/bin/fish</code>
|
||||
</p>
|
||||
<p>Enjoy! Bugs can be reported on <a href="https://github.org/fish-shell/fish-shell/">GitHub</a>.</p>
|
||||
</body>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, "Helvetica Neue", sans-serif;
|
||||
font-size: 10pt;
|
||||
}
|
||||
code, tt {
|
||||
font-family: ui-monospace, Menlo, monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
<strong>fish</strong> is a smart and user-friendly command line shell. For more information, visit <a href="https://fishshell.com">fishshell.com</a>.
|
||||
</p>
|
||||
<p>
|
||||
<strong>fish</strong> will be installed into <tt>/usr/local/</tt>, and its path will be added to <wbr><tt>/etc/shells</tt> if necessary.
|
||||
</p>
|
||||
<p>
|
||||
Your default shell will <em>not</em> be changed. To make <strong>fish</strong> your login shell after the installation, run:
|
||||
</p>
|
||||
<p>
|
||||
<code>chsh -s /usr/local/bin/fish</code>
|
||||
</p>
|
||||
<p>Enjoy! Bugs can be reported on <a href="https://github.com/fish-shell/fish-shell/">GitHub</a>.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
|
||||
if test $# -eq 0
|
||||
then
|
||||
echo "usage: $0 shellname [shellname ...]"
|
||||
exit 1
|
||||
echo "usage: $0 shellname [shellname ...]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
scriptname=$(basename "$0")
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "${scriptname} must be run as root"
|
||||
exit 1
|
||||
echo "${scriptname} must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
file=/etc/shells
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
echo "Removing any previous installation"
|
||||
pkgutil --pkg-info "${INSTALL_PKG_SESSION_ID}" && pkgutil --only-files --files "${INSTALL_PKG_SESSION_ID}" | while read -r installed
|
||||
do rm -v "${DSTVOLUME}${installed}"
|
||||
do rm -v "${DSTVOLUME}${installed}"
|
||||
done
|
||||
echo "... removed"
|
||||
|
||||
@@ -47,8 +47,8 @@ if test -z "$CI" || [ "$(git -C "$workspace_root" tag | wc -l)" -gt 1 ]; then {
|
||||
num_authors=$(wc -l <"$relnotes_tmp/committers-now")
|
||||
num_new_authors=$(wc -l <"$relnotes_tmp/committers-new")
|
||||
printf %s \
|
||||
"This release comprises $num_commits commits since $previous_version," \
|
||||
" contributed by $num_authors authors, $num_new_authors of which are new committers."
|
||||
"This release brings $num_commits new commits since $previous_version," \
|
||||
" contributed by $num_authors authors, $num_new_authors of which are new faces."
|
||||
echo
|
||||
echo
|
||||
)
|
||||
@@ -86,10 +86,13 @@ if test -z "$CI" || [ "$(git -C "$workspace_root" tag | wc -l)" -gt 1 ]; then {
|
||||
echo
|
||||
echo 'Download links:'
|
||||
echo 'To download the source code for fish, we suggest the file named ``fish-'"$version"'.tar.xz``.'
|
||||
# shellcheck disable=2016
|
||||
echo 'The file downloaded from ``Source code (tar.gz)`` will not build correctly.'
|
||||
# shellcheck disable=2016
|
||||
echo 'A GPG signature using `this key <'"${FISH_GPG_PUBLIC_KEY_URL:-???}"'>`__ is available as ``fish-'"$version"'.tar.xz.asc``.'
|
||||
echo
|
||||
echo 'The files called ``fish-'"$version"'-linux-*.tar.xz`` contain'
|
||||
# shellcheck disable=2016
|
||||
echo '`standalone fish binaries <https://github.com/fish-shell/fish-shell/?tab=readme-ov-file#building-fish-with-cargo>`__'
|
||||
echo 'for any Linux with the given CPU architecture.'
|
||||
} >"$relnotes_tmp/fake-workspace"/CHANGELOG.rst
|
||||
|
||||
@@ -72,7 +72,7 @@ integration_branch=$(
|
||||
--format='%(refname:strip=2)'
|
||||
)
|
||||
[ -n "$integration_branch" ] ||
|
||||
git merge-base --is-ancestor $remote/master HEAD
|
||||
git merge-base --is-ancestor "$remote"/master HEAD
|
||||
|
||||
sed -n 1p CHANGELOG.rst | grep -q '^fish .*(released .*)$'
|
||||
sed -n 2p CHANGELOG.rst | grep -q '^===*$'
|
||||
@@ -83,13 +83,18 @@ sed -i \
|
||||
-e "2c$(printf %s "$changelog_title" | sed s/./=/g)" \
|
||||
CHANGELOG.rst
|
||||
|
||||
CommitVersion() {
|
||||
sed -i "s/^version = \".*\"/version = \"$1\"/g" Cargo.toml
|
||||
cargo fetch --offline
|
||||
if [ "$1" = "$version" ]; then
|
||||
# debchange is a Debian script to manage the Debian changelog, but
|
||||
# it's too annoying to install everywhere. Just do it by hand.
|
||||
cat - contrib/debian/changelog > contrib/debian/changelog.new <<EOF
|
||||
CreateCommit() {
|
||||
git commit -m "$1
|
||||
|
||||
Created by ./build_tools/release.sh $version"
|
||||
}
|
||||
|
||||
sed -i "s/^version = \".*\"/version = \"$1\"/g" Cargo.toml
|
||||
cargo fetch --offline # bumps the version in Cargo.lock
|
||||
if [ "$1" = "$version" ]; then
|
||||
# debchange is a Debian script to manage the Debian changelog, but
|
||||
# it's too annoying to install everywhere. Just do it by hand.
|
||||
cat - contrib/debian/changelog > contrib/debian/changelog.new <<EOF
|
||||
fish (${version}-1) stable; urgency=medium
|
||||
|
||||
* Release of new version $version.
|
||||
@@ -99,21 +104,18 @@ fish (${version}-1) stable; urgency=medium
|
||||
-- $committer $(date -R)
|
||||
|
||||
EOF
|
||||
mv contrib/debian/changelog.new contrib/debian/changelog
|
||||
git add contrib/debian/changelog
|
||||
fi
|
||||
git add CHANGELOG.rst Cargo.toml Cargo.lock
|
||||
git commit -m "$2
|
||||
|
||||
Created by ./build_tools/release.sh $version"
|
||||
}
|
||||
|
||||
CommitVersion "$version" "Release $version"
|
||||
mv contrib/debian/changelog.new contrib/debian/changelog
|
||||
git add contrib/debian/changelog
|
||||
fi
|
||||
git add CHANGELOG.rst Cargo.toml Cargo.lock
|
||||
CreateCommit "Release $version"
|
||||
|
||||
# Tags must be full objects, not lightweight tags, for
|
||||
# git_version-gen.sh to work.
|
||||
git -c "user.signingKey=$committer" \
|
||||
tag --sign --message="Release $version" $version
|
||||
tag --sign --message="Release $version" "$version"
|
||||
|
||||
git push $remote $version
|
||||
git push "$remote" "$version"
|
||||
|
||||
TIMEOUT=
|
||||
gh() {
|
||||
@@ -171,21 +173,25 @@ actual_tag_oid=$(git ls-remote "$remote" |
|
||||
(
|
||||
cd "$tmpdir/local-tarball/fish-$version"
|
||||
uv --no-managed-python venv
|
||||
# shellcheck disable=1091
|
||||
. .venv/bin/activate
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug .
|
||||
ninja doc
|
||||
)
|
||||
CopyDocs() {
|
||||
rm -rf "$fish_site/site/docs/$1"
|
||||
cp -r "$tmpdir/local-tarball/fish-$version/user_doc/html" "$fish_site/site/docs/$1"
|
||||
git -C $fish_site add "site/docs/$1"
|
||||
cp -r "$tmpdir/local-tarball/fish-$version/cargo/fish-docs/html" "$fish_site/site/docs/$1"
|
||||
git -C "$fish_site" add "site/docs/$1"
|
||||
}
|
||||
minor_version=${version%.*}
|
||||
CopyDocs "$minor_version"
|
||||
latest_release=$(
|
||||
releases=$(git tag | grep '^[0-9]*\.[0-9]*\.[0-9]*.*' |
|
||||
sed $(: "De-prioritize release candidates (1.2.3-rc0)") \
|
||||
's/-/~/g' | LC_ALL=C sort --version-sort)
|
||||
sed '
|
||||
# De-prioritize release candidates (1.2.3-rc0)
|
||||
s/-/~/g
|
||||
' | LC_ALL=C sort --version-sort
|
||||
)
|
||||
printf %s\\n "$releases" | tail -1
|
||||
)
|
||||
if [ "$version" = "$latest_release" ]; then
|
||||
@@ -270,7 +276,7 @@ done
|
||||
)
|
||||
|
||||
if [ -n "$integration_branch" ]; then {
|
||||
git push $remote "$version^{commit}":refs/heads/$integration_branch
|
||||
git push "$remote" "$version^{commit}:refs/heads/$integration_branch"
|
||||
} else {
|
||||
changelog=$(cat - CHANGELOG.rst <<EOF
|
||||
fish ?.?.? (released ???)
|
||||
@@ -279,8 +285,9 @@ fish ?.?.? (released ???)
|
||||
EOF
|
||||
)
|
||||
printf %s\\n "$changelog" >CHANGELOG.rst
|
||||
CommitVersion ${version}-snapshot "start new cycle"
|
||||
git push $remote HEAD:master
|
||||
git add CHANGELOG.rst
|
||||
CreateCommit "start new cycle"
|
||||
git push "$remote" HEAD:master
|
||||
} fi
|
||||
|
||||
milestone_version="$(
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
#!/usr/bin/env fish
|
||||
#
|
||||
# This runs Python files, fish scripts (*.fish), and Rust files
|
||||
# through their respective code formatting programs.
|
||||
#
|
||||
# `--all`: Format all eligible files instead of the ones specified as arguments.
|
||||
# `--check`: Instead of reformatting, fail if a file is not formatted correctly.
|
||||
# `--force`: Proceed without asking if uncommitted changes are detected.
|
||||
# Only relevant if `--all` is specified but `--check` is not specified.
|
||||
|
||||
set -l fish_files
|
||||
set -l python_files
|
||||
set -l rust_files
|
||||
set -l all no
|
||||
|
||||
argparse all check force -- $argv
|
||||
or exit $status
|
||||
|
||||
if set -l -q _flag_all
|
||||
set all yes
|
||||
if set -q argv[1]
|
||||
echo "Unexpected arguments: '$argv'"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
set -l workspace_root (status dirname)/..
|
||||
|
||||
if test $all = yes
|
||||
if not set -l -q _flag_force; and not set -l -q _flag_check
|
||||
# Potential for false positives: Not all fish files are formatted, see the `fish_files`
|
||||
# definition below.
|
||||
set -l relevant_uncommitted_changes (git status --porcelain --short --untracked-files=all | sed -e 's/^ *[^ ]* *//' | grep -E '.*\.(fish|py|rs)$')
|
||||
if set -q relevant_uncommitted_changes[1]
|
||||
for changed_file in $relevant_uncommitted_changes
|
||||
echo $changed_file
|
||||
end
|
||||
echo
|
||||
echo 'You have uncommitted changes (listed above). Are you sure you want to restyle?'
|
||||
read -P 'y/N? ' -n1 -l ans
|
||||
if not string match -qi y -- $ans
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
||||
set fish_files $workspace_root/{benchmarks,build_tools,etc,share}/**.fish
|
||||
set python_files $workspace_root
|
||||
else
|
||||
# Format the files specified as arguments.
|
||||
set -l files $argv
|
||||
set fish_files (string match -r '^.*\.fish$' -- $files)
|
||||
set python_files (string match -r '^.*\.py$' -- $files)
|
||||
set rust_files (string match -r '^.*\.rs$' -- $files)
|
||||
end
|
||||
|
||||
set -l red (set_color red)
|
||||
set -l green (set_color green)
|
||||
set -l yellow (set_color yellow)
|
||||
set -l normal (set_color normal)
|
||||
|
||||
function die -V red -V normal
|
||||
echo $red$argv[1]$normal
|
||||
exit 1
|
||||
end
|
||||
|
||||
if set -q fish_files[1]
|
||||
if not type -q fish_indent
|
||||
echo
|
||||
echo $yellow'Could not find `fish_indent` in `$PATH`.'$normal
|
||||
exit 127
|
||||
end
|
||||
echo === Running "$green"fish_indent"$normal"
|
||||
if set -l -q _flag_check
|
||||
fish_indent --check -- $fish_files
|
||||
or die "Fish files are not formatted correctly."
|
||||
else
|
||||
fish_indent -w -- $fish_files
|
||||
end
|
||||
end
|
||||
|
||||
if set -q python_files[1]
|
||||
if not type -q ruff
|
||||
echo
|
||||
echo $yellow'Please install `ruff` to style python'$normal
|
||||
exit 127
|
||||
end
|
||||
echo === Running "$green"ruff format"$normal"
|
||||
if set -l -q _flag_check
|
||||
ruff format --check $python_files
|
||||
or die "Python files are not formatted correctly."
|
||||
else
|
||||
ruff format $python_files
|
||||
end
|
||||
end
|
||||
|
||||
if test $all = yes; or set -q rust_files[1]
|
||||
if not cargo fmt --version >/dev/null
|
||||
echo
|
||||
echo $yellow'Please install "rustfmt" to style Rust, e.g. via:'
|
||||
echo "rustup component add rustfmt"$normal
|
||||
exit 127
|
||||
end
|
||||
|
||||
set -l edition_spec string match -r '^edition\s*=.*'
|
||||
test "$($edition_spec <Cargo.toml)" = "$($edition_spec <.rustfmt.toml)"
|
||||
or die "Cargo.toml and .rustfmt.toml use different editions"
|
||||
|
||||
echo === Running "$green"rustfmt"$normal"
|
||||
if set -l -q _flag_check
|
||||
if test $all = yes
|
||||
cargo fmt --all --check
|
||||
else
|
||||
rustfmt --check --files-with-diff $rust_files
|
||||
end
|
||||
or die "Rust files are not formatted correctly."
|
||||
else
|
||||
if test $all = yes
|
||||
cargo fmt --all
|
||||
else
|
||||
rustfmt $rust_files
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -10,22 +10,49 @@ command -v updatecli
|
||||
command -v uv
|
||||
sort --version-sort </dev/null
|
||||
|
||||
uv lock --check
|
||||
# TODO This is copied from .github/actions/install-sphinx/action.yml
|
||||
uv lock --check --exclude-newer="$(awk -F'"' <uv.lock '/^exclude-newer[[:space:]]*=/ {print $2}')"
|
||||
|
||||
update_gh_action() {
|
||||
repo=$1
|
||||
version=$(curl -fsS "https://api.github.com/repos/$repo/releases/latest" | jq -r .tag_name)
|
||||
[ -n "$version" ]
|
||||
tag_oid=$(git ls-remote "https://github.com/$repo.git" "refs/tags/$version" | cut -f1)
|
||||
[ -n "$tag_oid" ]
|
||||
find .github/workflows -name '*.yml' -type f -exec \
|
||||
sed -i "s|uses: $repo@\S\+\( \+#.*\)\?|\
|
||||
uses: $repo@$tag_oid # $version, build_tools/update-dependencies.sh|g" {} +
|
||||
}
|
||||
update_gh_action actions/checkout
|
||||
update_gh_action actions/github-script
|
||||
update_gh_action actions/upload-artifact
|
||||
update_gh_action actions/download-artifact
|
||||
update_gh_action docker/login-action
|
||||
update_gh_action docker/build-push-action
|
||||
update_gh_action docker/metadata-action
|
||||
update_gh_action EmbarkStudios/cargo-deny-action
|
||||
update_gh_action dessant/lock-threads
|
||||
update_gh_action softprops/action-gh-release
|
||||
update_gh_action msys2/setup-msys2
|
||||
|
||||
updatecli "${@:-apply}"
|
||||
|
||||
uv lock # Python version constraints may have changed.
|
||||
# Python version constraints may have changed.
|
||||
uv lock --upgrade --exclude-newer="$(date --date='7 days ago' --iso-8601)"
|
||||
|
||||
from_gh() {
|
||||
repo=$1
|
||||
path=$2
|
||||
out_dir=$3
|
||||
destination=$3
|
||||
contents=$(curl -fsS https://raw.githubusercontent.com/"${repo}"/refs/heads/master/"${path}")
|
||||
printf '%s\n' >"$out_dir/$(basename "$path")" "$contents"
|
||||
printf '%s\n' "$contents" >"$destination"
|
||||
}
|
||||
from_gh ridiculousfish/widecharwidth widechar_width.rs crates/widecharwidth/src/
|
||||
from_gh ridiculousfish/littlecheck littlecheck/littlecheck.py tests/
|
||||
|
||||
from_gh ridiculousfish/widecharwidth widechar_width.rs crates/widecharwidth/src/widechar_width.rs
|
||||
from_gh ridiculousfish/littlecheck littlecheck/littlecheck.py tests/littlecheck.py
|
||||
from_gh catppuccin/fish themes/catppuccin-frappe.theme share/themes/catppuccin-frappe.theme
|
||||
from_gh catppuccin/fish themes/catppuccin-macchiato.theme share/themes/catppuccin-macchiato.theme
|
||||
from_gh catppuccin/fish themes/catppuccin-mocha.theme share/themes/catppuccin-mocha.theme
|
||||
|
||||
# Update Cargo.lock
|
||||
cargo update
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
#!/usr/bin/env fish
|
||||
|
||||
# Updates the files used for gettext translations.
|
||||
# By default, the whole xgettext + msgmerge pipeline runs,
|
||||
# which extracts the messages from the source files into $template_file,
|
||||
# and updates the PO files for each language from that.
|
||||
#
|
||||
# Use cases:
|
||||
# For developers:
|
||||
# - Run with no args to update all PO files after making changes to Rust/fish sources.
|
||||
# For translators:
|
||||
# - Specify the language you want to work on as an argument, which must be a file in the
|
||||
# localization/po/ directory. You can specify a language which does not have translations
|
||||
# yet by specifying the name of a file which does not yet exist.
|
||||
# Make sure to follow the naming convention.
|
||||
# For testing:
|
||||
# - Specify `--dry-run` to see if any updates to the PO files would by applied by this script.
|
||||
# If this flag is specified, the script will exit with an error if there are outstanding
|
||||
# changes, and will display the diff. Do not specify other flags if `--dry-run` is specified.
|
||||
#
|
||||
# Specify `--use-existing-template=DIR` to prevent running cargo for extracting an up-to-date
|
||||
# version of the localized strings. This flag is intended for testing setups which make it
|
||||
# inconvenient to run cargo here, but run it in an earlier step to ensure up-to-date values.
|
||||
# This argument is passed on to the `fish_xgettext.fish` script and has no other uses.
|
||||
# `DIR` must be the path to a gettext template file generated from our compilation process.
|
||||
# It can be obtained by running:
|
||||
# set -l DIR (mktemp -d)
|
||||
# FISH_GETTEXT_EXTRACTION_DIR=$DIR cargo check --features=gettext-extract
|
||||
|
||||
# The sort utility is locale-sensitive.
|
||||
# Ensure that sorting output is consistent by setting LC_ALL here.
|
||||
set -gx LC_ALL C.UTF-8
|
||||
|
||||
set -l build_tools (status dirname)
|
||||
set -l po_dir $build_tools/../localization/po
|
||||
|
||||
set -l extract
|
||||
|
||||
argparse dry-run use-existing-template= -- $argv
|
||||
or exit $status
|
||||
|
||||
if test -z $argv[1]
|
||||
# Update everything if not specified otherwise.
|
||||
set -g po_files $po_dir/*.po
|
||||
else
|
||||
set -l po_dir_id (stat --format='%d:%i' -- $po_dir)
|
||||
for arg in $argv
|
||||
set -l arg_dir_id (stat --format='%d:%i' -- (dirname $arg) 2>/dev/null)
|
||||
if test $po_dir_id != "$arg_dir_id"
|
||||
echo "Argument $arg is not a file in the directory $(realpath $po_dir)."
|
||||
echo "Non-option arguments must specify paths to files in this directory."
|
||||
echo ""
|
||||
echo "If you want to add a new language to the translations not the following:"
|
||||
echo "The filename must identify a language, with a two letter ISO 639-1 language code of the target language (e.g. 'pt' for Portuguese), and use the file extension '.po'."
|
||||
echo "Optionally, you can specify a regional variant (e.g. 'pt_BR')."
|
||||
echo "So valid filenames are of the shape 'll.po' or 'll_CC.po'."
|
||||
exit 1
|
||||
end
|
||||
if not basename $arg | grep -qE '^[a-z]{2,3}(_[A-Z]{2})?\.po$'
|
||||
echo "Filename does not match the expected format ('ll.po' or 'll_CC.po')."
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
set -g po_files $argv
|
||||
end
|
||||
|
||||
set -g template_file (mktemp)
|
||||
# Protect from externally set $tmpdir leaking into this script.
|
||||
set -g tmpdir
|
||||
|
||||
function cleanup_exit
|
||||
set -l exit_status $status
|
||||
|
||||
rm $template_file
|
||||
|
||||
if set -g --query tmpdir[1]
|
||||
rm -r $tmpdir
|
||||
end
|
||||
|
||||
exit $exit_status
|
||||
end
|
||||
|
||||
if set -l --query extract
|
||||
set -l xgettext_args
|
||||
if set -l --query _flag_use_existing_template
|
||||
set xgettext_args --use-existing-template=$_flag_use_existing_template
|
||||
end
|
||||
$build_tools/fish_xgettext.fish $xgettext_args >$template_file
|
||||
or cleanup_exit
|
||||
end
|
||||
|
||||
if set -l --query _flag_dry_run
|
||||
# On a dry run, we do not modify localization/po/ but write to a temporary directory instead
|
||||
# and check if there is a difference between localization/po/ and the tmpdir after re-generating
|
||||
# the PO files.
|
||||
set -g tmpdir (mktemp -d)
|
||||
|
||||
# Ensure tmpdir has the same initial state as the po dir.
|
||||
cp -r $po_dir/* $tmpdir
|
||||
end
|
||||
|
||||
# This is used to identify lines which should be set here via $header_lines.
|
||||
# Make sure that this prefix does not appear elsewhere in the file and only contains characters
|
||||
# without special meaning in a sed pattern.
|
||||
set -g header_prefix "# fish-note-sections: "
|
||||
|
||||
function print_header
|
||||
set -l header_lines \
|
||||
"Translations are divided into sections, each starting with a fish-section-* pseudo-message." \
|
||||
"The first few sections are more important." \
|
||||
"Ignore the tier3 sections unless you have a lot of time."
|
||||
for line in $header_lines
|
||||
printf '%s%s\n' $header_prefix $line
|
||||
end
|
||||
end
|
||||
|
||||
function merge_po_files --argument-names template_file po_file
|
||||
msgmerge --no-wrap --update --no-fuzzy-matching --backup=none --quiet \
|
||||
$po_file $template_file
|
||||
or cleanup_exit
|
||||
set -l new_po_file (mktemp) # TODO Remove on failure.
|
||||
# Remove obsolete messages instead of keeping them as #~ entries.
|
||||
and msgattrib --no-wrap --no-obsolete -o $new_po_file $po_file
|
||||
or cleanup_exit
|
||||
|
||||
begin
|
||||
print_header
|
||||
# Paste PO file without old header lines.
|
||||
sed '/^'$header_prefix'/d' $new_po_file
|
||||
end >$po_file
|
||||
rm $new_po_file
|
||||
end
|
||||
|
||||
for po_file in $po_files
|
||||
if set --query tmpdir[1]
|
||||
set po_file $tmpdir/(basename $po_file)
|
||||
end
|
||||
if test -e $po_file
|
||||
merge_po_files $template_file $po_file
|
||||
else
|
||||
begin
|
||||
print_header
|
||||
cat $template_file
|
||||
end >$po_file
|
||||
end
|
||||
end
|
||||
|
||||
if set -g --query tmpdir[1]
|
||||
diff -ur $po_dir $tmpdir
|
||||
or begin
|
||||
echo ERROR: translations in localization/po/ are stale. Try running build_tools/update_translations.fish
|
||||
cleanup_exit
|
||||
end
|
||||
end
|
||||
|
||||
cleanup_exit
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -11,6 +11,6 @@ codename=$(
|
||||
curl -fsS https://sources.debian.org/api/src/"${package}"/ |
|
||||
jq -r --arg codename "${codename}" '
|
||||
.versions[] | select(.suites[] == $codename) | .version' |
|
||||
sed 's/^\([0-9]\+\.[0-9]\+\).*/\1/' |
|
||||
sed -E 's/^([0-9]+\.[0-9]+).*/\1/' |
|
||||
sort --version-sort |
|
||||
tail -1
|
||||
|
||||
@@ -1,42 +1,32 @@
|
||||
find_program(SPHINX_EXECUTABLE NAMES sphinx-build
|
||||
HINTS
|
||||
$ENV{SPHINX_DIR}
|
||||
PATH_SUFFIXES bin
|
||||
DOC "Sphinx documentation generator")
|
||||
|
||||
include(FeatureSummary)
|
||||
|
||||
set(SPHINX_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/doc_src")
|
||||
set(SPHINX_ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}/user_doc")
|
||||
set(SPHINX_BUILD_DIR "${SPHINX_ROOT_DIR}/build")
|
||||
set(SPHINX_HTML_DIR "${SPHINX_ROOT_DIR}/html")
|
||||
set(SPHINX_MANPAGE_DIR "${SPHINX_ROOT_DIR}/man")
|
||||
set(SPHINX_OUTPUT_DIR "${FISH_RUST_BUILD_DIR}/fish-docs")
|
||||
|
||||
set(FISH_INDENT_FOR_BUILDING_DOCS "" CACHE FILEPATH "Path to fish_indent executable for building HTML docs")
|
||||
|
||||
if(FISH_INDENT_FOR_BUILDING_DOCS)
|
||||
set(SPHINX_HTML_FISH_INDENT_DEP)
|
||||
else()
|
||||
set(FISH_INDENT_FOR_BUILDING_DOCS "${CMAKE_CURRENT_BINARY_DIR}/fish_indent")
|
||||
set(SPHINX_HTML_FISH_INDENT_DEP fish_indent)
|
||||
endif()
|
||||
|
||||
set(VARS_FOR_CARGO_SPHINX_WRAPPER
|
||||
"CARGO_TARGET_DIR=${FISH_RUST_BUILD_DIR}"
|
||||
"FISH_SPHINX=${SPHINX_EXECUTABLE}"
|
||||
)
|
||||
|
||||
# sphinx-docs uses fish_indent for highlighting.
|
||||
# Prepend the output dir of fish_indent to PATH.
|
||||
add_custom_target(sphinx-docs
|
||||
mkdir -p ${SPHINX_HTML_DIR}/_static/
|
||||
COMMAND env PATH="${CMAKE_BINARY_DIR}:$$PATH"
|
||||
${SPHINX_EXECUTABLE}
|
||||
-j auto
|
||||
-q -b html
|
||||
-c "${SPHINX_SRC_DIR}"
|
||||
-d "${SPHINX_ROOT_DIR}/.doctrees-html"
|
||||
"${SPHINX_SRC_DIR}"
|
||||
"${SPHINX_HTML_DIR}"
|
||||
DEPENDS ${SPHINX_SRC_DIR}/fish_indent_lexer.py fish_indent
|
||||
COMMAND env ${VARS_FOR_CARGO_SPHINX_WRAPPER}
|
||||
${Rust_CARGO} xtask html-docs --fish-indent=${FISH_INDENT_FOR_BUILDING_DOCS}
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
DEPENDS ${SPHINX_HTML_FISH_INDENT_DEP}
|
||||
COMMENT "Building HTML documentation with Sphinx")
|
||||
|
||||
add_custom_target(sphinx-manpages
|
||||
env FISH_BUILD_VERSION_FILE=${CMAKE_CURRENT_BINARY_DIR}/${FBVF}
|
||||
${SPHINX_EXECUTABLE}
|
||||
-j auto
|
||||
-q -b man
|
||||
-c "${SPHINX_SRC_DIR}"
|
||||
-d "${SPHINX_ROOT_DIR}/.doctrees-man"
|
||||
"${SPHINX_SRC_DIR}"
|
||||
"${SPHINX_MANPAGE_DIR}/man1"
|
||||
DEPENDS CHECK-FISH-BUILD-VERSION-FILE
|
||||
COMMAND env ${VARS_FOR_CARGO_SPHINX_WRAPPER}
|
||||
${Rust_CARGO} xtask man-pages
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMENT "Building man pages with Sphinx")
|
||||
|
||||
if(NOT DEFINED WITH_DOCS) # Don't check for legacy options if the new one is defined, to help bisecting.
|
||||
@@ -61,10 +51,10 @@ endif()
|
||||
add_feature_info(Documentation WITH_DOCS "user manual and documentation")
|
||||
|
||||
if(WITH_DOCS)
|
||||
configure_file("${SPHINX_SRC_DIR}/conf.py" "${SPHINX_BUILD_DIR}/conf.py" @ONLY)
|
||||
add_custom_target(doc ALL
|
||||
DEPENDS sphinx-docs sphinx-manpages)
|
||||
add_custom_target(doc ALL DEPENDS sphinx-docs sphinx-manpages)
|
||||
# Group docs targets into a DocsTargets folder
|
||||
set_property(TARGET doc sphinx-docs sphinx-manpages
|
||||
PROPERTY FOLDER cmake/DocTargets)
|
||||
set_property(
|
||||
TARGET doc sphinx-docs sphinx-manpages
|
||||
PROPERTY FOLDER cmake/DocTargets
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -22,17 +22,17 @@ foreach(_VAR ${_Rust_USER_VARS})
|
||||
endforeach()
|
||||
|
||||
if (NOT DEFINED Rust_CARGO_CACHED)
|
||||
find_program(Rust_CARGO_CACHED cargo PATHS "$ENV{HOME}/.cargo/bin")
|
||||
find_program(Rust_CARGO_CACHED cargo PATHS "$ENV{HOME}/.cargo/bin")
|
||||
endif()
|
||||
|
||||
if (NOT EXISTS "${Rust_CARGO_CACHED}")
|
||||
message(FATAL_ERROR "The cargo executable ${Rust_CARGO_CACHED} was not found. "
|
||||
"Consider setting `Rust_CARGO_CACHED` to the absolute path of `cargo`."
|
||||
)
|
||||
message(FATAL_ERROR "The cargo executable ${Rust_CARGO_CACHED} was not found. "
|
||||
"Consider setting `Rust_CARGO_CACHED` to the absolute path of `cargo`."
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED Rust_COMPILER_CACHED)
|
||||
find_program(Rust_COMPILER_CACHED rustc PATHS "$ENV{HOME}/.cargo/bin")
|
||||
find_program(Rust_COMPILER_CACHED rustc PATHS "$ENV{HOME}/.cargo/bin")
|
||||
endif()
|
||||
|
||||
|
||||
@@ -45,31 +45,31 @@ endif()
|
||||
# Figure out the target by just using the host target.
|
||||
# If you want to cross-compile, you'll have to set Rust_CARGO_TARGET
|
||||
if(NOT Rust_CARGO_TARGET_CACHED)
|
||||
execute_process(
|
||||
COMMAND "${Rust_COMPILER_CACHED}" --version --verbose
|
||||
OUTPUT_VARIABLE _RUSTC_VERSION_RAW
|
||||
RESULT_VARIABLE _RUSTC_VERSION_RESULT
|
||||
)
|
||||
|
||||
if(NOT ( "${_RUSTC_VERSION_RESULT}" EQUAL "0" ))
|
||||
message(FATAL_ERROR "Failed to get rustc version.\n"
|
||||
"${Rust_COMPILER} --version failed with error: `${_RUSTC_VERSION_RESULT}`")
|
||||
endif()
|
||||
|
||||
if (_RUSTC_VERSION_RAW MATCHES "host: ([a-zA-Z0-9_\\-]*)\n")
|
||||
set(Rust_DEFAULT_HOST_TARGET "${CMAKE_MATCH_1}")
|
||||
else()
|
||||
message(FATAL_ERROR
|
||||
"Failed to parse rustc host target. `rustc --version --verbose` evaluated to:\n${_RUSTC_VERSION_RAW}"
|
||||
execute_process(
|
||||
COMMAND "${Rust_COMPILER_CACHED}" --version --verbose
|
||||
OUTPUT_VARIABLE _RUSTC_VERSION_RAW
|
||||
RESULT_VARIABLE _RUSTC_VERSION_RESULT
|
||||
)
|
||||
endif()
|
||||
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
message(FATAL_ERROR "CMake is in cross-compiling mode."
|
||||
"Manually set `Rust_CARGO_TARGET`."
|
||||
)
|
||||
endif()
|
||||
set(Rust_CARGO_TARGET_CACHED "${Rust_DEFAULT_HOST_TARGET}" CACHE STRING "Target triple")
|
||||
if(NOT ( "${_RUSTC_VERSION_RESULT}" EQUAL "0" ))
|
||||
message(FATAL_ERROR "Failed to get rustc version.\n"
|
||||
"${Rust_COMPILER} --version failed with error: `${_RUSTC_VERSION_RESULT}`")
|
||||
endif()
|
||||
|
||||
if (_RUSTC_VERSION_RAW MATCHES "host: ([a-zA-Z0-9_\\-]*)\n")
|
||||
set(Rust_DEFAULT_HOST_TARGET "${CMAKE_MATCH_1}")
|
||||
else()
|
||||
message(FATAL_ERROR
|
||||
"Failed to parse rustc host target. `rustc --version --verbose` evaluated to:\n${_RUSTC_VERSION_RAW}"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
message(FATAL_ERROR "CMake is in cross-compiling mode."
|
||||
"Manually set `Rust_CARGO_TARGET`."
|
||||
)
|
||||
endif()
|
||||
set(Rust_CARGO_TARGET_CACHED "${Rust_DEFAULT_HOST_TARGET}" CACHE STRING "Target triple")
|
||||
endif()
|
||||
|
||||
# Set the input variables as non-cache variables so that the variables are available after
|
||||
|
||||
@@ -14,32 +14,36 @@ set(rel_completionsdir "fish/vendor_completions.d")
|
||||
set(rel_functionsdir "fish/vendor_functions.d")
|
||||
set(rel_confdir "fish/vendor_conf.d")
|
||||
|
||||
set(extra_completionsdir
|
||||
"${datadir}/${rel_completionsdir}"
|
||||
CACHE STRING "Path for extra completions")
|
||||
set(
|
||||
extra_completionsdir "${datadir}/${rel_completionsdir}"
|
||||
CACHE STRING "Path for extra completions"
|
||||
)
|
||||
|
||||
set(extra_functionsdir
|
||||
"${datadir}/${rel_functionsdir}"
|
||||
CACHE STRING "Path for extra functions")
|
||||
set(
|
||||
extra_functionsdir "${datadir}/${rel_functionsdir}"
|
||||
CACHE STRING "Path for extra functions"
|
||||
)
|
||||
|
||||
set(extra_confdir
|
||||
"${datadir}/${rel_confdir}"
|
||||
CACHE STRING "Path for extra configuration")
|
||||
set(
|
||||
extra_confdir "${datadir}/${rel_confdir}"
|
||||
CACHE STRING "Path for extra configuration"
|
||||
)
|
||||
|
||||
|
||||
# These are the man pages that go in system manpath; all manpages go in the fish-specific manpath.
|
||||
set(MANUALS ${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish.1
|
||||
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish_indent.1
|
||||
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish_key_reader.1
|
||||
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-doc.1
|
||||
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-tutorial.1
|
||||
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-language.1
|
||||
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-interactive.1
|
||||
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-terminal-compatibility.1
|
||||
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-completions.1
|
||||
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-prompt-tutorial.1
|
||||
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-for-bash-users.1
|
||||
${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish-faq.1
|
||||
set(MANUALS
|
||||
${SPHINX_OUTPUT_DIR}/man/man1/fish.1
|
||||
${SPHINX_OUTPUT_DIR}/man/man1/fish_indent.1
|
||||
${SPHINX_OUTPUT_DIR}/man/man1/fish_key_reader.1
|
||||
${SPHINX_OUTPUT_DIR}/man/man1/fish-doc.1
|
||||
${SPHINX_OUTPUT_DIR}/man/man1/fish-tutorial.1
|
||||
${SPHINX_OUTPUT_DIR}/man/man1/fish-language.1
|
||||
${SPHINX_OUTPUT_DIR}/man/man1/fish-interactive.1
|
||||
${SPHINX_OUTPUT_DIR}/man/man1/fish-terminal-compatibility.1
|
||||
${SPHINX_OUTPUT_DIR}/man/man1/fish-completions.1
|
||||
${SPHINX_OUTPUT_DIR}/man/man1/fish-prompt-tutorial.1
|
||||
${SPHINX_OUTPUT_DIR}/man/man1/fish-for-bash-users.1
|
||||
${SPHINX_OUTPUT_DIR}/man/man1/fish-faq.1
|
||||
)
|
||||
|
||||
# Determine which man page we don't want to install.
|
||||
@@ -48,40 +52,49 @@ set(MANUALS ${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/fish.1
|
||||
# On other operating systems, don't install a realpath man page, as they almost all have a realpath
|
||||
# command, while macOS does not.
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
set(CONDEMNED_PAGE "open.1")
|
||||
set(CONDEMNED_PAGE "open.1")
|
||||
else()
|
||||
set(CONDEMNED_PAGE "realpath.1")
|
||||
set(CONDEMNED_PAGE "realpath.1")
|
||||
endif()
|
||||
|
||||
# Define a function to help us create directories.
|
||||
function(FISH_CREATE_DIRS)
|
||||
foreach(dir ${ARGV})
|
||||
install(DIRECTORY DESTINATION ${dir})
|
||||
endforeach(dir)
|
||||
foreach(dir ${ARGV})
|
||||
install(DIRECTORY DESTINATION ${dir})
|
||||
endforeach(dir)
|
||||
endfunction(FISH_CREATE_DIRS)
|
||||
|
||||
function(FISH_TRY_CREATE_DIRS)
|
||||
foreach(dir ${ARGV})
|
||||
if(NOT IS_ABSOLUTE ${dir})
|
||||
set(abs_dir "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${dir}")
|
||||
else()
|
||||
set(abs_dir "\$ENV{DESTDIR}${dir}")
|
||||
endif()
|
||||
install(SCRIPT CODE "EXECUTE_PROCESS(COMMAND mkdir -p ${abs_dir} OUTPUT_QUIET ERROR_QUIET)
|
||||
execute_process(COMMAND chmod 755 ${abs_dir} OUTPUT_QUIET ERROR_QUIET)
|
||||
")
|
||||
endforeach()
|
||||
foreach(dir ${ARGV})
|
||||
if(NOT IS_ABSOLUTE ${dir})
|
||||
set(abs_dir "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${dir}")
|
||||
else()
|
||||
set(abs_dir "\$ENV{DESTDIR}${dir}")
|
||||
endif()
|
||||
install(SCRIPT CODE "
|
||||
EXECUTE_PROCESS(COMMAND mkdir -p ${abs_dir} OUTPUT_QUIET ERROR_QUIET)
|
||||
execute_process(COMMAND chmod 755 ${abs_dir} OUTPUT_QUIET ERROR_QUIET)
|
||||
")
|
||||
endforeach()
|
||||
endfunction(FISH_TRY_CREATE_DIRS)
|
||||
|
||||
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/fish
|
||||
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ
|
||||
GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
|
||||
DESTINATION ${bindir})
|
||||
install(
|
||||
PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/fish
|
||||
PERMISSIONS
|
||||
OWNER_READ
|
||||
OWNER_WRITE
|
||||
OWNER_EXECUTE
|
||||
GROUP_READ
|
||||
GROUP_EXECUTE
|
||||
WORLD_READ
|
||||
WORLD_EXECUTE
|
||||
DESTINATION ${bindir}
|
||||
)
|
||||
|
||||
if(NOT IS_ABSOLUTE ${bindir})
|
||||
set(abs_bindir "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${bindir}")
|
||||
set(abs_bindir "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${bindir}")
|
||||
else()
|
||||
set(abs_bindir "\$ENV{DESTDIR}${bindir}")
|
||||
set(abs_bindir "\$ENV{DESTDIR}${bindir}")
|
||||
endif()
|
||||
install(CODE "file(CREATE_LINK ${abs_bindir}/fish ${abs_bindir}/fish_indent)")
|
||||
install(CODE "file(CREATE_LINK ${abs_bindir}/fish ${abs_bindir}/fish_key_reader)")
|
||||
@@ -90,81 +103,116 @@ fish_create_dirs(${sysconfdir}/fish/conf.d ${sysconfdir}/fish/completions
|
||||
${sysconfdir}/fish/functions)
|
||||
install(FILES etc/config.fish DESTINATION ${sysconfdir}/fish/)
|
||||
|
||||
fish_create_dirs(${rel_datadir}/fish ${rel_datadir}/fish/completions
|
||||
${rel_datadir}/fish/functions
|
||||
${rel_datadir}/fish/man/man1 ${rel_datadir}/fish/tools
|
||||
${rel_datadir}/fish/tools/web_config
|
||||
${rel_datadir}/fish/tools/web_config/js
|
||||
${rel_datadir}/fish/prompts
|
||||
${rel_datadir}/fish/themes
|
||||
)
|
||||
fish_create_dirs(
|
||||
${rel_datadir}/fish ${rel_datadir}/fish/completions
|
||||
${rel_datadir}/fish/functions
|
||||
${rel_datadir}/fish/man/man1 ${rel_datadir}/fish/tools
|
||||
${rel_datadir}/fish/tools/web_config
|
||||
${rel_datadir}/fish/tools/web_config/js
|
||||
${rel_datadir}/fish/prompts
|
||||
${rel_datadir}/fish/themes
|
||||
)
|
||||
|
||||
configure_file(share/__fish_build_paths.fish.in share/__fish_build_paths.fish)
|
||||
install(FILES share/config.fish
|
||||
${CMAKE_CURRENT_BINARY_DIR}/share/__fish_build_paths.fish
|
||||
DESTINATION ${rel_datadir}/fish)
|
||||
${CMAKE_CURRENT_BINARY_DIR}/share/__fish_build_paths.fish
|
||||
DESTINATION ${rel_datadir}/fish
|
||||
)
|
||||
|
||||
# Create only the vendor directories inside the prefix (#5029 / #6508)
|
||||
fish_create_dirs(${rel_datadir}/fish/vendor_completions.d ${rel_datadir}/fish/vendor_functions.d
|
||||
${rel_datadir}/fish/vendor_conf.d)
|
||||
fish_create_dirs(
|
||||
${rel_datadir}/fish/vendor_completions.d
|
||||
${rel_datadir}/fish/vendor_functions.d
|
||||
${rel_datadir}/fish/vendor_conf.d
|
||||
)
|
||||
|
||||
fish_try_create_dirs(${rel_datadir}/pkgconfig)
|
||||
configure_file(fish.pc.in fish.pc.noversion @ONLY)
|
||||
|
||||
add_custom_command(OUTPUT fish.pc
|
||||
add_custom_command(
|
||||
OUTPUT fish.pc
|
||||
COMMAND sed '/Version/d' fish.pc.noversion > fish.pc
|
||||
COMMAND printf "Version: " >> fish.pc
|
||||
COMMAND cat ${FBVF} >> fish.pc
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh >> fish.pc
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
DEPENDS CHECK-FISH-BUILD-VERSION-FILE ${CMAKE_CURRENT_BINARY_DIR}/fish.pc.noversion)
|
||||
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/fish.pc.noversion
|
||||
)
|
||||
|
||||
add_custom_target(build_fish_pc ALL DEPENDS fish.pc)
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/fish.pc
|
||||
DESTINATION ${rel_datadir}/pkgconfig)
|
||||
install(
|
||||
FILES ${CMAKE_CURRENT_BINARY_DIR}/fish.pc
|
||||
DESTINATION ${rel_datadir}/pkgconfig
|
||||
)
|
||||
|
||||
install(DIRECTORY share/completions/
|
||||
DESTINATION ${rel_datadir}/fish/completions
|
||||
FILES_MATCHING PATTERN "*.fish")
|
||||
install(
|
||||
DIRECTORY share/completions/
|
||||
DESTINATION ${rel_datadir}/fish/completions
|
||||
FILES_MATCHING PATTERN "*.fish"
|
||||
)
|
||||
|
||||
install(DIRECTORY share/functions/
|
||||
DESTINATION ${rel_datadir}/fish/functions
|
||||
FILES_MATCHING PATTERN "*.fish")
|
||||
install(
|
||||
DIRECTORY share/functions/
|
||||
DESTINATION ${rel_datadir}/fish/functions
|
||||
FILES_MATCHING PATTERN "*.fish"
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY share/prompts/
|
||||
DESTINATION ${rel_datadir}/fish/prompts
|
||||
FILES_MATCHING PATTERN "*.fish"
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY share/themes/
|
||||
DESTINATION ${rel_datadir}/fish/themes
|
||||
FILES_MATCHING PATTERN "*.theme"
|
||||
)
|
||||
|
||||
# CONDEMNED_PAGE is managed by the conditional above
|
||||
# Building the man pages is optional: if sphinx isn't installed, they're not built
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/user_doc/man/man1/
|
||||
DESTINATION ${rel_datadir}/fish/man/man1
|
||||
FILES_MATCHING
|
||||
PATTERN "*.1"
|
||||
PATTERN ${CONDEMNED_PAGE} EXCLUDE)
|
||||
install(
|
||||
DIRECTORY ${SPHINX_OUTPUT_DIR}/man/man1/
|
||||
DESTINATION ${rel_datadir}/fish/man/man1
|
||||
FILES_MATCHING
|
||||
PATTERN "*.1"
|
||||
PATTERN ${CONDEMNED_PAGE} EXCLUDE
|
||||
)
|
||||
|
||||
install(PROGRAMS share/tools/create_manpage_completions.py
|
||||
DESTINATION ${rel_datadir}/fish/tools/)
|
||||
install(
|
||||
PROGRAMS share/tools/create_manpage_completions.py
|
||||
DESTINATION ${rel_datadir}/fish/tools/
|
||||
)
|
||||
|
||||
install(DIRECTORY share/tools/web_config
|
||||
DESTINATION ${rel_datadir}/fish/tools/
|
||||
FILES_MATCHING
|
||||
PATTERN "*.png"
|
||||
PATTERN "*.css"
|
||||
PATTERN "*.html"
|
||||
PATTERN "*.py"
|
||||
PATTERN "*.js"
|
||||
PATTERN "*.theme"
|
||||
PATTERN "*.fish")
|
||||
install(
|
||||
DIRECTORY share/tools/web_config
|
||||
DESTINATION ${rel_datadir}/fish/tools/
|
||||
FILES_MATCHING
|
||||
PATTERN "*.png"
|
||||
PATTERN "*.css"
|
||||
PATTERN "*.html"
|
||||
PATTERN "*.py"
|
||||
PATTERN "*.js"
|
||||
)
|
||||
|
||||
# Building the man pages is optional: if Sphinx isn't installed, they're not built
|
||||
install(FILES ${MANUALS} DESTINATION ${mandir}/man1/ OPTIONAL)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/user_doc/html/ # Trailing slash is important!
|
||||
DESTINATION ${docdir} OPTIONAL)
|
||||
install(
|
||||
DIRECTORY ${SPHINX_OUTPUT_DIR}/html/ # Trailing slash is important!
|
||||
DESTINATION ${docdir} OPTIONAL
|
||||
)
|
||||
install(FILES CHANGELOG.rst DESTINATION ${docdir})
|
||||
|
||||
# Group install targets into a InstallTargets folder
|
||||
set_property(TARGET build_fish_pc CHECK-FISH-BUILD-VERSION-FILE
|
||||
PROPERTY FOLDER cmake/InstallTargets)
|
||||
set_property(
|
||||
TARGET build_fish_pc
|
||||
PROPERTY FOLDER cmake/InstallTargets
|
||||
)
|
||||
|
||||
# Make a target build_root that installs into the buildroot directory, for testing.
|
||||
set(BUILDROOT_DIR ${CMAKE_CURRENT_BINARY_DIR}/buildroot)
|
||||
add_custom_target(build_root
|
||||
COMMAND DESTDIR=${BUILDROOT_DIR} ${CMAKE_COMMAND}
|
||||
--build ${CMAKE_CURRENT_BINARY_DIR} --target install)
|
||||
add_custom_target(
|
||||
build_root
|
||||
COMMAND DESTDIR=${BUILDROOT_DIR} ${CMAKE_COMMAND}
|
||||
--build ${CMAKE_CURRENT_BINARY_DIR} --target install
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ add_executable(fish_macapp EXCLUDE_FROM_ALL
|
||||
# so cmake must be re-run after version changes for the app to be updated. But
|
||||
# generally this will be run by make_macos_pkg.sh which always re-runs cmake.
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh --stdout
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh
|
||||
COMMAND cut -d- -f1
|
||||
OUTPUT_VARIABLE FISH_SHORT_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
set(FISH_USE_SYSTEM_PCRE2 ON CACHE BOOL
|
||||
"Try to use PCRE2 from the system, instead of the pcre2-sys version")
|
||||
"Try to use PCRE2 from the system, instead of the pcre2-sys version"
|
||||
)
|
||||
|
||||
if(FISH_USE_SYSTEM_PCRE2)
|
||||
message(STATUS "Trying to use PCRE2 from the system")
|
||||
message(STATUS "Trying to use PCRE2 from the system")
|
||||
else()
|
||||
message(STATUS "Forcing static build of PCRE2")
|
||||
set(FISH_PCRE2_BUILDFLAG "PCRE2_SYS_STATIC=1")
|
||||
message(STATUS "Forcing static build of PCRE2")
|
||||
set(FISH_PCRE2_BUILDFLAG "PCRE2_SYS_STATIC=1")
|
||||
endif(FISH_USE_SYSTEM_PCRE2)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
include(FeatureSummary)
|
||||
include(FindRust)
|
||||
find_package(Rust REQUIRED)
|
||||
|
||||
set(FISH_RUST_BUILD_DIR "${CMAKE_BINARY_DIR}/cargo/build")
|
||||
set(FISH_RUST_BUILD_DIR "${CMAKE_BINARY_DIR}/cargo")
|
||||
|
||||
if(DEFINED ASAN)
|
||||
list(APPEND CARGO_FLAGS "-Z" "build-std")
|
||||
list(APPEND FISH_CARGO_FEATURES_LIST "asan")
|
||||
endif()
|
||||
if(DEFINED TSAN)
|
||||
list(APPEND CARGO_FLAGS "-Z" "build-std")
|
||||
@@ -20,10 +20,17 @@ endif()
|
||||
|
||||
set(rust_profile $<IF:$<CONFIG:Debug>,debug,$<IF:$<CONFIG:RelWithDebInfo>,release-with-debug,release>>)
|
||||
|
||||
option(WITH_GETTEXT "Build with gettext localization support. Requires `msgfmt` to work." ON)
|
||||
if (NOT DEFINED WITH_MESSAGE_LOCALIZATION) # Don't check for legacy options if the new one is defined, to help bisecting.
|
||||
if(DEFINED WITH_GETTEXT)
|
||||
message(FATAL_ERROR "the WITH_GETTEXT option is no longer supported, use -DWITH_MESSAGE_LOCALIZATION=ON|OFF")
|
||||
endif()
|
||||
endif()
|
||||
option(WITH_MESSAGE_LOCALIZATION "Build with localization support. Requires `msgfmt` to work." ON)
|
||||
# Enable gettext feature unless explicitly disabled.
|
||||
if(NOT DEFINED WITH_GETTEXT OR "${WITH_GETTEXT}")
|
||||
if(NOT DEFINED WITH_MESSAGE_LOCALIZATION OR "${WITH_MESSAGE_LOCALIZATION}")
|
||||
list(APPEND FISH_CARGO_FEATURES_LIST "localize-messages")
|
||||
endif()
|
||||
|
||||
add_feature_info(Translation WITH_MESSAGE_LOCALIZATION "message localization (requires gettext)")
|
||||
|
||||
list(JOIN FISH_CARGO_FEATURES_LIST , FISH_CARGO_FEATURES)
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
add_executable(fish_test_helper tests/fish_test_helper.c)
|
||||
|
||||
FILE(GLOB FISH_CHECKS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/tests/checks/*.fish)
|
||||
foreach(CHECK ${FISH_CHECKS})
|
||||
get_filename_component(CHECK_NAME ${CHECK} NAME)
|
||||
add_custom_target(
|
||||
test_${CHECK_NAME}
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${CMAKE_CURRENT_BINARY_DIR}
|
||||
checks/${CHECK_NAME}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
|
||||
DEPENDS fish fish_indent fish_key_reader fish_test_helper
|
||||
USES_TERMINAL
|
||||
)
|
||||
get_filename_component(CHECK_NAME ${CHECK} NAME)
|
||||
add_custom_target(
|
||||
test_${CHECK_NAME}
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${CMAKE_CURRENT_BINARY_DIR}
|
||||
checks/${CHECK_NAME}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
|
||||
DEPENDS fish fish_indent fish_key_reader
|
||||
USES_TERMINAL
|
||||
)
|
||||
endforeach(CHECK)
|
||||
|
||||
FILE(GLOB PEXPECTS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/tests/pexpects/*.py)
|
||||
foreach(PEXPECT ${PEXPECTS})
|
||||
get_filename_component(PEXPECT ${PEXPECT} NAME)
|
||||
add_custom_target(
|
||||
test_${PEXPECT}
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${CMAKE_CURRENT_BINARY_DIR}
|
||||
pexpects/${PEXPECT}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
|
||||
DEPENDS fish fish_indent fish_key_reader fish_test_helper
|
||||
USES_TERMINAL
|
||||
)
|
||||
get_filename_component(PEXPECT ${PEXPECT} NAME)
|
||||
add_custom_target(
|
||||
test_${PEXPECT}
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${CMAKE_CURRENT_BINARY_DIR}
|
||||
pexpects/${PEXPECT}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
|
||||
DEPENDS fish fish_indent fish_key_reader
|
||||
USES_TERMINAL
|
||||
)
|
||||
endforeach(PEXPECT)
|
||||
|
||||
# Rust stuff.
|
||||
@@ -32,14 +30,20 @@ if(DEFINED ASAN)
|
||||
# Rust w/ -Zsanitizer=address requires explicitly specifying the --target triple or else linker
|
||||
# errors pertaining to asan symbols will ensue.
|
||||
if(NOT DEFINED Rust_CARGO_TARGET)
|
||||
message(FATAL_ERROR "ASAN requires defining the CMake variable Rust_CARGO_TARGET to the
|
||||
intended target triple")
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"ASAN requires defining the CMake variable Rust_CARGO_TARGET to the
|
||||
intended target triple"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
if(DEFINED TSAN)
|
||||
if(NOT DEFINED Rust_CARGO_TARGET)
|
||||
message(FATAL_ERROR "TSAN requires defining the CMake variable Rust_CARGO_TARGET to the
|
||||
intended target triple")
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"TSAN requires defining the CMake variable Rust_CARGO_TARGET to the
|
||||
intended target triple"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -55,10 +59,10 @@ endif()
|
||||
|
||||
# The top-level test target is "fish_run_tests".
|
||||
add_custom_target(fish_run_tests
|
||||
# TODO: This should be replaced with a unified solution, possibly build_tools/check.sh.
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${max_concurrency_flag} ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND env ${VARS_FOR_CARGO}
|
||||
${Rust_CARGO}
|
||||
# TODO: This should be replaced with a unified solution, possibly build_tools/check.sh.
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/tests/test_driver.py ${max_concurrency_flag} ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND
|
||||
env ${VARS_FOR_CARGO} ${Rust_CARGO}
|
||||
test
|
||||
--no-default-features
|
||||
--features=${FISH_CARGO_FEATURES}
|
||||
@@ -66,7 +70,7 @@ add_custom_target(fish_run_tests
|
||||
--workspace
|
||||
--target-dir ${rust_target_dir}
|
||||
${cargo_test_flags}
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
DEPENDS fish fish_indent fish_key_reader fish_test_helper
|
||||
USES_TERMINAL
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
DEPENDS fish fish_indent fish_key_reader
|
||||
USES_TERMINAL
|
||||
)
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
# This file adds commands to manage the FISH-BUILD-VERSION-FILE (hereafter
|
||||
# FBVF). This file exists in the build directory and is used to populate the
|
||||
# documentation and also the version string in fish_version.o (printed with
|
||||
# `echo $version` and also fish --version). The essential idea is that we are
|
||||
# going to invoke git_version_gen.sh, which will update the
|
||||
# FISH-BUILD-VERSION-FILE only if it needs to change; this is what makes
|
||||
# incremental rebuilds fast.
|
||||
#
|
||||
# This code is delicate, with the chief subtlety revolving around Ninja. A
|
||||
# natural and naive approach would tell the generated build system that FBVF is
|
||||
# a dependency of fish_version.o, and that git_version_gen.sh updates it. Make
|
||||
# will then invoke the script, check the timestamp on fish_version.o and FBVF,
|
||||
# see that FBVF is earlier, and then not rebuild fish_version.o. Ninja,
|
||||
# however, decides what to build up-front and will unconditionally rebuild
|
||||
# fish_version.o.
|
||||
#
|
||||
# To avoid this with Ninja, we want to hook into its 'restat' option which we
|
||||
# can do through the BYPRODUCTS feature of CMake. See
|
||||
# https://cmake.org/cmake/help/latest/policy/CMP0058.html
|
||||
#
|
||||
# Unfortunately BYPRODUCTS behaves strangely with the Makefile generator: it
|
||||
# marks FBVF as generated and then CMake itself will `touch` it on every build,
|
||||
# meaning that using BYPRODUCTS will cause fish_version.o to be rebuilt
|
||||
# unconditionally with the Makefile generator. Thus we want to use the
|
||||
# natural-and-naive approach for Makefiles.
|
||||
|
||||
# **IMPORTANT** If you touch these build rules, please test both Ninja and
|
||||
# Makefile generators with both a clean and dirty git tree. Verify that both
|
||||
# generated build systems rebuild fish when the git tree goes from dirty to
|
||||
# clean (and vice versa), and verify they do NOT rebuild it when the git tree
|
||||
# stays the same (incremental builds must be fast).
|
||||
|
||||
# Just a handy abbreviation.
|
||||
set(FBVF FISH-BUILD-VERSION-FILE)
|
||||
|
||||
# TODO: find a cleaner way to do this.
|
||||
IF (${CMAKE_GENERATOR} STREQUAL Ninja)
|
||||
set(FBVF-OUTPUT fish-build-version-witness.txt)
|
||||
set(CFBVF-BYPRODUCTS ${FBVF})
|
||||
else(${CMAKE_GENERATOR} STREQUAL Ninja)
|
||||
set(FBVF-OUTPUT ${FBVF})
|
||||
set(CFBVF-BYPRODUCTS)
|
||||
endif(${CMAKE_GENERATOR} STREQUAL Ninja)
|
||||
|
||||
# Set up the version targets
|
||||
add_custom_target(CHECK-FISH-BUILD-VERSION-FILE
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build_tools/git_version_gen.sh ${CMAKE_CURRENT_BINARY_DIR}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
BYPRODUCTS ${CFBVF-BYPRODUCTS})
|
||||
|
||||
add_custom_command(OUTPUT ${FBVF-OUTPUT}
|
||||
DEPENDS CHECK-FISH-BUILD-VERSION-FILE)
|
||||
|
||||
# Abbreviation for the target.
|
||||
set(CFBVF CHECK-FISH-BUILD-VERSION-FILE)
|
||||
@@ -1,3 +1,59 @@
|
||||
fish (4.7.1-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.7.1.
|
||||
|
||||
See https://github.com/fish-shell/fish-shell/releases/tag/4.7.1 for details.
|
||||
|
||||
-- Johannes Altmanninger <aclopte@gmail.com> Fri, 08 May 2026 00:02:14 +0800
|
||||
|
||||
fish (4.7.0-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.7.0.
|
||||
|
||||
See https://github.com/fish-shell/fish-shell/releases/tag/4.7.0 for details.
|
||||
|
||||
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 05 May 2026 15:24:27 +0800
|
||||
|
||||
fish (4.6.0-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.6.0.
|
||||
|
||||
See https://github.com/fish-shell/fish-shell/releases/tag/4.6.0 for details.
|
||||
|
||||
-- Johannes Altmanninger <aclopte@gmail.com> Sat, 28 Mar 2026 12:56:37 +0800
|
||||
|
||||
fish (4.5.0-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.5.0.
|
||||
|
||||
See https://github.com/fish-shell/fish-shell/releases/tag/4.5.0 for details.
|
||||
|
||||
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 17 Feb 2026 11:32:33 +1100
|
||||
|
||||
fish (4.4.0-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.4.0.
|
||||
|
||||
See https://github.com/fish-shell/fish-shell/releases/tag/4.4.0 for details.
|
||||
|
||||
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 03 Feb 2026 12:11:51 +1100
|
||||
|
||||
fish (4.3.3-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.3.3.
|
||||
|
||||
See https://github.com/fish-shell/fish-shell/releases/tag/4.3.3 for details.
|
||||
|
||||
-- Johannes Altmanninger <aclopte@gmail.com> Wed, 07 Jan 2026 08:34:20 +0100
|
||||
|
||||
fish (4.3.2-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.3.2.
|
||||
|
||||
See https://github.com/fish-shell/fish-shell/releases/tag/4.3.2 for details.
|
||||
|
||||
-- Johannes Altmanninger <aclopte@gmail.com> Tue, 30 Dec 2025 17:21:04 +0100
|
||||
|
||||
fish (4.3.1-1) stable; urgency=medium
|
||||
|
||||
* Release of new version 4.3.1.
|
||||
|
||||
@@ -10,10 +10,12 @@ Build-Depends: debhelper-compat (= 13),
|
||||
gettext,
|
||||
libpcre2-dev,
|
||||
rustc (>= 1.85) | rustc-web (>= 1.85) | rustc-1.85,
|
||||
sphinx-doc,
|
||||
# pkg-config is needed for the pcre2 crate to find the pcre2 system library
|
||||
pkgconf | pkg-config,
|
||||
python3-sphinx,
|
||||
# Test dependencies
|
||||
locales-all,
|
||||
man-db,
|
||||
man-db | man,
|
||||
python3
|
||||
# 4.6.2 is Debian 12/Ubuntu Noble 24.04; Ubuntu Jammy is 4.6.0.1
|
||||
Standards-Version: 4.6.2
|
||||
@@ -26,8 +28,8 @@ Architecture: any
|
||||
# for col and lock
|
||||
Depends: bsdextrautils,
|
||||
file,
|
||||
# for man
|
||||
man-db,
|
||||
# for showing built-in help pages
|
||||
man-db | man,
|
||||
# for kill
|
||||
procps,
|
||||
python3 (>=3.5),
|
||||
|
||||
@@ -34,7 +34,7 @@ Copyright: 1990-2007 Free Software Foundation, Inc.
|
||||
2022 fish-shell contributors
|
||||
License: GPL-2+
|
||||
|
||||
Files: src/wgetopt.rs
|
||||
Files: crates/wgetopt/src/wgetopt.rs
|
||||
Copyright: 1989-1994 Free Software Foundation, Inc.
|
||||
License: LGPL-2+
|
||||
|
||||
|
||||
@@ -16,14 +16,12 @@ export DEB_BUILD_MAINT_OPTIONS=optimize=-lto
|
||||
# Setting the build system is still required, because otherwise the GNUmakefile gets picked up
|
||||
override_dh_auto_configure:
|
||||
ln -s cargo-vendor/vendor vendor
|
||||
ln -s cargo-vendor/.cargo .cargo
|
||||
dh_auto_configure -- -DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DRust_CARGO=$$(command -v cargo-1.85 || command -v cargo) \
|
||||
-DRust_COMPILER=$$(command -v rustc-1.85 || command -v rustc)
|
||||
|
||||
override_dh_clean:
|
||||
dh_clean --exclude=Cargo.toml.orig
|
||||
-unlink .cargo
|
||||
-unlink vendor
|
||||
|
||||
override_dh_auto_test:
|
||||
|
||||
3
contrib/debian/source/local-options
Normal file
3
contrib/debian/source/local-options
Normal file
@@ -0,0 +1,3 @@
|
||||
# The vendor tarball drops a new version of .cargo/config into place. Representing this as a patch
|
||||
# in automated workflows is tricky, so for our purposes auto-commit is fine.
|
||||
auto-commit
|
||||
33
contrib/shell.nix
Normal file
33
contrib/shell.nix
Normal file
@@ -0,0 +1,33 @@
|
||||
# Environment containing all dependencies needed for
|
||||
# - building fish,
|
||||
# - building documentation,
|
||||
# - running all tests,
|
||||
# - formatting and checking lints.
|
||||
#
|
||||
# enter interactive bash shell:
|
||||
# nix-shell contrib/shell.nix
|
||||
#
|
||||
# using system nixpkgs (otherwise fetches pinned version):
|
||||
# nix-shell contrib/shell.nix --arg pkgs 'import <nixpkgs> {}'
|
||||
#
|
||||
# run single command:
|
||||
# nix-shell contrib/shell --run "cargo xtask check"
|
||||
{ pkgs ? (import (builtins.fetchTarball {
|
||||
url = "https://github.com/NixOS/nixpkgs/archive/nixos-25.11.tar.gz";
|
||||
sha256 = "1ia5kjykm9xmrpwbzhbaf4cpwi3yaxr7shl6amj8dajvgbyh2yh4";
|
||||
}) { }), ... }:
|
||||
pkgs.mkShell {
|
||||
buildInputs = [
|
||||
(pkgs.python3.withPackages (pyPkgs: [ pyPkgs.pexpect ]))
|
||||
pkgs.cargo
|
||||
pkgs.clippy
|
||||
pkgs.cmake
|
||||
pkgs.gettext
|
||||
pkgs.pcre2
|
||||
pkgs.procps # tests use pgrep/pkill
|
||||
pkgs.ruff
|
||||
pkgs.rustc
|
||||
pkgs.rustfmt
|
||||
pkgs.sphinx
|
||||
];
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{borrow::Cow, env, os::unix::ffi::OsStrExt, path::Path};
|
||||
use std::{borrow::Cow, env, os::unix::ffi::OsStrExt as _, path::Path};
|
||||
|
||||
pub fn env_var(name: &str) -> Option<String> {
|
||||
let err = match env::var(name) {
|
||||
@@ -34,6 +34,18 @@ pub fn fish_build_dir() -> Cow<'static, Path> {
|
||||
.unwrap_or(cargo_target_dir())
|
||||
}
|
||||
|
||||
pub fn fish_doc_dir() -> Cow<'static, Path> {
|
||||
cargo_target_dir().join("fish-docs").into()
|
||||
}
|
||||
|
||||
fn l10n_dir() -> Cow<'static, Path> {
|
||||
workspace_root().join("localization").into()
|
||||
}
|
||||
|
||||
pub fn po_dir() -> Cow<'static, Path> {
|
||||
l10n_dir().join("po").into()
|
||||
}
|
||||
|
||||
// TODO Move this to rsconf
|
||||
pub fn rebuild_if_path_changed<P: AsRef<Path>>(path: P) {
|
||||
rsconf::rebuild_if_path_changed(path.as_ref().to_str().unwrap());
|
||||
@@ -83,3 +95,18 @@ pub fn target_os_is_bsd() -> bool {
|
||||
pub fn target_os_is_cygwin() -> bool {
|
||||
target_os() == "cygwin"
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! as_os_strs {
|
||||
[ $( $x:expr ),* $(,)? ] => {
|
||||
{
|
||||
use std::ffi::OsStr;
|
||||
fn as_os_str<S: AsRef<OsStr> + ?Sized>(s: &S) -> &OsStr {
|
||||
s.as_ref()
|
||||
}
|
||||
[
|
||||
$( as_os_str($x) ),*
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
use std::path::Path;
|
||||
|
||||
use fish_build_helper::{as_os_strs, fish_doc_dir};
|
||||
|
||||
fn main() {
|
||||
let man_dir = fish_build_helper::fish_build_dir().join("fish-man");
|
||||
let sec1_dir = man_dir.join("man1");
|
||||
let sec1_dir = fish_doc_dir().join("man").join("man1");
|
||||
// Running `cargo clippy` on a clean build directory panics, because when rust-embed
|
||||
// tries to embed a directory which does not exist it will panic.
|
||||
let _ = std::fs::create_dir_all(&sec1_dir);
|
||||
if !cfg!(clippy) {
|
||||
build_man(&man_dir, &sec1_dir);
|
||||
build_man(&sec1_dir);
|
||||
}
|
||||
}
|
||||
|
||||
fn build_man(man_dir: &Path, sec1_dir: &Path) {
|
||||
fn build_man(sec1_dir: &Path) {
|
||||
use fish_build_helper::{env_var, workspace_root};
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
let workspace_root = workspace_root();
|
||||
let doc_src_dir = workspace_root.join("doc_src");
|
||||
let doctrees_dir = fish_doc_dir().join(".doctrees-man");
|
||||
|
||||
fish_build_helper::rebuild_if_paths_changed([
|
||||
&workspace_root.join("CHANGELOG.rst"),
|
||||
@@ -27,36 +26,24 @@ fn build_man(man_dir: &Path, sec1_dir: &Path) {
|
||||
&doc_src_dir,
|
||||
]);
|
||||
|
||||
let args: &[&OsStr] = {
|
||||
fn as_os_str<S: AsRef<OsStr> + ?Sized>(s: &S) -> &OsStr {
|
||||
s.as_ref()
|
||||
}
|
||||
macro_rules! as_os_strs {
|
||||
( [ $( $x:expr, )* ] ) => {
|
||||
&[
|
||||
$( as_os_str($x), )*
|
||||
]
|
||||
}
|
||||
}
|
||||
as_os_strs!([
|
||||
"-j",
|
||||
"auto",
|
||||
"-q",
|
||||
"-b",
|
||||
"man",
|
||||
"-c",
|
||||
&doc_src_dir,
|
||||
// doctree path - put this *above* the man1 dir to exclude it.
|
||||
// this is ~6M
|
||||
"-d",
|
||||
&man_dir,
|
||||
&doc_src_dir,
|
||||
&sec1_dir,
|
||||
])
|
||||
};
|
||||
let args = as_os_strs![
|
||||
"-j",
|
||||
"auto",
|
||||
"-q",
|
||||
"-b",
|
||||
"man",
|
||||
"-c",
|
||||
&doc_src_dir,
|
||||
// doctree path - put this *above* the man1 dir to exclude it.
|
||||
// this is ~6M
|
||||
"-d",
|
||||
&doctrees_dir,
|
||||
&doc_src_dir,
|
||||
&sec1_dir,
|
||||
];
|
||||
|
||||
rsconf::rebuild_if_env_changed("FISH_BUILD_DOCS");
|
||||
if env_var("FISH_BUILD_DOCS") == Some("0".to_string()) {
|
||||
if env_var("FISH_BUILD_DOCS") == Some("0".to_owned()) {
|
||||
rsconf::warn!("Skipping man pages because $FISH_BUILD_DOCS is set to 0");
|
||||
return;
|
||||
}
|
||||
@@ -73,12 +60,12 @@ macro_rules! as_os_strs {
|
||||
.spawn()
|
||||
{
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
if env_var("FISH_BUILD_DOCS") == Some("1".to_string()) {
|
||||
panic!(
|
||||
"Could not find sphinx-build required to build man pages.\n\
|
||||
Install Sphinx or disable building the docs by setting $FISH_BUILD_DOCS=0."
|
||||
);
|
||||
}
|
||||
assert_ne!(
|
||||
env_var("FISH_BUILD_DOCS"),
|
||||
Some("1".to_owned()),
|
||||
"Could not find sphinx-build required to build man pages.\n\
|
||||
Install Sphinx or disable building the docs by setting $FISH_BUILD_DOCS=0."
|
||||
);
|
||||
rsconf::warn!(
|
||||
"Could not find sphinx-build required to build man pages. \
|
||||
If you install Sphinx now, you need to trigger a rebuild to include man pages. \
|
||||
@@ -105,9 +92,10 @@ macro_rules! as_os_strs {
|
||||
rsconf::warn!("sphinx-build: {}", String::from_utf8_lossy(&out.stderr));
|
||||
}
|
||||
assert_eq!(&String::from_utf8_lossy(&out.stdout), "");
|
||||
if !out.status.success() {
|
||||
panic!("sphinx-build failed to build the man pages.");
|
||||
}
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"sphinx-build failed to build the man pages."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "fish-wchar"
|
||||
name = "fish-color"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
@@ -8,7 +8,7 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fish-common.workspace = true
|
||||
widestring.workspace = true
|
||||
fish-widestring.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::prelude::*;
|
||||
use fish_common::assert_sorted_by_name;
|
||||
use fish_widestring::{L, wstr};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct Color24 {
|
||||
@@ -321,8 +322,8 @@ fn term256_color_for_rgb(color: Color24) -> u8 {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::color::{Color, Color24};
|
||||
use crate::prelude::*;
|
||||
use super::{Color, Color24};
|
||||
use fish_widestring::L;
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
@@ -342,9 +343,18 @@ fn parse() {
|
||||
#[test]
|
||||
fn parse_rgb() {
|
||||
assert!(Color::from_wstr(L!("##FF00A0")).is_none());
|
||||
assert!(Color::from_wstr(L!("#FF00A0")) == Some(Color::from_rgb(0xff, 0x00, 0xa0)));
|
||||
assert!(Color::from_wstr(L!("FF00A0")) == Some(Color::from_rgb(0xff, 0x00, 0xa0)));
|
||||
assert!(Color::from_wstr(L!("FAF")) == Some(Color::from_rgb(0xff, 0xaa, 0xff)));
|
||||
assert_eq!(
|
||||
Color::from_wstr(L!("#FF00A0")),
|
||||
Some(Color::from_rgb(0xff, 0x00, 0xa0))
|
||||
);
|
||||
assert_eq!(
|
||||
Color::from_wstr(L!("FF00A0")),
|
||||
Some(Color::from_rgb(0xff, 0x00, 0xa0))
|
||||
);
|
||||
assert_eq!(
|
||||
Color::from_wstr(L!("FAF")),
|
||||
Some(Color::from_rgb(0xff, 0xaa, 0xff))
|
||||
);
|
||||
}
|
||||
|
||||
// Regression test for multiplicative overflow in convert_color.
|
||||
@@ -7,9 +7,15 @@ repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
fish-feature-flags.workspace = true
|
||||
fish-widestring.workspace = true
|
||||
libc.workspace = true
|
||||
nix.workspace = true
|
||||
once_cell.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
fish-build-helper.workspace = true
|
||||
rsconf.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
5
crates/common/build.rs
Normal file
5
crates/common/build.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use fish_build_helper::target_os_is_apple;
|
||||
|
||||
fn main() {
|
||||
rsconf::declare_cfg("apple", target_os_is_apple());
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,10 +8,9 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fish-common.workspace = true
|
||||
fish-wchar.workspace = true
|
||||
fish-widecharwidth.workspace = true
|
||||
fish-widestring.workspace = true
|
||||
libc.workspace = true
|
||||
once_cell.workspace = true
|
||||
widestring.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use fish_build_helper::target_os_is_cygwin;
|
||||
|
||||
fn main() {
|
||||
rsconf::declare_cfg("cygwin", target_os_is_cygwin())
|
||||
rsconf::declare_cfg("cygwin", target_os_is_cygwin());
|
||||
}
|
||||
|
||||
@@ -3,15 +3,17 @@
|
||||
//!
|
||||
//! Many of these functions are more or less broken and incomplete.
|
||||
|
||||
use fish_wchar::prelude::*;
|
||||
use fish_widecharwidth::{WcLookupTable, WcWidth};
|
||||
use once_cell::sync::Lazy;
|
||||
use fish_widestring::prelude::*;
|
||||
use std::cmp;
|
||||
use std::sync::atomic::{AtomicIsize, Ordering};
|
||||
use std::sync::{
|
||||
LazyLock,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
/// Width of ambiguous East Asian characters and, as of TR11, all private-use characters.
|
||||
/// 1 is the typical default, but we accept any non-negative override via `$fish_ambiguous_width`.
|
||||
pub static FISH_AMBIGUOUS_WIDTH: AtomicIsize = AtomicIsize::new(1);
|
||||
pub static FISH_AMBIGUOUS_WIDTH: AtomicUsize = AtomicUsize::new(1);
|
||||
|
||||
/// Width of emoji characters.
|
||||
///
|
||||
@@ -23,65 +25,33 @@
|
||||
/// Valid values are 1, and 2. 1 is the typical emoji width used in Unicode 8 while some newer
|
||||
/// terminals use a width of 2 since Unicode 9.
|
||||
// For some reason, this is declared here and exposed here, but is set in `env_dispatch`.
|
||||
pub static FISH_EMOJI_WIDTH: AtomicIsize = AtomicIsize::new(1);
|
||||
pub static FISH_EMOJI_WIDTH: AtomicUsize = AtomicUsize::new(2);
|
||||
|
||||
static WC_LOOKUP_TABLE: Lazy<WcLookupTable> = Lazy::new(WcLookupTable::new);
|
||||
|
||||
/// A safe wrapper around the system `wcwidth()` function
|
||||
#[cfg(not(cygwin))]
|
||||
pub fn wcwidth(c: char) -> isize {
|
||||
unsafe extern "C" {
|
||||
pub unsafe fn wcwidth(c: libc::wchar_t) -> libc::c_int;
|
||||
}
|
||||
|
||||
const _: () = assert!(std::mem::size_of::<libc::wchar_t>() >= std::mem::size_of::<char>());
|
||||
let width = unsafe { wcwidth(c as libc::wchar_t) };
|
||||
isize::try_from(width).unwrap()
|
||||
}
|
||||
|
||||
// Big hack to use our versions of wcswidth where we know them to be broken, which is
|
||||
// EVERYWHERE (https://github.com/fish-shell/fish-shell/issues/2199)
|
||||
pub fn fish_wcwidth(c: char) -> isize {
|
||||
// The system version of wcwidth should accurately reflect the ability to represent characters
|
||||
// in the console session, but knows nothing about the capabilities of other terminal emulators
|
||||
// or ttys. Use it from the start only if we are logged in to the physical console.
|
||||
#[cfg(not(cygwin))]
|
||||
if fish_common::is_console_session() {
|
||||
return wcwidth(c);
|
||||
}
|
||||
static WC_LOOKUP_TABLE: LazyLock<WcLookupTable> = LazyLock::new(WcLookupTable::new);
|
||||
|
||||
pub fn fish_wcwidth(c: char) -> Option<usize> {
|
||||
// Check for VS16 which selects emoji presentation. This "promotes" a character like U+2764
|
||||
// (width 1) to an emoji (probably width 2). So treat it as width 1 so the sums work. See #2652.
|
||||
// VS15 selects text presentation.
|
||||
let variation_selector_16 = '\u{FE0F}';
|
||||
let variation_selector_15 = '\u{FE0E}';
|
||||
if c == variation_selector_16 {
|
||||
return 1;
|
||||
return Some(1);
|
||||
} else if c == variation_selector_15 {
|
||||
return 0;
|
||||
return Some(0);
|
||||
}
|
||||
|
||||
// Check for Emoji_Modifier property. Only the Fitzpatrick modifiers have this, in range
|
||||
// 1F3FB..1F3FF. This is a hack because such an emoji appearing on its own would be drawn as
|
||||
// width 2, but that's unlikely to be useful. See #8275.
|
||||
if ('\u{1F3FB}'..='\u{1F3FF}').contains(&c) {
|
||||
return 0;
|
||||
return Some(0);
|
||||
}
|
||||
|
||||
let width = WC_LOOKUP_TABLE.classify(c);
|
||||
match width {
|
||||
WcWidth::NonCharacter | WcWidth::NonPrint | WcWidth::Combining | WcWidth::Unassigned => {
|
||||
#[cfg(not(cygwin))]
|
||||
{
|
||||
// Fall back to system wcwidth in this case.
|
||||
wcwidth(c)
|
||||
}
|
||||
#[cfg(cygwin)]
|
||||
{
|
||||
// No system wcwidth for UTF-32 on cygwin.
|
||||
0
|
||||
}
|
||||
}
|
||||
Some(match width {
|
||||
WcWidth::NonPrint => return None,
|
||||
WcWidth::NonCharacter | WcWidth::Combining | WcWidth::Unassigned => 0,
|
||||
WcWidth::Ambiguous | WcWidth::PrivateUse => {
|
||||
// TR11: "All private-use characters are by default classified as Ambiguous".
|
||||
FISH_AMBIGUOUS_WIDTH.load(Ordering::Relaxed)
|
||||
@@ -89,26 +59,25 @@ pub fn fish_wcwidth(c: char) -> isize {
|
||||
WcWidth::One => 1,
|
||||
WcWidth::Two => 2,
|
||||
WcWidth::WidenedIn9 => FISH_EMOJI_WIDTH.load(Ordering::Relaxed),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// fish's internal versions of wcwidth and wcswidth, which can use an internal implementation if
|
||||
/// the system one is busted.
|
||||
pub fn fish_wcswidth(s: &wstr) -> isize {
|
||||
// ascii fast path; empty iterator returns true for .all()
|
||||
if s.chars().all(|c| c.is_ascii() && !c.is_ascii_control()) {
|
||||
return s.len() as isize;
|
||||
pub fn fish_wcswidth(s: &wstr) -> Option<usize> {
|
||||
fish_wcswidth_canonicalizing(s, std::convert::identity)
|
||||
}
|
||||
|
||||
pub fn fish_wcswidth_canonicalizing(s: &wstr, canonicalize: fn(char) -> char) -> Option<usize> {
|
||||
let chars = s.chars().map(canonicalize);
|
||||
// ascii fast path
|
||||
if chars.clone().all(|c| c.is_ascii() && !c.is_ascii_control()) {
|
||||
return Some(s.len());
|
||||
}
|
||||
|
||||
let mut result = 0;
|
||||
for c in s.chars() {
|
||||
let w = fish_wcwidth(c);
|
||||
if w < 0 {
|
||||
return -1;
|
||||
}
|
||||
result += w;
|
||||
for c in chars {
|
||||
result += fish_wcwidth(c)?;
|
||||
}
|
||||
result
|
||||
Some(result)
|
||||
}
|
||||
|
||||
pub fn wcscasecmp(lhs: &wstr, rhs: &wstr) -> cmp::Ordering {
|
||||
@@ -183,7 +152,7 @@ pub fn new(mut chars: Chars, to_lowercase: fn(char) -> ToLowercase) -> Self {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::wcscasecmp;
|
||||
use fish_wchar::prelude::*;
|
||||
use fish_widestring::prelude::*;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[test]
|
||||
|
||||
13
crates/feature-flags/Cargo.toml
Normal file
13
crates/feature-flags/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "fish-feature-flags"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fish-widestring.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,15 +1,14 @@
|
||||
//! Flags to enable upcoming features
|
||||
|
||||
use crate::prelude::*;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[cfg(test)]
|
||||
use std::cell::RefCell;
|
||||
use fish_widestring::{L, WExt as _, wstr};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
/// The list of flags.
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FeatureFlag {
|
||||
/// Whether ^ is supported for stderr redirection.
|
||||
StderrNoCaret,
|
||||
@@ -63,10 +62,10 @@ pub struct FeatureMetadata {
|
||||
pub description: &'static wstr,
|
||||
|
||||
/// Default flag value.
|
||||
pub default_value: bool,
|
||||
default_value: bool,
|
||||
|
||||
/// Whether the value can still be changed or not.
|
||||
pub read_only: bool,
|
||||
read_only: bool,
|
||||
}
|
||||
|
||||
/// The metadata, indexed by flag.
|
||||
@@ -131,9 +130,11 @@ pub struct FeatureMetadata {
|
||||
flag: FeatureFlag::IgnoreTerminfo,
|
||||
name: L!("ignore-terminfo"),
|
||||
groups: L!("4.1"),
|
||||
description: L!("do not look up $TERM in terminfo database"),
|
||||
description: L!(
|
||||
"do not look up $TERM in terminfo database (historical, can no longer be changed)"
|
||||
),
|
||||
default_value: true,
|
||||
read_only: false,
|
||||
read_only: true,
|
||||
},
|
||||
FeatureMetadata {
|
||||
flag: FeatureFlag::QueryTerm,
|
||||
@@ -154,31 +155,26 @@ pub struct FeatureMetadata {
|
||||
];
|
||||
|
||||
thread_local!(
|
||||
#[cfg(test)]
|
||||
static LOCAL_FEATURES: RefCell<Option<Features>> = const { RefCell::new(None) };
|
||||
static LOCAL_OVERRIDE_STACK: RefCell<Vec<(FeatureFlag, bool)>> =
|
||||
const { RefCell::new(Vec::new()) };
|
||||
);
|
||||
|
||||
/// The singleton shared feature set.
|
||||
static FEATURES: Features = Features::new();
|
||||
|
||||
/// Perform a feature test on the global set of features.
|
||||
pub fn test(flag: FeatureFlag) -> bool {
|
||||
#[cfg(test)]
|
||||
{
|
||||
LOCAL_FEATURES.with(|fc| fc.borrow().as_ref().unwrap_or(&FEATURES).test(flag))
|
||||
pub fn feature_test(flag: FeatureFlag) -> bool {
|
||||
if let Some(value) = LOCAL_OVERRIDE_STACK.with(|stack| {
|
||||
for &(overridden_feature, value) in stack.borrow().iter().rev() {
|
||||
if flag == overridden_feature {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
None
|
||||
}) {
|
||||
return value;
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
FEATURES.test(flag)
|
||||
}
|
||||
}
|
||||
|
||||
pub use test as feature_test;
|
||||
|
||||
/// Set a flag.
|
||||
#[cfg(test)]
|
||||
pub fn set(flag: FeatureFlag, value: bool) {
|
||||
LOCAL_FEATURES.with(|fc| fc.borrow().as_ref().unwrap_or(&FEATURES).set(flag, value));
|
||||
FEATURES.test(flag)
|
||||
}
|
||||
|
||||
/// Parses a comma-separated feature-flag string, updating ourselves with the values.
|
||||
@@ -186,38 +182,31 @@ pub fn set(flag: FeatureFlag, value: bool) {
|
||||
/// The special group name "all" may be used for those who like to live on the edge.
|
||||
/// Unknown features are silently ignored.
|
||||
pub fn set_from_string<'a>(str: impl Into<&'a wstr>) {
|
||||
let wstr: &wstr = str.into();
|
||||
#[cfg(test)]
|
||||
{
|
||||
LOCAL_FEATURES.with(|fc| {
|
||||
fc.borrow()
|
||||
.as_ref()
|
||||
.unwrap_or(&FEATURES)
|
||||
.set_from_string(wstr)
|
||||
});
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
FEATURES.set_from_string(wstr)
|
||||
}
|
||||
FEATURES.set_from_string(str.into());
|
||||
}
|
||||
|
||||
impl Features {
|
||||
const fn new() -> Self {
|
||||
Features {
|
||||
values: [
|
||||
AtomicBool::new(METADATA[0].default_value),
|
||||
AtomicBool::new(METADATA[1].default_value),
|
||||
AtomicBool::new(METADATA[2].default_value),
|
||||
AtomicBool::new(METADATA[3].default_value),
|
||||
AtomicBool::new(METADATA[4].default_value),
|
||||
AtomicBool::new(METADATA[5].default_value),
|
||||
AtomicBool::new(METADATA[6].default_value),
|
||||
AtomicBool::new(METADATA[7].default_value),
|
||||
AtomicBool::new(METADATA[8].default_value),
|
||||
AtomicBool::new(METADATA[9].default_value),
|
||||
],
|
||||
}
|
||||
// TODO: feature(const_array): use std::array::from_fn()
|
||||
use std::mem::{MaybeUninit, transmute};
|
||||
let values = {
|
||||
let mut data: [MaybeUninit<AtomicBool>; METADATA.len()] =
|
||||
[const { MaybeUninit::uninit() }; METADATA.len()];
|
||||
|
||||
let mut i = 0;
|
||||
while i < METADATA.len() {
|
||||
data[i].write(AtomicBool::new(METADATA[i].default_value));
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// SAFETY: `data` is guaranteed initialized by the loop
|
||||
unsafe {
|
||||
transmute::<[MaybeUninit<AtomicBool>; METADATA.len()], [AtomicBool; METADATA.len()]>(
|
||||
data,
|
||||
)
|
||||
}
|
||||
};
|
||||
Features { values }
|
||||
}
|
||||
|
||||
fn test(&self, flag: FeatureFlag) -> bool {
|
||||
@@ -225,23 +214,18 @@ fn test(&self, flag: FeatureFlag) -> bool {
|
||||
}
|
||||
|
||||
fn set(&self, flag: FeatureFlag, value: bool) {
|
||||
self.values[flag as usize].store(value, Ordering::SeqCst)
|
||||
self.values[flag as usize].store(value, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
fn set_from_string(&self, str: &wstr) {
|
||||
let whitespace = L!("\t\n\0x0B\0x0C\r ").as_char_slice();
|
||||
for entry in str.as_char_slice().split(|c| *c == ',') {
|
||||
for entry in str.split(',') {
|
||||
let entry = entry.trim();
|
||||
if entry.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Trim leading and trailing whitespace
|
||||
let entry = &entry[entry.iter().take_while(|c| whitespace.contains(c)).count()..];
|
||||
let entry =
|
||||
&entry[..entry.len() - entry.iter().take_while(|c| whitespace.contains(c)).count()];
|
||||
|
||||
// A "no-" prefix inverts the sense.
|
||||
let (name, value) = match entry.strip_prefix(L!("no-").as_char_slice()) {
|
||||
let (name, value) = match entry.strip_prefix("no-") {
|
||||
Some(suffix) => (suffix, false),
|
||||
None => (entry, true),
|
||||
};
|
||||
@@ -267,28 +251,20 @@ fn set_from_string(&self, str: &wstr) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn scoped_test(flag: FeatureFlag, value: bool, test_fn: impl FnOnce()) {
|
||||
LOCAL_FEATURES.with(|fc| {
|
||||
assert!(
|
||||
fc.borrow().is_none(),
|
||||
"scoped_test() does not support nesting"
|
||||
);
|
||||
|
||||
let f = Features::new();
|
||||
f.set(flag, value);
|
||||
*fc.borrow_mut() = Some(f);
|
||||
|
||||
/// Run code with a feature overridden.
|
||||
/// This should only be used in tests.
|
||||
pub fn with_overridden_feature(flag: FeatureFlag, value: bool, test_fn: impl FnOnce()) {
|
||||
LOCAL_OVERRIDE_STACK.with(|stack| {
|
||||
stack.borrow_mut().push((flag, value));
|
||||
test_fn();
|
||||
|
||||
*fc.borrow_mut() = None;
|
||||
stack.borrow_mut().pop();
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{FeatureFlag, Features, METADATA, scoped_test, set, test};
|
||||
use crate::prelude::*;
|
||||
use super::{FeatureFlag, Features, METADATA, feature_test, with_overridden_feature};
|
||||
use fish_widestring::L;
|
||||
|
||||
#[test]
|
||||
fn test_feature_flags() {
|
||||
@@ -314,25 +290,19 @@ fn test_feature_flags() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scoped() {
|
||||
scoped_test(FeatureFlag::QuestionMarkNoGlob, true, || {
|
||||
assert!(test(FeatureFlag::QuestionMarkNoGlob));
|
||||
fn test_overridden_feature() {
|
||||
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, true, || {
|
||||
assert!(feature_test(FeatureFlag::QuestionMarkNoGlob));
|
||||
});
|
||||
|
||||
set(FeatureFlag::QuestionMarkNoGlob, true);
|
||||
|
||||
scoped_test(FeatureFlag::QuestionMarkNoGlob, false, || {
|
||||
assert!(!test(FeatureFlag::QuestionMarkNoGlob));
|
||||
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, false, || {
|
||||
assert!(!feature_test(FeatureFlag::QuestionMarkNoGlob));
|
||||
});
|
||||
|
||||
set(FeatureFlag::QuestionMarkNoGlob, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_nested_scopes_not_supported() {
|
||||
scoped_test(FeatureFlag::QuestionMarkNoGlob, true, || {
|
||||
scoped_test(FeatureFlag::QuestionMarkNoGlob, false, || {});
|
||||
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, false, || {
|
||||
with_overridden_feature(FeatureFlag::QuestionMarkNoGlob, true, || {
|
||||
assert!(feature_test(FeatureFlag::QuestionMarkNoGlob));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
extern crate proc_macro;
|
||||
use fish_tempfile::random_filename;
|
||||
use proc_macro::TokenStream;
|
||||
use std::{ffi::OsString, io::Write, path::PathBuf};
|
||||
use std::{ffi::OsString, io::Write as _, path::PathBuf};
|
||||
|
||||
fn unescape_multiline_rust_string(s: String) -> String {
|
||||
if !s.contains('\n') {
|
||||
@@ -26,7 +26,7 @@ enum State {
|
||||
Escaped => match c {
|
||||
'\\' => {
|
||||
unescaped.push('\\');
|
||||
state = Ground
|
||||
state = Ground;
|
||||
}
|
||||
'\n' => state = ContinuationLineLeadingWhitespace,
|
||||
_ => panic!("Unsupported escape sequence '\\{c}' in message string '{s}'"),
|
||||
@@ -35,7 +35,7 @@ enum State {
|
||||
' ' | '\t' => (),
|
||||
_ => {
|
||||
unescaped.push(c);
|
||||
state = Ground
|
||||
state = Ground;
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -47,11 +47,18 @@ enum State {
|
||||
// unsynchronized writers to the same file.
|
||||
fn write_po_entry_to_file(message: &TokenStream, dir: &OsString) {
|
||||
let message_string = unescape_multiline_rust_string(message.to_string());
|
||||
if message_string.contains('\n') {
|
||||
panic!(
|
||||
"Gettext strings may not contain unescaped newlines. Unescaped newline found in '{message_string}'"
|
||||
)
|
||||
}
|
||||
assert!(
|
||||
!message_string.contains('\n'),
|
||||
"Gettext strings may not contain unescaped newlines. Unescaped newline found in '{message_string}'"
|
||||
);
|
||||
let msgid_without_quotes = &message_string[1..(message_string.len() - 1)];
|
||||
// We don't want leading or trailing whitespace in our messages.
|
||||
let trimmed_msgid = msgid_without_quotes.trim();
|
||||
assert_eq!(msgid_without_quotes, trimmed_msgid);
|
||||
assert!(!trimmed_msgid.starts_with("\\n"));
|
||||
assert!(!trimmed_msgid.ends_with("\\n"));
|
||||
assert!(!trimmed_msgid.starts_with("\\t"));
|
||||
assert!(!trimmed_msgid.ends_with("\\t"));
|
||||
// Crude check for format strings. This might result in false positives.
|
||||
let format_string_annotation = if message_string.contains('%') {
|
||||
"#, c-format\n"
|
||||
@@ -90,11 +97,10 @@ pub fn gettext_extract(message: TokenStream) -> TokenStream {
|
||||
let first_token = token_trees
|
||||
.next()
|
||||
.expect("gettext_extract got empty token stream. Expected one token.");
|
||||
if token_trees.next().is_some() {
|
||||
panic!(
|
||||
"Invalid number of tokens passed to gettext_extract. Expected one token, but got more."
|
||||
)
|
||||
}
|
||||
assert!(
|
||||
token_trees.next().is_none(),
|
||||
"Invalid number of tokens passed to gettext_extract. Expected one token, but got more."
|
||||
);
|
||||
let proc_macro2::TokenTree::Group(group) = first_token else {
|
||||
panic!("Expected group in gettext_extract, but got: {first_token:?}");
|
||||
};
|
||||
@@ -102,11 +108,10 @@ pub fn gettext_extract(message: TokenStream) -> TokenStream {
|
||||
let first_group_token = group_tokens
|
||||
.next()
|
||||
.expect("gettext_extract expected one group token but got none.");
|
||||
if group_tokens.next().is_some() {
|
||||
panic!(
|
||||
"Invalid number of tokens in group passed to gettext_extract. Expected one token, but got more."
|
||||
)
|
||||
}
|
||||
assert!(
|
||||
group_tokens.next().is_none(),
|
||||
"Invalid number of tokens in group passed to gettext_extract. Expected one token, but got more."
|
||||
);
|
||||
if let proc_macro2::TokenTree::Literal(_) = first_group_token {
|
||||
write_po_entry_to_file(&message, &dir_path);
|
||||
} else {
|
||||
|
||||
@@ -11,24 +11,16 @@ fn main() {
|
||||
PathBuf::from(fish_build_helper::fish_build_dir()).join("fish-localization-map-cache");
|
||||
embed_localizations(&cache_dir);
|
||||
|
||||
fish_build_helper::rebuild_if_path_changed(
|
||||
fish_build_helper::workspace_root()
|
||||
.join("localization")
|
||||
.join("po"),
|
||||
);
|
||||
fish_build_helper::rebuild_if_path_changed(fish_build_helper::po_dir());
|
||||
}
|
||||
|
||||
fn embed_localizations(cache_dir: &Path) {
|
||||
use fish_gettext_mo_file_parser::parse_mo_file;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufWriter, Write},
|
||||
io::{BufWriter, Write as _},
|
||||
};
|
||||
|
||||
let po_dir = fish_build_helper::workspace_root()
|
||||
.join("localization")
|
||||
.join("po");
|
||||
|
||||
// Ensure that the directory is created, because clippy cannot compile the code if the
|
||||
// directory does not exist.
|
||||
std::fs::create_dir_all(cache_dir).unwrap();
|
||||
@@ -56,7 +48,7 @@ fn embed_localizations(cache_dir: &Path) {
|
||||
Ok(output) => {
|
||||
let has_check_format =
|
||||
String::from_utf8_lossy(&output.stdout).contains("--check-format");
|
||||
for dir_entry_result in po_dir.read_dir().unwrap() {
|
||||
for dir_entry_result in fish_build_helper::po_dir().read_dir().unwrap() {
|
||||
let dir_entry = dir_entry_result.unwrap();
|
||||
let po_file_path = dir_entry.path();
|
||||
if po_file_path.extension() != Some(OsStr::new("po")) {
|
||||
@@ -91,7 +83,7 @@ fn embed_localizations(cache_dir: &Path) {
|
||||
if cached_map_mtime > po_mtime {
|
||||
// Cached map file is considered up-to-date.
|
||||
continue;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the map file.
|
||||
@@ -104,7 +96,7 @@ fn embed_localizations(cache_dir: &Path) {
|
||||
cmd = cmd.arg("--check-format");
|
||||
} else {
|
||||
tmp_mo_file = Some(cache_dir.join("messages.mo"));
|
||||
};
|
||||
}
|
||||
cmd.arg(format!(
|
||||
"--output-file={}",
|
||||
tmp_mo_file
|
||||
@@ -115,12 +107,11 @@ fn embed_localizations(cache_dir: &Path) {
|
||||
.output()
|
||||
.unwrap()
|
||||
};
|
||||
if !output.status.success() {
|
||||
panic!(
|
||||
"msgfmt failed:\n{}",
|
||||
String::from_utf8(output.stderr).unwrap()
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"msgfmt failed:\n{}",
|
||||
String::from_utf8(output.stderr).unwrap()
|
||||
);
|
||||
let mo_data =
|
||||
tmp_mo_file.map_or(output.stdout, |path| std::fs::read(path).unwrap());
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
const U32_SIZE: usize = std::mem::size_of::<u32>();
|
||||
const U32_SIZE: usize = size_of::<u32>();
|
||||
|
||||
fn read_le_u32(bytes: &[u8]) -> u32 {
|
||||
u32::from_le_bytes(bytes[..U32_SIZE].try_into().unwrap())
|
||||
@@ -47,9 +47,12 @@ fn check_if_revision_is_supported(revision: u32) -> std::io::Result<()> {
|
||||
}
|
||||
|
||||
fn as_usize(value: u32) -> usize {
|
||||
use std::mem::size_of;
|
||||
const _: () = assert!(size_of::<u32>() <= size_of::<usize>());
|
||||
usize::try_from(value).unwrap()
|
||||
const {
|
||||
assert!(size_of::<u32>() <= size_of::<usize>());
|
||||
}
|
||||
|
||||
// SAFETY: `usize` is guaranteed to be at least as wide as `u32` by the const assert above.
|
||||
unsafe { usize::try_from(value).unwrap_unchecked() }
|
||||
}
|
||||
|
||||
fn parse_strings(
|
||||
|
||||
@@ -8,7 +8,6 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fish-gettext-maps.workspace = true
|
||||
once_cell.workspace = true
|
||||
phf.workspace = true
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -1,259 +1,18 @@
|
||||
use fish_gettext_maps::CATALOGS;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{collections::HashSet, sync::Mutex};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{LazyLock, Mutex},
|
||||
};
|
||||
|
||||
type Catalog = &'static phf::Map<&'static str, &'static str>;
|
||||
|
||||
pub struct SetLanguageLints<'a> {
|
||||
pub duplicates: Vec<&'a str>,
|
||||
pub non_existing: Vec<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum LanguagePrecedenceOrigin {
|
||||
Default,
|
||||
LocaleVariable(LocaleVariable),
|
||||
LanguageEnvVar,
|
||||
StatusLanguage,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum LocaleVariable {
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
LANG,
|
||||
#[allow(non_camel_case_types)]
|
||||
LC_MESSAGES,
|
||||
#[allow(non_camel_case_types)]
|
||||
LC_ALL,
|
||||
}
|
||||
|
||||
impl LocaleVariable {
|
||||
fn as_language_precedence_origin(&self) -> LanguagePrecedenceOrigin {
|
||||
LanguagePrecedenceOrigin::LocaleVariable(*self)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::LANG => "LANG",
|
||||
Self::LC_MESSAGES => "LC_MESSAGES",
|
||||
Self::LC_ALL => "LC_ALL",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LocaleVariable {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
struct InternalLocalizationState {
|
||||
precedence_origin: LanguagePrecedenceOrigin,
|
||||
language_precedence: Vec<(String, Catalog)>,
|
||||
}
|
||||
|
||||
pub struct PublicLocalizationState {
|
||||
pub precedence_origin: LanguagePrecedenceOrigin,
|
||||
pub language_precedence: Vec<String>,
|
||||
}
|
||||
|
||||
/// Stores the current localization status.
|
||||
/// `is_active` indicates whether localization is currently active, and the reason if it is
|
||||
/// not.
|
||||
/// The `origin` indicates where the values in `language_precedence` were taken from.
|
||||
/// `language_precedence` stores the catalogs in the order they should be used.
|
||||
///
|
||||
/// This struct should be updated when the relevant variables change or `status language` is used
|
||||
/// to modify the localization state.
|
||||
static LOCALIZATION_STATE: Lazy<Mutex<InternalLocalizationState>> =
|
||||
Lazy::new(|| Mutex::new(InternalLocalizationState::new()));
|
||||
|
||||
impl InternalLocalizationState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
precedence_origin: LanguagePrecedenceOrigin::Default,
|
||||
language_precedence: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn to_public(&self) -> PublicLocalizationState {
|
||||
PublicLocalizationState {
|
||||
precedence_origin: self.precedence_origin,
|
||||
language_precedence: self
|
||||
.language_precedence
|
||||
.iter()
|
||||
.map(|(lang, _)| lang.to_owned())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_from_env(
|
||||
&mut self,
|
||||
message_locale: Option<(LocaleVariable, String)>,
|
||||
language_var: Option<Vec<String>>,
|
||||
) {
|
||||
// Do not override values set via `status language`.
|
||||
if self.precedence_origin == LanguagePrecedenceOrigin::StatusLanguage {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some((precedence_origin, locale)) = &message_locale {
|
||||
// Regular locale names start with lowercase letters (`ll_CC`, followed by some suffix).
|
||||
// The C or POSIX locale is special, and often used to disable localization.
|
||||
// Their names are upper-case, but variants with suffixes (`C.UTF-8`) exist.
|
||||
// To ensure that such variants are accounted for, we match on prefixes of the
|
||||
// locale name.
|
||||
// https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html#tag_07_02
|
||||
fn is_c_locale(locale: &str) -> bool {
|
||||
locale.starts_with('C') || locale.starts_with("POSIX")
|
||||
}
|
||||
if is_c_locale(locale) {
|
||||
self.precedence_origin =
|
||||
LanguagePrecedenceOrigin::LocaleVariable(*precedence_origin);
|
||||
self.language_precedence.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let (precedence_origin, language_list) = if let Some(list) = language_var {
|
||||
(LanguagePrecedenceOrigin::LanguageEnvVar, list)
|
||||
} else if let Some((precedence_origin, locale)) = message_locale {
|
||||
let mut normalized_name = String::new();
|
||||
// Strip off encoding and modifier. (We always expect UTF-8 and don't support modifiers.)
|
||||
for c in locale.chars() {
|
||||
if c.is_alphabetic() || c == '_' {
|
||||
normalized_name.push(c);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// At this point, the normalized_name should have the shape `ll` or `ll_CC`.
|
||||
(
|
||||
precedence_origin.as_language_precedence_origin(),
|
||||
vec![normalized_name],
|
||||
)
|
||||
} else {
|
||||
(LanguagePrecedenceOrigin::Default, vec![])
|
||||
};
|
||||
|
||||
let mut seen_languages = HashSet::new();
|
||||
self.language_precedence = language_list
|
||||
.into_iter()
|
||||
.flat_map(|lang| find_existing_catalogs(&lang))
|
||||
.filter(|(lang, _)| seen_languages.insert(lang.to_owned()))
|
||||
.collect();
|
||||
self.precedence_origin = precedence_origin;
|
||||
}
|
||||
|
||||
fn update_from_status_language_builtin<'a, 'b: 'a, S: AsRef<str> + 'a>(
|
||||
&mut self,
|
||||
langs: &'b [S],
|
||||
) -> SetLanguageLints<'a> {
|
||||
let mut seen = HashSet::new();
|
||||
let mut duplicates = vec![];
|
||||
for lang in langs {
|
||||
let lang = lang.as_ref();
|
||||
if !seen.insert(lang) {
|
||||
duplicates.push(lang)
|
||||
}
|
||||
}
|
||||
let mut existing_langs = vec![];
|
||||
let mut non_existing = vec![];
|
||||
for lang in langs {
|
||||
let lang = lang.as_ref();
|
||||
if let Some(catalog) = CATALOGS.get(lang) {
|
||||
existing_langs.push((lang.to_owned(), *catalog));
|
||||
} else {
|
||||
non_existing.push(lang);
|
||||
}
|
||||
}
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
let unique_langs = existing_langs
|
||||
.into_iter()
|
||||
.filter(|(lang, _)| seen.insert(lang.to_owned()))
|
||||
.collect();
|
||||
self.language_precedence = unique_langs;
|
||||
self.precedence_origin = LanguagePrecedenceOrigin::StatusLanguage;
|
||||
|
||||
SetLanguageLints {
|
||||
duplicates,
|
||||
non_existing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to find catalogs for `language`.
|
||||
/// `language` must be an ISO 639 language code, optionally followed by an underscore and an ISO
|
||||
/// 3166 country/territory code.
|
||||
/// Uses the catalog with the exact same name as `language` if it exists.
|
||||
/// If a country code is present (`ll_CC`), only the catalog named `ll` will be considered as a fallback.
|
||||
/// If no country code is present (`ll`), all catalogs whose names start with `ll_` will be used in
|
||||
/// arbitrary order.
|
||||
fn find_existing_catalogs(language: &str) -> Vec<(String, Catalog)> {
|
||||
// Try the exact name first.
|
||||
// If there already is a corresponding catalog return the language.
|
||||
if let Some(catalog) = CATALOGS.get(language) {
|
||||
return vec![(language.to_owned(), catalog)];
|
||||
}
|
||||
let language_without_country_code = language.split_once('_').map_or(language, |(ll, _cc)| ll);
|
||||
if language == language_without_country_code {
|
||||
// We have `ll` format. In this case, try to find any catalog whose name starts with `ll_`.
|
||||
// Note that it is important to include the underscore in the pattern, otherwise `ll` might
|
||||
// fall back to `llx_CC`, where `llx` is a 3-letter language identifier.
|
||||
let ll_prefix = format!("{language}_");
|
||||
let mut lang_catalogs = vec![];
|
||||
for (&lang_name, &catalog) in CATALOGS.entries() {
|
||||
if lang_name.starts_with(&ll_prefix) {
|
||||
lang_catalogs.push((lang_name.to_owned(), catalog));
|
||||
}
|
||||
}
|
||||
lang_catalogs
|
||||
} else {
|
||||
// If `language` contained a country code, we only try to fall back to a catalog
|
||||
// without a country code.
|
||||
if let Some(catalog) = CATALOGS.get(language_without_country_code) {
|
||||
vec![(language_without_country_code.to_owned(), catalog)]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_from_env(
|
||||
locale: Option<(LocaleVariable, String)>,
|
||||
language_var: Option<Vec<String>>,
|
||||
) {
|
||||
let mut localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
localization_state.update_from_env(locale, language_var);
|
||||
}
|
||||
|
||||
pub fn update_from_status_language_builtin<'a, 'b: 'a, S: AsRef<str> + 'a>(
|
||||
langs: &'b [S],
|
||||
) -> SetLanguageLints<'a> {
|
||||
let mut localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
localization_state.update_from_status_language_builtin(langs)
|
||||
}
|
||||
|
||||
pub fn unset_from_status_language_builtin(
|
||||
locale: Option<(LocaleVariable, String)>,
|
||||
language_var: Option<Vec<String>>,
|
||||
) {
|
||||
let mut localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
localization_state.precedence_origin = LanguagePrecedenceOrigin::Default;
|
||||
localization_state.update_from_env(locale, language_var);
|
||||
}
|
||||
|
||||
pub fn status_language() -> PublicLocalizationState {
|
||||
let localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
localization_state.to_public()
|
||||
}
|
||||
static LANGUAGE_PRECEDENCE: Mutex<Vec<(&'static str, Catalog)>> = Mutex::new(Vec::new());
|
||||
|
||||
pub fn gettext(message_str: &'static str) -> Option<&'static str> {
|
||||
let localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
let language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap();
|
||||
|
||||
// Use the localization from the highest-precedence language that has one available.
|
||||
for (_, catalog) in localization_state.language_precedence.iter() {
|
||||
for (_, catalog) in language_precedence.iter() {
|
||||
if let Some(localized_str) = catalog.get(message_str) {
|
||||
return Some(localized_str);
|
||||
}
|
||||
@@ -261,8 +20,40 @@ pub fn gettext(message_str: &'static str) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn list_available_languages() -> Vec<&'static str> {
|
||||
let mut langs: Vec<_> = CATALOGS.entries().map(|(&lang, _)| lang).collect();
|
||||
langs.sort();
|
||||
langs
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct GettextLocalizationLanguage {
|
||||
language: &'static str,
|
||||
}
|
||||
|
||||
static AVAILABLE_LANGUAGES: LazyLock<HashMap<&'static str, GettextLocalizationLanguage>> =
|
||||
LazyLock::new(|| {
|
||||
HashMap::from_iter(
|
||||
CATALOGS
|
||||
.entries()
|
||||
.map(|(&language, _)| (language, GettextLocalizationLanguage { language })),
|
||||
)
|
||||
});
|
||||
|
||||
pub fn get_available_languages() -> &'static HashMap<&'static str, GettextLocalizationLanguage> {
|
||||
&AVAILABLE_LANGUAGES
|
||||
}
|
||||
|
||||
pub fn set_language_precedence(new_precedence: &[GettextLocalizationLanguage]) {
|
||||
let catalogs = new_precedence
|
||||
.iter()
|
||||
.map(|lang| {
|
||||
(
|
||||
lang.language,
|
||||
*CATALOGS
|
||||
.get(lang.language)
|
||||
.expect("Only languages for which catalogs exist may be passed to gettext."),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
*LANGUAGE_PRECEDENCE.lock().unwrap() = catalogs;
|
||||
}
|
||||
|
||||
pub fn get_language_precedence() -> Vec<&'static str> {
|
||||
let language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap();
|
||||
language_precedence.iter().map(|&(lang, _)| lang).collect()
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ description = "printf implementation, based on musl"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
assert_matches.workspace = true
|
||||
libc.workspace = true
|
||||
widestring = { workspace = true, optional = true }
|
||||
unicode-segmentation.workspace = true
|
||||
unicode-width.workspace = true
|
||||
widestring = { workspace = true, optional = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -199,27 +199,28 @@ fn to_arg(self) -> Arg<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use assert_matches::assert_matches;
|
||||
#[cfg(feature = "widestring")]
|
||||
use widestring::utf32str;
|
||||
|
||||
#[test]
|
||||
fn test_to_arg() {
|
||||
assert!(matches!("test".to_arg(), Arg::Str("test")));
|
||||
assert!(matches!(String::from("test").to_arg(), Arg::Str(_)));
|
||||
assert_matches!("test".to_arg(), Arg::Str("test"));
|
||||
assert_matches!(String::from("test").to_arg(), Arg::Str(_));
|
||||
#[cfg(feature = "widestring")]
|
||||
assert!(matches!(utf32str!("test").to_arg(), Arg::WStr(_)));
|
||||
assert_matches!(utf32str!("test").to_arg(), Arg::WStr(_));
|
||||
#[cfg(feature = "widestring")]
|
||||
assert!(matches!(WString::from("test").to_arg(), Arg::WStr(_)));
|
||||
assert!(matches!(42f32.to_arg(), Arg::Float(_)));
|
||||
assert!(matches!(42f64.to_arg(), Arg::Float(_)));
|
||||
assert!(matches!('x'.to_arg(), Arg::UInt(120)));
|
||||
assert_matches!(WString::from("test").to_arg(), Arg::WStr(_));
|
||||
assert_matches!(42f32.to_arg(), Arg::Float(_));
|
||||
assert_matches!(42f64.to_arg(), Arg::Float(_));
|
||||
assert_matches!('x'.to_arg(), Arg::UInt(120));
|
||||
let mut usize_val: usize = 0;
|
||||
assert!(matches!((&mut usize_val).to_arg(), Arg::USizeRef(_)));
|
||||
assert!(matches!(42i8.to_arg(), Arg::SInt(42)));
|
||||
assert!(matches!(42i16.to_arg(), Arg::SInt(42)));
|
||||
assert!(matches!(42i32.to_arg(), Arg::SInt(42)));
|
||||
assert!(matches!(42i64.to_arg(), Arg::SInt(42)));
|
||||
assert!(matches!(42isize.to_arg(), Arg::SInt(42)));
|
||||
assert_matches!((&mut usize_val).to_arg(), Arg::USizeRef(_));
|
||||
assert_matches!(42i8.to_arg(), Arg::SInt(42));
|
||||
assert_matches!(42i16.to_arg(), Arg::SInt(42));
|
||||
assert_matches!(42i32.to_arg(), Arg::SInt(42));
|
||||
assert_matches!(42i64.to_arg(), Arg::SInt(42));
|
||||
assert_matches!(42isize.to_arg(), Arg::SInt(42));
|
||||
|
||||
assert_eq!((-42i8).to_arg(), Arg::SInt(-42));
|
||||
assert_eq!((-42i16).to_arg(), Arg::SInt(-42));
|
||||
@@ -227,14 +228,14 @@ fn test_to_arg() {
|
||||
assert_eq!((-42i64).to_arg(), Arg::SInt(-42));
|
||||
assert_eq!((-42isize).to_arg(), Arg::SInt(-42));
|
||||
|
||||
assert!(matches!(42u8.to_arg(), Arg::UInt(42)));
|
||||
assert!(matches!(42u16.to_arg(), Arg::UInt(42)));
|
||||
assert!(matches!(42u32.to_arg(), Arg::UInt(42)));
|
||||
assert!(matches!(42u64.to_arg(), Arg::UInt(42)));
|
||||
assert!(matches!(42usize.to_arg(), Arg::UInt(42)));
|
||||
assert_matches!(42u8.to_arg(), Arg::UInt(42));
|
||||
assert_matches!(42u16.to_arg(), Arg::UInt(42));
|
||||
assert_matches!(42u32.to_arg(), Arg::UInt(42));
|
||||
assert_matches!(42u64.to_arg(), Arg::UInt(42));
|
||||
assert_matches!(42usize.to_arg(), Arg::UInt(42));
|
||||
|
||||
let ptr = std::ptr::from_ref(&42f32);
|
||||
assert!(matches!(ptr.to_arg(), Arg::UInt(_)));
|
||||
assert_matches!(ptr.to_arg(), Arg::UInt(_));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -266,7 +266,7 @@ fn should_round_up(&self, digit_idx: i32, remainder: u32, mod_base: u32) -> bool
|
||||
if rounding_digit & 1 != 0 {
|
||||
round += 2.0;
|
||||
// round now has an odd lsb (though round itself is even).
|
||||
debug_assert!(round.to_bits() & 1 != 0);
|
||||
debug_assert_ne!(round.to_bits() & 1, 0);
|
||||
}
|
||||
|
||||
// Set 'small' to a value which is less than halfway, exactly halfway, or more than halfway
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
use super::locale::Locale;
|
||||
use super::printf_impl::{ConversionSpec, Error, ModifierFlags, pad};
|
||||
use assert_matches::debug_assert_matches;
|
||||
use decimal::{DIGIT_WIDTH, Decimal, DigitLimit};
|
||||
use std::cmp::min;
|
||||
use std::fmt::Write;
|
||||
@@ -129,10 +130,10 @@ pub(crate) fn format_float(
|
||||
) -> Result<usize, Error> {
|
||||
// Only float conversions are expected.
|
||||
type CS = ConversionSpec;
|
||||
debug_assert!(matches!(
|
||||
debug_assert_matches!(
|
||||
conv_spec,
|
||||
CS::e | CS::E | CS::f | CS::F | CS::g | CS::G | CS::a | CS::A
|
||||
));
|
||||
);
|
||||
let prefix = match (y.is_sign_negative(), flags.mark_pos, flags.pad_pos) {
|
||||
(true, _, _) => "-",
|
||||
(false, true, _) => "+",
|
||||
|
||||
@@ -51,7 +51,7 @@ macro_rules! sprintf {
|
||||
{
|
||||
// May be no args!
|
||||
#[allow(unused_imports)]
|
||||
use $crate::ToArg;
|
||||
use $crate::ToArg as _;
|
||||
$crate::printf_c_locale(
|
||||
$target,
|
||||
$fmt,
|
||||
@@ -84,7 +84,7 @@ macro_rules! sprintf {
|
||||
///
|
||||
/// let result = printf_c_locale(&mut output, fmt, &mut args);
|
||||
///
|
||||
/// assert!(result == Ok(10));
|
||||
/// assert_eq!(result, Ok(10));
|
||||
/// assert_eq!(output, "1.2346e+05");
|
||||
/// ```
|
||||
pub fn printf_c_locale(
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
use super::arg::Arg;
|
||||
use super::fmt_fp::format_float;
|
||||
use super::locale::Locale;
|
||||
use assert_matches::assert_matches;
|
||||
use std::fmt::{self, Write};
|
||||
use std::mem;
|
||||
use std::result::Result;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use unicode_segmentation::UnicodeSegmentation as _;
|
||||
use unicode_width::UnicodeWidthStr as _;
|
||||
|
||||
#[cfg(feature = "widestring")]
|
||||
use widestring::Utf32Str as wstr;
|
||||
@@ -57,7 +58,7 @@ fn try_set(&mut self, c: char) -> bool {
|
||||
'+' => self.mark_pos = true,
|
||||
'\'' => self.grouped = true,
|
||||
_ => return false,
|
||||
};
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -301,7 +302,7 @@ pub(super) fn pad(
|
||||
min_width: usize,
|
||||
current_width: usize,
|
||||
) -> fmt::Result {
|
||||
assert!(c == '0' || c == ' ');
|
||||
assert_matches!(c, '0' | ' ');
|
||||
if current_width >= min_width {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -341,7 +342,7 @@ pub(super) fn pad(
|
||||
///
|
||||
/// let result = sprintf_locale(&mut output, fmt, &locale::EN_US_LOCALE, &mut args);
|
||||
///
|
||||
/// assert!(result == Ok(12));
|
||||
/// assert_eq!(result, Ok(12));
|
||||
/// assert_eq!(output, "1,234,567.89");
|
||||
/// ```
|
||||
pub fn sprintf_locale(
|
||||
@@ -371,7 +372,7 @@ pub fn sprintf_locale(
|
||||
}
|
||||
|
||||
// Consume the % at the start of the format specifier.
|
||||
debug_assert!(s.at(0) == Some('%'));
|
||||
debug_assert_eq!(s.at(0), Some('%'));
|
||||
s.advance_by(1);
|
||||
|
||||
// Read modifier flags. '-' and '0' flags are mutually exclusive.
|
||||
@@ -561,7 +562,7 @@ pub fn sprintf_locale(
|
||||
};
|
||||
// Numeric output should be empty iff the value is 0.
|
||||
if spec_is_numeric && body.is_empty() {
|
||||
debug_assert!(arg.as_uint().unwrap() == 0);
|
||||
debug_assert_eq!(arg.as_uint().unwrap(), 0);
|
||||
}
|
||||
|
||||
// Decide if we want to apply thousands grouping to the body, and compute its size.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::arg::ToArg;
|
||||
use crate::locale::{C_LOCALE, EN_US_LOCALE, Locale};
|
||||
use crate::{Error, FormatString, sprintf_locale};
|
||||
use crate::{Error, FormatString as _, sprintf_locale};
|
||||
use std::f64::consts::{E, PI, TAU};
|
||||
use std::ffi::CStr;
|
||||
use std::fmt;
|
||||
@@ -13,7 +13,7 @@ macro_rules! sprintf_check {
|
||||
$(,)? // optional trailing comma
|
||||
) => {
|
||||
{
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use unicode_width::UnicodeWidthStr as _;
|
||||
let mut target = String::new();
|
||||
let mut args = [$($arg.to_arg()),*];
|
||||
let len = $crate::printf_c_locale(
|
||||
@@ -400,8 +400,10 @@ fn test_char() {
|
||||
|
||||
#[test]
|
||||
fn test_ptr() {
|
||||
assert_fmt!("%p", core::ptr::null::<u8>() => "0");
|
||||
assert_fmt!("%p", 0xDEADBEEF_usize as *const u8 => "0xdeadbeef");
|
||||
assert_fmt!("%p", core::ptr::null::<()>() => "0");
|
||||
|
||||
let tmp = core::ptr::without_provenance::<()>(0xDEADBEEF);
|
||||
assert_fmt!("%p", tmp => "0xdeadbeef");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -861,8 +863,8 @@ fn test_float_hex_prec() {
|
||||
let mut libc_sprintf = libc_sprintf_one_float_with_precision(&mut c_storage, c"%.*a");
|
||||
|
||||
let mut failed = false;
|
||||
for sign in [1.0, -1.0].into_iter() {
|
||||
for mut v in [0.0, 0.5, 1.0, 1.5, PI, TAU, E].into_iter() {
|
||||
for sign in [1.0, -1.0] {
|
||||
for mut v in [0.0, 0.5, 1.0, 1.5, PI, TAU, E] {
|
||||
v *= sign;
|
||||
for preci in 1..=200_usize {
|
||||
rust_str.clear();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use rand::distr::{Alphanumeric, Distribution};
|
||||
use rand::distr::{Alphanumeric, Distribution as _};
|
||||
|
||||
pub struct TempFile {
|
||||
file: File,
|
||||
@@ -114,7 +114,7 @@ pub fn new_dir() -> std::io::Result<TempDir> {
|
||||
mod tests {
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Seek, Write},
|
||||
io::{Read as _, Seek as _, Write as _},
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
||||
17
crates/util/Cargo.toml
Normal file
17
crates/util/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "fish-util"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
errno.workspace = true
|
||||
fish-widestring.workspace = true
|
||||
libc.workspace = true
|
||||
nix.workspace = true
|
||||
rand.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,9 +1,15 @@
|
||||
//! Generic utilities library.
|
||||
|
||||
use crate::prelude::*;
|
||||
use rand::{SeedableRng, rngs::SmallRng};
|
||||
use std::cmp::Ordering;
|
||||
use std::time;
|
||||
use errno::errno;
|
||||
use fish_widestring::prelude::*;
|
||||
use rand::{SeedableRng as _, rngs::SmallRng};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
ffi::CStr,
|
||||
io::Write as _,
|
||||
os::fd::{BorrowedFd, RawFd},
|
||||
time,
|
||||
};
|
||||
|
||||
/// Compares two wide character strings with an (arguably) intuitive ordering. This function tries
|
||||
/// to order strings in a way which is intuitive to humans with regards to sorting strings
|
||||
@@ -57,14 +63,23 @@ pub fn wcsfilecmp(a: &wstr, b: &wstr) -> Ordering {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sort dashes after Z - see #5634
|
||||
let mut acl = if ac == '-' { '[' } else { ac };
|
||||
let mut bcl = if bc == '-' { '[' } else { bc };
|
||||
let transform = |c| {
|
||||
// Sort dashes after Z - see #5634
|
||||
if c == '-' {
|
||||
return '[';
|
||||
}
|
||||
if c == '/' {
|
||||
return '\0';
|
||||
}
|
||||
c
|
||||
};
|
||||
let ac = transform(ac);
|
||||
let bc = transform(bc);
|
||||
// TODO Compare the tail (enabled by Rust's Unicode support).
|
||||
acl = acl.to_uppercase().next().unwrap();
|
||||
bcl = bcl.to_uppercase().next().unwrap();
|
||||
let ac = ac.to_uppercase().next().unwrap();
|
||||
let bc = bc.to_uppercase().next().unwrap();
|
||||
|
||||
match acl.cmp(&bcl) {
|
||||
match ac.cmp(&bc) {
|
||||
Ordering::Equal => {
|
||||
ai += 1;
|
||||
bi += 1;
|
||||
@@ -93,7 +108,7 @@ pub fn wcsfilecmp(a: &wstr, b: &wstr) -> Ordering {
|
||||
Ordering::Less // string a is a prefix of b and b is longer
|
||||
}
|
||||
} else {
|
||||
assert!(bi == b.len());
|
||||
assert_eq!(bi, b.len());
|
||||
Ordering::Greater // string b is a prefix of a and a is longer
|
||||
}
|
||||
}
|
||||
@@ -127,9 +142,9 @@ pub fn wcsfilecmp_glob(a: &wstr, b: &wstr) -> Ordering {
|
||||
}
|
||||
|
||||
// TODO Compare the tail (enabled by Rust's Unicode support).
|
||||
let acl = ac.to_lowercase().next().unwrap();
|
||||
let bcl = bc.to_lowercase().next().unwrap();
|
||||
match acl.cmp(&bcl) {
|
||||
let ac = ac.to_lowercase().next().unwrap();
|
||||
let bc = bc.to_lowercase().next().unwrap();
|
||||
match ac.cmp(&bc) {
|
||||
Ordering::Equal => {
|
||||
ai += 1;
|
||||
bi += 1;
|
||||
@@ -158,7 +173,7 @@ pub fn wcsfilecmp_glob(a: &wstr, b: &wstr) -> Ordering {
|
||||
Ordering::Less // string a is a prefix of b and b is longer
|
||||
}
|
||||
} else {
|
||||
assert!(bi == b.len());
|
||||
assert_eq!(bi, b.len());
|
||||
Ordering::Greater // string b is a prefix of a and a is longer
|
||||
}
|
||||
}
|
||||
@@ -234,33 +249,30 @@ fn wcsfilecmp_leading_digits(a: &wstr, b: &wstr) -> (Ordering, usize, usize) {
|
||||
(ret, ai, bi)
|
||||
}
|
||||
|
||||
/// Finds `needle` in a `haystack` and returns the index of the first matching element, if any.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use fish::util::find_subslice;
|
||||
/// let haystack = b"ABC ABCDAB ABCDABCDABDE";
|
||||
///
|
||||
/// assert_eq!(find_subslice(b"ABCDABD", haystack), Some(15));
|
||||
/// assert_eq!(find_subslice(b"ABCDE", haystack), None);
|
||||
/// ```
|
||||
pub fn find_subslice<T: PartialEq>(
|
||||
needle: impl AsRef<[T]>,
|
||||
haystack: impl AsRef<[T]>,
|
||||
) -> Option<usize> {
|
||||
let needle = needle.as_ref();
|
||||
if needle.is_empty() {
|
||||
return Some(0);
|
||||
pub fn write_to_fd(input: &[u8], fd: RawFd) -> nix::Result<usize> {
|
||||
nix::unistd::write(unsafe { BorrowedFd::borrow_raw(fd) }, input)
|
||||
}
|
||||
|
||||
/// Prints the provided string, followed by a colon, space, and the string representation of the
|
||||
/// current errno via [`libc::strerror`].
|
||||
pub fn perror(s: &str) {
|
||||
let e = errno().0;
|
||||
let mut stderr = std::io::stderr().lock();
|
||||
if !s.is_empty() {
|
||||
let _ = write!(stderr, "{s}: ");
|
||||
}
|
||||
let haystack = haystack.as_ref();
|
||||
haystack.windows(needle.len()).position(|w| w == needle)
|
||||
let slice = unsafe {
|
||||
let msg = libc::strerror(e);
|
||||
CStr::from_ptr(msg).to_bytes()
|
||||
};
|
||||
let _ = stderr.write_all(slice);
|
||||
let _ = stderr.write_all(b"\n");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::wcsfilecmp;
|
||||
use crate::prelude::*;
|
||||
use fish_widestring::prelude::*;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
/// Verify the behavior of the `wcsfilecmp()` function.
|
||||
@@ -311,5 +323,8 @@ macro_rules! validate {
|
||||
validate!("a00b", "a0b", Ordering::Less);
|
||||
validate!("a0b", "a00b", Ordering::Greater);
|
||||
validate!("a-b", "azb", Ordering::Greater);
|
||||
validate!("a", "a b", Ordering::Less);
|
||||
validate!("a/", "a b/", Ordering::Less);
|
||||
validate!("a/b", "a b", Ordering::Less); // Note this is arbitrary.
|
||||
}
|
||||
}
|
||||
@@ -1,505 +0,0 @@
|
||||
//! Support for wide strings.
|
||||
//!
|
||||
//! There are two wide string types that are commonly used:
|
||||
//! - wstr: a string slice without a nul terminator. Like `&str` but wide chars.
|
||||
//! - WString: an owning string without a nul terminator. Like `String` but wide chars.
|
||||
|
||||
use fish_common::{ENCODE_DIRECT_BASE, ENCODE_DIRECT_END, subslice_position};
|
||||
use std::{iter, slice};
|
||||
pub use widestring::{Utf32Str as wstr, Utf32String as WString, utfstr::CharsUtf32};
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::{IntoCharIter, L, ToWString, WExt, WString, wstr};
|
||||
}
|
||||
|
||||
/// Creates a wstr string slice, like the "L" prefix of C++.
|
||||
/// The result is of type wstr.
|
||||
/// It is NOT nul-terminated.
|
||||
#[macro_export]
|
||||
macro_rules! L {
|
||||
($string:expr) => {
|
||||
widestring::utf32str!($string)
|
||||
};
|
||||
}
|
||||
|
||||
/// Encode a literal byte in a UTF-32 character. This is required for e.g. the echo builtin, whose
|
||||
/// escape sequences can be used to construct raw byte sequences which are then interpreted as e.g.
|
||||
/// UTF-8 by the terminal. If we were to interpret each of those bytes as a codepoint and encode it
|
||||
/// as a UTF-32 character, printing them would result in several characters instead of one UTF-8
|
||||
/// character.
|
||||
///
|
||||
/// See <https://github.com/fish-shell/fish-shell/issues/1894>.
|
||||
pub fn encode_byte_to_char(byte: u8) -> char {
|
||||
char::from_u32(u32::from(ENCODE_DIRECT_BASE) + u32::from(byte))
|
||||
.expect("private-use codepoint should be valid char")
|
||||
}
|
||||
|
||||
/// Decode a literal byte from a UTF-32 character.
|
||||
pub fn decode_byte_from_char(c: char) -> Option<u8> {
|
||||
if c >= ENCODE_DIRECT_BASE && c < ENCODE_DIRECT_END {
|
||||
Some(
|
||||
(u32::from(c) - u32::from(ENCODE_DIRECT_BASE))
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Helpers to convert things to widestring.
|
||||
/// This is like std::string::ToString.
|
||||
pub trait ToWString {
|
||||
fn to_wstring(&self) -> WString;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_wstring_impl(mut val: u64, neg: bool) -> WString {
|
||||
// 20 digits max in u64: 18446744073709551616.
|
||||
let mut digits = [0; 24];
|
||||
let mut ndigits = 0;
|
||||
while val > 0 {
|
||||
digits[ndigits] = (val % 10) as u8;
|
||||
val /= 10;
|
||||
ndigits += 1;
|
||||
}
|
||||
if ndigits == 0 {
|
||||
digits[0] = 0;
|
||||
ndigits = 1;
|
||||
}
|
||||
let mut result = WString::with_capacity(ndigits + neg as usize);
|
||||
if neg {
|
||||
result.push('-');
|
||||
}
|
||||
for i in (0..ndigits).rev() {
|
||||
result.push((digits[i] + b'0') as char);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Implement to_wstring() for signed types.
|
||||
macro_rules! impl_to_wstring_signed {
|
||||
($($t:ty), *) => {
|
||||
$(
|
||||
impl ToWString for $t {
|
||||
fn to_wstring(&self) -> WString {
|
||||
let val = *self as i64;
|
||||
to_wstring_impl(val.unsigned_abs(), val < 0)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
impl_to_wstring_signed!(i8, i16, i32, i64, isize);
|
||||
|
||||
/// Implement to_wstring() for unsigned types.
|
||||
macro_rules! impl_to_wstring_unsigned {
|
||||
($($t:ty), *) => {
|
||||
$(
|
||||
impl ToWString for $t {
|
||||
fn to_wstring(&self) -> WString {
|
||||
to_wstring_impl(*self as u64, false)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_to_wstring_unsigned!(u8, u16, u32, u64, u128, usize);
|
||||
|
||||
/// A trait for a thing that can produce a double-ended, cloneable
|
||||
/// iterator of chars.
|
||||
/// Common implementations include char, &str, &wstr, &WString.
|
||||
pub trait IntoCharIter {
|
||||
type Iter: DoubleEndedIterator<Item = char> + Clone;
|
||||
fn chars(self) -> Self::Iter;
|
||||
fn extend_wstring(&self, _out: &mut WString) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoCharIter for char {
|
||||
type Iter = std::iter::Once<char>;
|
||||
fn chars(self) -> Self::Iter {
|
||||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoCharIter for &'a str {
|
||||
type Iter = std::str::Chars<'a>;
|
||||
fn chars(self) -> Self::Iter {
|
||||
str::chars(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoCharIter for &'a String {
|
||||
type Iter = std::str::Chars<'a>;
|
||||
fn chars(self) -> Self::Iter {
|
||||
str::chars(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoCharIter for &'a [char] {
|
||||
type Iter = iter::Copied<slice::Iter<'a, char>>;
|
||||
|
||||
fn chars(self) -> Self::Iter {
|
||||
self.iter().copied()
|
||||
}
|
||||
fn extend_wstring(&self, out: &mut WString) -> bool {
|
||||
out.push_utfstr(wstr::from_char_slice(self));
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoCharIter for &'a wstr {
|
||||
type Iter = CharsUtf32<'a>;
|
||||
fn chars(self) -> Self::Iter {
|
||||
wstr::chars(self)
|
||||
}
|
||||
fn extend_wstring(&self, out: &mut WString) -> bool {
|
||||
self.as_char_slice().extend_wstring(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoCharIter for &'a WString {
|
||||
type Iter = CharsUtf32<'a>;
|
||||
fn chars(self) -> Self::Iter {
|
||||
wstr::chars(self)
|
||||
}
|
||||
fn extend_wstring(&self, out: &mut WString) -> bool {
|
||||
self.as_char_slice().extend_wstring(out)
|
||||
}
|
||||
}
|
||||
|
||||
// Also support `str.chars()` itself.
|
||||
impl<'a> IntoCharIter for std::str::Chars<'a> {
|
||||
type Iter = Self;
|
||||
fn chars(self) -> Self::Iter {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// Also support `wstr.chars()` itself.
|
||||
impl<'a> IntoCharIter for CharsUtf32<'a> {
|
||||
type Iter = Self;
|
||||
fn chars(self) -> Self::Iter {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a: 'b, 'b> IntoCharIter for &'b std::borrow::Cow<'a, str> {
|
||||
type Iter = std::str::Chars<'b>;
|
||||
fn chars(self) -> Self::Iter {
|
||||
str::chars(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a: 'b, 'b> IntoCharIter for &'b std::borrow::Cow<'a, wstr> {
|
||||
type Iter = CharsUtf32<'b>;
|
||||
fn chars(self) -> Self::Iter {
|
||||
wstr::chars(self)
|
||||
}
|
||||
fn extend_wstring(&self, out: &mut WString) -> bool {
|
||||
self.as_char_slice().extend_wstring(out)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if `prefix` is a prefix of `contents`.
|
||||
fn iter_prefixes_iter<Prefix, Contents>(prefix: Prefix, mut contents: Contents) -> bool
|
||||
where
|
||||
Prefix: Iterator,
|
||||
Contents: Iterator,
|
||||
Prefix::Item: PartialEq<Contents::Item>,
|
||||
{
|
||||
for c1 in prefix {
|
||||
match contents.next() {
|
||||
Some(c2) if c1 == c2 => {}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Iterator type for splitting a wide string on a char.
|
||||
pub struct WStrCharSplitIter<'a> {
|
||||
split: char,
|
||||
chars: Option<&'a [char]>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for WStrCharSplitIter<'a> {
|
||||
type Item = &'a wstr;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let chars = self.chars?;
|
||||
if let Some(idx) = chars.iter().position(|c| *c == self.split) {
|
||||
let (prefix, rest) = chars.split_at(idx);
|
||||
self.chars = Some(&rest[1..]);
|
||||
Some(wstr::from_char_slice(prefix))
|
||||
} else {
|
||||
self.chars = None;
|
||||
Some(wstr::from_char_slice(chars))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience functions for WString.
|
||||
pub trait WExt {
|
||||
/// Access the chars of a WString or wstr.
|
||||
fn as_char_slice(&self) -> &[char];
|
||||
|
||||
/// Return a char slice from a *char index*.
|
||||
/// This is different from Rust string slicing, which takes a byte index.
|
||||
fn slice_from(&self, start: usize) -> &wstr {
|
||||
let chars = self.as_char_slice();
|
||||
wstr::from_char_slice(&chars[start..])
|
||||
}
|
||||
|
||||
/// Return a char slice up to a *char index*.
|
||||
/// This is different from Rust string slicing, which takes a byte index.
|
||||
fn slice_to(&self, end: usize) -> &wstr {
|
||||
let chars = self.as_char_slice();
|
||||
wstr::from_char_slice(&chars[..end])
|
||||
}
|
||||
|
||||
/// Return the number of chars.
|
||||
/// This is different from Rust string len, which returns the number of bytes.
|
||||
fn char_count(&self) -> usize {
|
||||
self.as_char_slice().len()
|
||||
}
|
||||
|
||||
/// Return the char at an index.
|
||||
/// If the index is equal to the length, return '\0'.
|
||||
/// If the index exceeds the length, then panic.
|
||||
fn char_at(&self, index: usize) -> char {
|
||||
let chars = self.as_char_slice();
|
||||
if index == chars.len() {
|
||||
'\0'
|
||||
} else {
|
||||
chars[index]
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the char at an index.
|
||||
/// If the index is equal to the length, return '\0'.
|
||||
/// If the index exceeds the length, return None.
|
||||
fn try_char_at(&self, index: usize) -> Option<char> {
|
||||
let chars = self.as_char_slice();
|
||||
match index {
|
||||
_ if index == chars.len() => Some('\0'),
|
||||
_ if index > chars.len() => None,
|
||||
_ => Some(chars[index]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an iterator over substrings, split by a given char.
|
||||
/// The split char is not included in the substrings.
|
||||
fn split(&self, c: char) -> WStrCharSplitIter<'_> {
|
||||
WStrCharSplitIter {
|
||||
split: c,
|
||||
chars: Some(self.as_char_slice()),
|
||||
}
|
||||
}
|
||||
|
||||
fn split_once(&self, pos: usize) -> (&wstr, &wstr) {
|
||||
(self.slice_to(pos), self.slice_from(pos))
|
||||
}
|
||||
|
||||
/// Returns the index of the first match against the provided substring or `None`.
|
||||
fn find(&self, search: impl AsRef<[char]>) -> Option<usize> {
|
||||
subslice_position(self.as_char_slice(), search.as_ref())
|
||||
}
|
||||
|
||||
/// Replaces all matches of a pattern with another string.
|
||||
fn replace(&self, from: impl AsRef<[char]>, to: &wstr) -> WString {
|
||||
let from = from.as_ref();
|
||||
let mut s = self.as_char_slice().to_vec();
|
||||
let mut offset = 0;
|
||||
while let Some(relpos) = subslice_position(&s[offset..], from) {
|
||||
offset += relpos;
|
||||
s.splice(offset..(offset + from.len()), to.chars());
|
||||
offset += to.len();
|
||||
}
|
||||
WString::from_chars(s)
|
||||
}
|
||||
|
||||
/// Return the index of the first occurrence of the given char, or None.
|
||||
fn find_char(&self, c: char) -> Option<usize> {
|
||||
self.as_char_slice().iter().position(|&x| x == c)
|
||||
}
|
||||
|
||||
fn contains(&self, c: char) -> bool {
|
||||
self.as_char_slice().contains(&c)
|
||||
}
|
||||
|
||||
/// Return whether we start with a given Prefix.
|
||||
/// The Prefix can be a char, a &str, a &wstr, or a &WString.
|
||||
fn starts_with<Prefix: IntoCharIter>(&self, prefix: Prefix) -> bool {
|
||||
iter_prefixes_iter(prefix.chars(), self.as_char_slice().iter().copied())
|
||||
}
|
||||
|
||||
fn strip_prefix<Prefix: IntoCharIter>(&self, prefix: Prefix) -> Option<&wstr> {
|
||||
let iter = prefix.chars();
|
||||
let prefix_len = iter.clone().count();
|
||||
iter_prefixes_iter(iter, self.as_char_slice().iter().copied())
|
||||
.then(|| self.slice_from(prefix_len))
|
||||
}
|
||||
|
||||
/// Return whether we end with a given Suffix.
|
||||
/// The Suffix can be a char, a &str, a &wstr, or a &WString.
|
||||
fn ends_with<Suffix: IntoCharIter>(&self, suffix: Suffix) -> bool {
|
||||
iter_prefixes_iter(
|
||||
suffix.chars().rev(),
|
||||
self.as_char_slice().iter().copied().rev(),
|
||||
)
|
||||
}
|
||||
|
||||
fn trim_matches(&self, pat: char) -> &wstr {
|
||||
let slice = self.as_char_slice();
|
||||
let leading_count = slice.chars().take_while(|&c| c == pat).count();
|
||||
let trailing_count = slice.chars().rev().take_while(|&c| c == pat).count();
|
||||
if leading_count == slice.len() {
|
||||
return L!("");
|
||||
}
|
||||
let slice = self.slice_from(leading_count);
|
||||
slice.slice_to(slice.len() - trailing_count)
|
||||
}
|
||||
}
|
||||
|
||||
impl WExt for WString {
|
||||
fn as_char_slice(&self) -> &[char] {
|
||||
self.as_utfstr().as_char_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl WExt for wstr {
|
||||
fn as_char_slice(&self) -> &[char] {
|
||||
wstr::as_char_slice(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_to_wstring() {
|
||||
assert_eq!(0_u64.to_wstring(), "0");
|
||||
assert_eq!(1_u64.to_wstring(), "1");
|
||||
assert_eq!(0_i64.to_wstring(), "0");
|
||||
assert_eq!(1_i64.to_wstring(), "1");
|
||||
assert_eq!((-1_i64).to_wstring(), "-1");
|
||||
assert_eq!((-5_i64).to_wstring(), "-5");
|
||||
let mut val: i64 = 1;
|
||||
loop {
|
||||
assert_eq!(val.to_wstring(), val.to_string());
|
||||
let Some(next) = val.checked_mul(-3) else {
|
||||
break;
|
||||
};
|
||||
val = next;
|
||||
}
|
||||
assert_eq!(u64::MAX.to_wstring(), "18446744073709551615");
|
||||
assert_eq!(i64::MIN.to_wstring(), "-9223372036854775808");
|
||||
assert_eq!(i64::MAX.to_wstring(), "9223372036854775807");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_char() {
|
||||
assert_eq!(Some(0), L!("abc").find_char('a'));
|
||||
assert_eq!(Some(1), L!("abc").find_char('b'));
|
||||
assert_eq!(None, L!("abc").find_char('X'));
|
||||
assert_eq!(None, L!("").find_char('X'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix() {
|
||||
assert!(L!("").starts_with(L!("")));
|
||||
assert!(L!("abc").starts_with(L!("")));
|
||||
assert!(L!("abc").starts_with('a'));
|
||||
assert!(L!("abc").starts_with("ab"));
|
||||
assert!(L!("abc").starts_with(L!("ab")));
|
||||
assert!(L!("abc").starts_with(&WString::from_str("abc")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_suffix() {
|
||||
assert!(L!("").ends_with(L!("")));
|
||||
assert!(L!("abc").ends_with(L!("")));
|
||||
assert!(L!("abc").ends_with('c'));
|
||||
assert!(L!("abc").ends_with("bc"));
|
||||
assert!(L!("abc").ends_with(L!("bc")));
|
||||
assert!(L!("abc").ends_with(&WString::from_str("abc")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split() {
|
||||
fn do_split(s: &wstr, c: char) -> Vec<&wstr> {
|
||||
s.split(c).collect()
|
||||
}
|
||||
assert_eq!(do_split(L!(""), 'b'), &[""]);
|
||||
assert_eq!(do_split(L!("abc"), 'b'), &["a", "c"]);
|
||||
assert_eq!(do_split(L!("xxb"), 'x'), &["", "", "b"]);
|
||||
assert_eq!(do_split(L!("bxxxb"), 'x'), &["b", "", "", "b"]);
|
||||
assert_eq!(do_split(L!(""), 'x'), &[""]);
|
||||
assert_eq!(do_split(L!("foo,bar,baz"), ','), &["foo", "bar", "baz"]);
|
||||
assert_eq!(do_split(L!("foobar"), ','), &["foobar"]);
|
||||
assert_eq!(do_split(L!("1,2,3,4,5"), ','), &["1", "2", "3", "4", "5"]);
|
||||
assert_eq!(
|
||||
do_split(L!("1,2,3,4,5,"), ','),
|
||||
&["1", "2", "3", "4", "5", ""]
|
||||
);
|
||||
assert_eq!(
|
||||
do_split(L!("Hello\nworld\nRust"), '\n'),
|
||||
&["Hello", "world", "Rust"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_prefix() {
|
||||
let needle = L!("hello");
|
||||
let haystack = L!("hello world");
|
||||
assert_eq!(haystack.find(needle), Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_one() {
|
||||
let needle = L!("ello");
|
||||
let haystack = L!("hello world");
|
||||
assert_eq!(haystack.find(needle), Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_suffix() {
|
||||
let needle = L!("world");
|
||||
let haystack = L!("hello world");
|
||||
assert_eq!(haystack.find(needle), Some(6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_none() {
|
||||
let needle = L!("worldz");
|
||||
let haystack = L!("hello world");
|
||||
assert_eq!(haystack.find(needle), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_none_larger() {
|
||||
// Notice that `haystack` and `needle` are reversed.
|
||||
let haystack = L!("world");
|
||||
let needle = L!("hello world");
|
||||
assert_eq!(haystack.find(needle), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_none_case_mismatch() {
|
||||
let haystack = L!("wOrld");
|
||||
let needle = L!("hello world");
|
||||
assert_eq!(haystack.find(needle), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trim_matches() {
|
||||
assert_eq!(L!("|foo|").trim_matches('|'), L!("foo"));
|
||||
assert_eq!(L!("<foo|").trim_matches('|'), L!("<foo"));
|
||||
assert_eq!(L!("|foo>").trim_matches('|'), L!("foo>"));
|
||||
}
|
||||
}
|
||||
20
crates/wcstringutil/Cargo.toml
Normal file
20
crates/wcstringutil/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "fish-wcstringutil"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
fish-fallback.workspace = true
|
||||
fish-widestring.workspace = true
|
||||
|
||||
# Only needed for cygwin detection.
|
||||
# TODO(MSRV>=1.86): remove
|
||||
[build-dependencies]
|
||||
fish-build-helper.workspace = true
|
||||
rsconf.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
3
crates/wcstringutil/build.rs
Normal file
3
crates/wcstringutil/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
rsconf::declare_cfg("cygwin", fish_build_helper::target_os_is_cygwin());
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
//! Helper functions for working with wcstring.
|
||||
|
||||
use crate::common::{get_ellipsis_char, get_ellipsis_str};
|
||||
use crate::prelude::*;
|
||||
use fish_fallback::{fish_wcwidth, lowercase, lowercase_rev, wcscasecmp, wcscasecmp_fuzzy};
|
||||
use fish_wchar::decode_byte_from_char;
|
||||
use fish_widestring::{ELLIPSIS_CHAR, prelude::*};
|
||||
|
||||
/// Return the number of newlines in a string.
|
||||
pub fn count_newlines(s: &wstr) -> usize {
|
||||
@@ -19,14 +17,24 @@ pub fn count_newlines(s: &wstr) -> usize {
|
||||
count
|
||||
}
|
||||
|
||||
fn is_prefix(mut lhs: impl Iterator<Item = char>, mut rhs: impl Iterator<Item = char>) -> bool {
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum IsPrefix {
|
||||
Prefix,
|
||||
Equal,
|
||||
}
|
||||
pub fn is_prefix(
|
||||
mut lhs: impl Iterator<Item = char>,
|
||||
mut rhs: impl Iterator<Item = char>,
|
||||
) -> Option<IsPrefix> {
|
||||
use IsPrefix::*;
|
||||
loop {
|
||||
match (lhs.next(), rhs.next()) {
|
||||
(None, _) => return true,
|
||||
(Some(_), None) => return false,
|
||||
(None, None) => return Some(Equal),
|
||||
(None, Some(_)) => return Some(Prefix),
|
||||
(Some(_), None) => return None,
|
||||
(Some(lhs), Some(rhs)) => {
|
||||
if lhs != rhs {
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +45,7 @@ fn is_prefix(mut lhs: impl Iterator<Item = char>, mut rhs: impl Iterator<Item =
|
||||
pub fn string_prefixes_string_case_insensitive(proposed_prefix: &wstr, value: &wstr) -> bool {
|
||||
let proposed_prefix = lowercase(proposed_prefix.chars());
|
||||
let value = lowercase(value.chars());
|
||||
is_prefix(proposed_prefix, value)
|
||||
is_prefix(proposed_prefix, value).is_some()
|
||||
}
|
||||
|
||||
pub fn string_prefixes_string_maybe_case_insensitive(
|
||||
@@ -64,7 +72,7 @@ pub fn strip_executable_suffix(path: &wstr) -> Option<&wstr> {
|
||||
pub fn string_suffixes_string_case_insensitive(proposed_suffix: &wstr, value: &wstr) -> bool {
|
||||
let proposed_suffix = lowercase_rev(proposed_suffix.chars());
|
||||
let value = lowercase_rev(value.chars());
|
||||
is_prefix(proposed_suffix, value)
|
||||
is_prefix(proposed_suffix, value).is_some()
|
||||
}
|
||||
|
||||
/// Test if a string prefixes another. Returns true if a is a prefix of b.
|
||||
@@ -138,7 +146,7 @@ fn fuzzy_canonicalize(c: char) -> char {
|
||||
// Note that the order of entries below affects the sort order of completions.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ContainType {
|
||||
/// Exact match: `foobar` matches `foo`
|
||||
/// Exact match
|
||||
Exact,
|
||||
/// Prefix match: `foo` matches `foobar`
|
||||
Prefix,
|
||||
@@ -213,7 +221,7 @@ pub fn try_create(
|
||||
// Helper to lazily compute if case insensitive matches should use icase or smartcase.
|
||||
// Use icase if the input contains any uppercase characters, smartcase otherwise.
|
||||
#[inline(always)]
|
||||
fn get_case_fold(s: &widestring::Utf32Str) -> CaseSensitivity {
|
||||
fn get_case_fold(s: &wstr) -> CaseSensitivity {
|
||||
if s.chars().any(|c| c.is_uppercase()) {
|
||||
CaseSensitivity::Insensitive
|
||||
} else {
|
||||
@@ -327,30 +335,6 @@ pub fn string_fuzzy_match_string(
|
||||
StringFuzzyMatch::try_create(string, match_against, anchor_start)
|
||||
}
|
||||
|
||||
/// Implementation of wcs2bytes that accepts a callback.
|
||||
/// The first argument can be either a `&str` or `&wstr`.
|
||||
/// This invokes `func` with byte slices containing the UTF-8 encoding of the characters in the
|
||||
/// input, doing one invocation per character.
|
||||
/// If `func` returns false, it stops; otherwise it continues.
|
||||
/// Return false if the callback returned false, otherwise true.
|
||||
pub fn str2bytes_callback(input: impl IntoCharIter, mut func: impl FnMut(&[u8]) -> bool) -> bool {
|
||||
// A `char` represents an Unicode scalar value, which takes up at most 4 bytes when encoded in UTF-8.
|
||||
let mut converted = [0_u8; 4];
|
||||
|
||||
for c in input.chars() {
|
||||
let bytes = if let Some(byte) = decode_byte_from_char(c) {
|
||||
converted[0] = byte;
|
||||
&converted[..=0]
|
||||
} else {
|
||||
c.encode_utf8(&mut converted).as_bytes()
|
||||
};
|
||||
if !func(bytes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Split a string by runs of any of the separator characters provided in `seps`.
|
||||
/// Note the delimiters are the characters in `seps`, not `seps` itself.
|
||||
/// `seps` may contain the NUL character.
|
||||
@@ -389,7 +373,7 @@ pub fn split_string_tok<'val>(
|
||||
pos = next_sep + 1;
|
||||
}
|
||||
if pos < end && max_results > 0 {
|
||||
assert!(out.len() + 1 == max_results, "Should have split the max");
|
||||
assert_eq!(out.len() + 1, max_results, "Should have split the max");
|
||||
out.push(wstr::from_char_slice(&val[pos..]));
|
||||
}
|
||||
assert!(out.len() <= max_results, "Got too many results");
|
||||
@@ -464,32 +448,13 @@ pub fn split_about<'haystack>(
|
||||
output
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum EllipsisType {
|
||||
None,
|
||||
// Prefer niceness over minimalness
|
||||
Prettiest,
|
||||
// Make every character count ($ instead of ...)
|
||||
Shortest,
|
||||
}
|
||||
|
||||
pub fn truncate(input: &wstr, max_len: usize, etype: Option<EllipsisType>) -> WString {
|
||||
let etype = etype.unwrap_or(EllipsisType::Prettiest);
|
||||
// TODO: This should work on render width rather than the number of codepoints.
|
||||
pub fn truncate(input: &wstr, max_len: usize) -> WString {
|
||||
if input.len() <= max_len {
|
||||
return input.to_owned();
|
||||
}
|
||||
|
||||
if etype == EllipsisType::None {
|
||||
return input[..max_len].to_owned();
|
||||
}
|
||||
if etype == EllipsisType::Prettiest {
|
||||
let ellipsis_str = get_ellipsis_str();
|
||||
let mut output = input[..max_len - ellipsis_str.len()].to_owned();
|
||||
output += ellipsis_str;
|
||||
return output;
|
||||
}
|
||||
let mut output = input[..max_len - 1].to_owned();
|
||||
output.push(get_ellipsis_char());
|
||||
output.push(ELLIPSIS_CHAR);
|
||||
output
|
||||
}
|
||||
|
||||
@@ -556,12 +521,12 @@ fn next(&mut self) -> Option<Self::Item> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Like fish_wcwidth, but returns 0 for characters with no real width instead of -1.
|
||||
/// Like fish_wcwidth, but returns 0 for characters with no real width instead of none.
|
||||
pub fn fish_wcwidth_visible(c: char) -> isize {
|
||||
if c == '\x08' {
|
||||
return -1;
|
||||
}
|
||||
fish_wcwidth(c).max(0)
|
||||
fish_wcwidth(c).unwrap_or_default().try_into().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -571,7 +536,7 @@ mod tests {
|
||||
split_string_tok, string_fuzzy_match_string, string_prefixes_string_case_insensitive,
|
||||
string_suffixes_string_case_insensitive,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use fish_widestring::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_string_prefixes_string_case_insensitive() {
|
||||
@@ -589,6 +554,8 @@ macro_rules! validate {
|
||||
validate!("i", "İ", true);
|
||||
validate!("gs", "gs_", true);
|
||||
validate!("gs_", "gs", false);
|
||||
assert_eq!("İn".to_lowercase().as_str(), "i\u{307}n");
|
||||
validate!("echo in", "echo İnstall", false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
15
crates/wgetopt/Cargo.toml
Normal file
15
crates/wgetopt/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "fish-wgetopt"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
assert_matches.workspace = true
|
||||
fish-wcstringutil.workspace = true
|
||||
fish-widestring.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -24,7 +24,8 @@
|
||||
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
|
||||
Cambridge, MA 02139, USA. */
|
||||
|
||||
use crate::prelude::*;
|
||||
use assert_matches::assert_matches;
|
||||
use fish_widestring::prelude::*;
|
||||
|
||||
/// Special char used with [`Ordering::ReturnInOrder`].
|
||||
pub const NON_OPTION_CHAR: char = '\x01';
|
||||
@@ -397,7 +398,7 @@ fn update_long_opt(
|
||||
option_index: usize,
|
||||
) -> char {
|
||||
self.wopt_index += 1;
|
||||
assert!(matches!(self.remaining_text.char_at(name_end), '\0' | '='));
|
||||
assert_matches!(self.remaining_text.char_at(name_end), '\0' | '=');
|
||||
|
||||
if self.remaining_text.char_at(name_end) == '=' {
|
||||
if opt_found.arg_type == ArgType::NoArgument {
|
||||
@@ -566,9 +567,9 @@ fn wgetopt_inner(&mut self, longopt_index: &mut usize) -> Option<char> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ArgType, WGetopter, WOption, wopt};
|
||||
use crate::prelude::*;
|
||||
use crate::wcstringutil::join_strings;
|
||||
use super::{ArgType, WGetopter, wopt};
|
||||
use fish_wcstringutil::join_strings;
|
||||
use fish_widestring::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_exchange() {
|
||||
@@ -611,8 +612,8 @@ fn test_exchange() {
|
||||
#[test]
|
||||
fn test_wgetopt() {
|
||||
// Regression test for a crash.
|
||||
const short_options: &wstr = L!("-a");
|
||||
const long_options: &[WOption] = &[wopt(L!("add"), ArgType::NoArgument, 'a')];
|
||||
let short_options = L!("-a");
|
||||
let long_options = &[wopt(L!("add"), ArgType::NoArgument, 'a')];
|
||||
let mut argv = [
|
||||
L!("abbr"),
|
||||
L!("--add"),
|
||||
15
crates/widestring/Cargo.toml
Normal file
15
crates/widestring/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "fish-widestring"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
libc.workspace = true
|
||||
unicode-width.workspace = true
|
||||
widestring.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
1069
crates/widestring/src/lib.rs
Normal file
1069
crates/widestring/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
391
crates/widestring/src/word_char.rs
Normal file
391
crates/widestring/src/word_char.rs
Normal file
@@ -0,0 +1,391 @@
|
||||
//! Support for character classification for vi-mode word movements
|
||||
|
||||
use std::{cmp::Ordering, ops::RangeInclusive};
|
||||
|
||||
/// Character class for word movements
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WordCharClass {
|
||||
Blank, // whitespace
|
||||
Newline, // newline
|
||||
Punctuation, // punctuation and symbols
|
||||
Word, // word character
|
||||
Emoji, // emoji
|
||||
Superscript, // superscript (U+2070-U+207F)
|
||||
Subscript, // subscript (U+2080-U+2094)
|
||||
Braille, // braille (U+2800-U+28FF)
|
||||
Hiragana, // Hiragana (U+3040-U+309F)
|
||||
Katakana, // Katakana (U+30A0-U+30FF)
|
||||
Cjk, // CJK Ideographs
|
||||
Hangul, // Hangul Syllables
|
||||
}
|
||||
|
||||
pub fn is_blank(c: char) -> bool {
|
||||
WordCharClass::from_char(c) == WordCharClass::Blank
|
||||
}
|
||||
|
||||
impl WordCharClass {
|
||||
/// Reference: <https://github.com/vim/vim/blob/48940d94/src/mbyte.c#L2866-L2982>
|
||||
pub fn from_char(c: char) -> Self {
|
||||
// Quick check for Latin1 characters
|
||||
if u32::from(c) < 0x100 {
|
||||
// newline
|
||||
if c == '\n' {
|
||||
return WordCharClass::Newline;
|
||||
}
|
||||
// space, tab, NUL, or non-breaking space
|
||||
if matches!(c, ' ' | '\t' | '\0' | '\u{a0}' /* NBSP */) {
|
||||
return WordCharClass::Blank;
|
||||
}
|
||||
if is_latin1_word_char(c) {
|
||||
return WordCharClass::Word;
|
||||
}
|
||||
return WordCharClass::Punctuation;
|
||||
}
|
||||
|
||||
// emoji check
|
||||
if is_emoji(c) {
|
||||
return WordCharClass::Emoji;
|
||||
}
|
||||
|
||||
// binary search in table
|
||||
CLASSES
|
||||
.binary_search_by(|interval| compare_range_to_char(&interval.range, c))
|
||||
.map_or(
|
||||
// most other characters are "word" characters
|
||||
WordCharClass::Word,
|
||||
|i| CLASSES[i].class,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if codepoint is a word character (alphanumeric)
|
||||
/// Note: Different from vim default behavior, we do not regard underscore as a word character!
|
||||
fn is_latin1_word_char(c: char) -> bool {
|
||||
c.is_ascii_alphanumeric() || (matches!( c, | 'À'..='ÿ') && c != '×' && c != '÷')
|
||||
}
|
||||
|
||||
fn compare_range_to_char(range: &RangeInclusive<char>, c: char) -> Ordering {
|
||||
if *range.end() < c {
|
||||
Ordering::Less
|
||||
} else if *range.start() > c {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Equal
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if codepoint is in emoji table using binary search (like vim's intable)
|
||||
fn is_emoji(c: char) -> bool {
|
||||
EMOJI_ALL
|
||||
.binary_search_by(|range| compare_range_to_char(range, c))
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Character class interval
|
||||
struct ClassInterval {
|
||||
range: RangeInclusive<char>,
|
||||
class: WordCharClass,
|
||||
}
|
||||
|
||||
impl ClassInterval {
|
||||
const fn new(range: RangeInclusive<char>, class: WordCharClass) -> Self {
|
||||
Self { range, class }
|
||||
}
|
||||
|
||||
const fn single(c: char, class: WordCharClass) -> Self {
|
||||
Self::new(c..=c, class)
|
||||
}
|
||||
}
|
||||
|
||||
/// Character classification table (sorted non-overlapping intervals)
|
||||
/// Reference: <https://github.com/vim/vim/blob/48940d94/src/mbyte.c#L2867-L2948>
|
||||
static CLASSES: &[ClassInterval] = {
|
||||
use ClassInterval as I;
|
||||
use WordCharClass::*;
|
||||
&[
|
||||
I::single('\u{037e}', Punctuation), // Greek question mark
|
||||
I::single('\u{0387}', Punctuation), // Greek ano teleia
|
||||
I::new('\u{055a}'..='\u{055f}', Punctuation), // Armenian punctuation
|
||||
I::single('\u{0589}', Punctuation), // Armenian full stop
|
||||
I::single('\u{05be}', Punctuation),
|
||||
I::single('\u{05c0}', Punctuation),
|
||||
I::single('\u{05c3}', Punctuation),
|
||||
I::new('\u{05f3}'..='\u{05f4}', Punctuation),
|
||||
I::single('\u{060c}', Punctuation),
|
||||
I::single('\u{061b}', Punctuation),
|
||||
I::single('\u{061f}', Punctuation),
|
||||
I::new('\u{066a}'..='\u{066d}', Punctuation),
|
||||
I::single('\u{06d4}', Punctuation),
|
||||
I::new('\u{0700}'..='\u{070d}', Punctuation), // Syriac punctuation
|
||||
I::new('\u{0964}'..='\u{0965}', Punctuation),
|
||||
I::single('\u{0970}', Punctuation),
|
||||
I::single('\u{0df4}', Punctuation),
|
||||
I::single('\u{0e4f}', Punctuation),
|
||||
I::new('\u{0e5a}'..='\u{0e5b}', Punctuation),
|
||||
I::new('\u{0f04}'..='\u{0f12}', Punctuation),
|
||||
I::new('\u{0f3a}'..='\u{0f3d}', Punctuation),
|
||||
I::single('\u{0f85}', Punctuation),
|
||||
I::new('\u{104a}'..='\u{104f}', Punctuation), // Myanmar punctuation
|
||||
I::single('\u{10fb}', Punctuation), // Georgian punctuation
|
||||
I::new('\u{1361}'..='\u{1368}', Punctuation), // Ethiopic punctuation
|
||||
I::new('\u{166d}'..='\u{166e}', Punctuation), // Canadian Syl. punctuation
|
||||
I::single('\u{1680}', Blank),
|
||||
I::new('\u{169b}'..='\u{169c}', Punctuation),
|
||||
I::new('\u{16eb}'..='\u{16ed}', Punctuation),
|
||||
I::new('\u{1735}'..='\u{1736}', Punctuation),
|
||||
I::new('\u{17d4}'..='\u{17dc}', Punctuation), // Khmer punctuation
|
||||
I::new('\u{1800}'..='\u{180a}', Punctuation), // Mongolian punctuation
|
||||
I::new('\u{2000}'..='\u{200b}', Blank), // spaces
|
||||
I::new('\u{200c}'..='\u{2027}', Punctuation), // punctuation and symbols
|
||||
I::new('\u{2028}'..='\u{2029}', Blank),
|
||||
I::new('\u{202a}'..='\u{202e}', Punctuation), // punctuation and symbols
|
||||
I::single('\u{202f}', Blank),
|
||||
I::new('\u{2030}'..='\u{205e}', Punctuation), // punctuation and symbols
|
||||
I::single('\u{205f}', Blank),
|
||||
I::new('\u{2060}'..='\u{206f}', Punctuation), // punctuation and symbols
|
||||
I::new('\u{2070}'..='\u{207f}', Superscript),
|
||||
I::new('\u{2080}'..='\u{2094}', Subscript),
|
||||
I::new('\u{20a0}'..='\u{27ff}', Punctuation), // all kinds of symbols
|
||||
I::new('\u{2800}'..='\u{28ff}', Braille),
|
||||
I::new('\u{2900}'..='\u{2998}', Punctuation), // arrows, brackets, etc.
|
||||
I::new('\u{29d8}'..='\u{29db}', Punctuation),
|
||||
I::new('\u{29fc}'..='\u{29fd}', Punctuation),
|
||||
I::new('\u{2e00}'..='\u{2e7f}', Punctuation), // supplemental punctuation
|
||||
I::single('\u{3000}', Blank), // ideographic space
|
||||
I::new('\u{3001}'..='\u{3020}', Punctuation), // ideographic punctuation
|
||||
I::single('\u{3030}', Punctuation),
|
||||
I::single('\u{303d}', Punctuation),
|
||||
I::new('\u{3040}'..='\u{309f}', Hiragana),
|
||||
I::new('\u{30a0}'..='\u{30ff}', Katakana),
|
||||
I::new('\u{3300}'..='\u{9fff}', Cjk),
|
||||
I::new('\u{ac00}'..='\u{d7a3}', Hangul),
|
||||
I::new('\u{f900}'..='\u{faff}', Cjk),
|
||||
I::new('\u{fd3e}'..='\u{fd3f}', Punctuation),
|
||||
I::new('\u{fe30}'..='\u{fe6b}', Punctuation), // punctuation forms
|
||||
I::new('\u{ff00}'..='\u{ff0f}', Punctuation), // half/fullwidth ASCII
|
||||
I::new('\u{ff1a}'..='\u{ff20}', Punctuation), // half/fullwidth ASCII
|
||||
I::new('\u{ff3b}'..='\u{ff40}', Punctuation), // half/fullwidth ASCII
|
||||
I::new('\u{ff5b}'..='\u{ff65}', Punctuation), // half/fullwidth ASCII
|
||||
I::new('\u{1d000}'..='\u{1d24f}', Punctuation), // Musical notation
|
||||
I::new('\u{1d400}'..='\u{1d7ff}', Punctuation), // Mathematical Alphanumeric Symbols
|
||||
I::new('\u{1f000}'..='\u{1f2ff}', Punctuation), // Game pieces; enclosed characters
|
||||
I::new('\u{1f300}'..='\u{1f9ff}', Punctuation), // Many symbol blocks
|
||||
I::new('\u{20000}'..='\u{2a6df}', Cjk),
|
||||
I::new('\u{2a700}'..='\u{2b73f}', Cjk),
|
||||
I::new('\u{2b740}'..='\u{2b81f}', Cjk),
|
||||
I::new('\u{2f800}'..='\u{2fa1f}', Cjk),
|
||||
]
|
||||
};
|
||||
|
||||
/// Reference: <https://github.com/vim/vim/blob/48940d94/src/mbyte.c#L2704-L2852>
|
||||
static EMOJI_ALL: &[RangeInclusive<char>] = &[
|
||||
'\u{203c}'..='\u{203c}',
|
||||
'\u{2049}'..='\u{2049}',
|
||||
'\u{2122}'..='\u{2122}',
|
||||
'\u{2139}'..='\u{2139}',
|
||||
'\u{2194}'..='\u{2199}',
|
||||
'\u{21a9}'..='\u{21aa}',
|
||||
'\u{231a}'..='\u{231b}',
|
||||
'\u{2328}'..='\u{2328}',
|
||||
'\u{23cf}'..='\u{23cf}',
|
||||
'\u{23e9}'..='\u{23f3}',
|
||||
'\u{23f8}'..='\u{23fa}',
|
||||
'\u{24c2}'..='\u{24c2}',
|
||||
'\u{25aa}'..='\u{25ab}',
|
||||
'\u{25b6}'..='\u{25b6}',
|
||||
'\u{25c0}'..='\u{25c0}',
|
||||
'\u{25fb}'..='\u{25fe}',
|
||||
'\u{2600}'..='\u{2604}',
|
||||
'\u{260e}'..='\u{260e}',
|
||||
'\u{2611}'..='\u{2611}',
|
||||
'\u{2614}'..='\u{2615}',
|
||||
'\u{2618}'..='\u{2618}',
|
||||
'\u{261d}'..='\u{261d}',
|
||||
'\u{2620}'..='\u{2620}',
|
||||
'\u{2622}'..='\u{2623}',
|
||||
'\u{2626}'..='\u{2626}',
|
||||
'\u{262a}'..='\u{262a}',
|
||||
'\u{262e}'..='\u{262f}',
|
||||
'\u{2638}'..='\u{263a}',
|
||||
'\u{2640}'..='\u{2640}',
|
||||
'\u{2642}'..='\u{2642}',
|
||||
'\u{2648}'..='\u{2653}',
|
||||
'\u{265f}'..='\u{2660}',
|
||||
'\u{2663}'..='\u{2663}',
|
||||
'\u{2665}'..='\u{2666}',
|
||||
'\u{2668}'..='\u{2668}',
|
||||
'\u{267b}'..='\u{267b}',
|
||||
'\u{267e}'..='\u{267f}',
|
||||
'\u{2692}'..='\u{2697}',
|
||||
'\u{2699}'..='\u{2699}',
|
||||
'\u{269b}'..='\u{269c}',
|
||||
'\u{26a0}'..='\u{26a1}',
|
||||
'\u{26a7}'..='\u{26a7}',
|
||||
'\u{26aa}'..='\u{26ab}',
|
||||
'\u{26b0}'..='\u{26b1}',
|
||||
'\u{26bd}'..='\u{26be}',
|
||||
'\u{26c4}'..='\u{26c5}',
|
||||
'\u{26c8}'..='\u{26c8}',
|
||||
'\u{26ce}'..='\u{26cf}',
|
||||
'\u{26d1}'..='\u{26d1}',
|
||||
'\u{26d3}'..='\u{26d4}',
|
||||
'\u{26e9}'..='\u{26ea}',
|
||||
'\u{26f0}'..='\u{26f5}',
|
||||
'\u{26f7}'..='\u{26fa}',
|
||||
'\u{26fd}'..='\u{26fd}',
|
||||
'\u{2702}'..='\u{2702}',
|
||||
'\u{2705}'..='\u{2705}',
|
||||
'\u{2708}'..='\u{270d}',
|
||||
'\u{270f}'..='\u{270f}',
|
||||
'\u{2712}'..='\u{2712}',
|
||||
'\u{2714}'..='\u{2714}',
|
||||
'\u{2716}'..='\u{2716}',
|
||||
'\u{271d}'..='\u{271d}',
|
||||
'\u{2721}'..='\u{2721}',
|
||||
'\u{2728}'..='\u{2728}',
|
||||
'\u{2733}'..='\u{2734}',
|
||||
'\u{2744}'..='\u{2744}',
|
||||
'\u{2747}'..='\u{2747}',
|
||||
'\u{274c}'..='\u{274c}',
|
||||
'\u{274e}'..='\u{274e}',
|
||||
'\u{2753}'..='\u{2755}',
|
||||
'\u{2757}'..='\u{2757}',
|
||||
'\u{2763}'..='\u{2764}',
|
||||
'\u{2795}'..='\u{2797}',
|
||||
'\u{27a1}'..='\u{27a1}',
|
||||
'\u{27b0}'..='\u{27b0}',
|
||||
'\u{27bf}'..='\u{27bf}',
|
||||
'\u{2934}'..='\u{2935}',
|
||||
'\u{2b05}'..='\u{2b07}',
|
||||
'\u{2b1b}'..='\u{2b1c}',
|
||||
'\u{2b50}'..='\u{2b50}',
|
||||
'\u{2b55}'..='\u{2b55}',
|
||||
'\u{3030}'..='\u{3030}',
|
||||
'\u{303d}'..='\u{303d}',
|
||||
'\u{3297}'..='\u{3297}',
|
||||
'\u{3299}'..='\u{3299}',
|
||||
'\u{1f004}'..='\u{1f004}',
|
||||
'\u{1f0cf}'..='\u{1f0cf}',
|
||||
'\u{1f170}'..='\u{1f171}',
|
||||
'\u{1f17e}'..='\u{1f17f}',
|
||||
'\u{1f18e}'..='\u{1f18e}',
|
||||
'\u{1f191}'..='\u{1f19a}',
|
||||
'\u{1f1e6}'..='\u{1f1ff}',
|
||||
'\u{1f201}'..='\u{1f202}',
|
||||
'\u{1f21a}'..='\u{1f21a}',
|
||||
'\u{1f22f}'..='\u{1f22f}',
|
||||
'\u{1f232}'..='\u{1f23a}',
|
||||
'\u{1f250}'..='\u{1f251}',
|
||||
'\u{1f300}'..='\u{1f321}',
|
||||
'\u{1f324}'..='\u{1f393}',
|
||||
'\u{1f396}'..='\u{1f397}',
|
||||
'\u{1f399}'..='\u{1f39b}',
|
||||
'\u{1f39e}'..='\u{1f3f0}',
|
||||
'\u{1f3f3}'..='\u{1f3f5}',
|
||||
'\u{1f3f7}'..='\u{1f4fd}',
|
||||
'\u{1f4ff}'..='\u{1f53d}',
|
||||
'\u{1f549}'..='\u{1f54e}',
|
||||
'\u{1f550}'..='\u{1f567}',
|
||||
'\u{1f56f}'..='\u{1f570}',
|
||||
'\u{1f573}'..='\u{1f57a}',
|
||||
'\u{1f587}'..='\u{1f587}',
|
||||
'\u{1f58a}'..='\u{1f58d}',
|
||||
'\u{1f590}'..='\u{1f590}',
|
||||
'\u{1f595}'..='\u{1f596}',
|
||||
'\u{1f5a4}'..='\u{1f5a5}',
|
||||
'\u{1f5a8}'..='\u{1f5a8}',
|
||||
'\u{1f5b1}'..='\u{1f5b2}',
|
||||
'\u{1f5bc}'..='\u{1f5bc}',
|
||||
'\u{1f5c2}'..='\u{1f5c4}',
|
||||
'\u{1f5d1}'..='\u{1f5d3}',
|
||||
'\u{1f5dc}'..='\u{1f5de}',
|
||||
'\u{1f5e1}'..='\u{1f5e1}',
|
||||
'\u{1f5e3}'..='\u{1f5e3}',
|
||||
'\u{1f5e8}'..='\u{1f5e8}',
|
||||
'\u{1f5ef}'..='\u{1f5ef}',
|
||||
'\u{1f5f3}'..='\u{1f5f3}',
|
||||
'\u{1f5fa}'..='\u{1f64f}',
|
||||
'\u{1f680}'..='\u{1f6c5}',
|
||||
'\u{1f6cb}'..='\u{1f6d2}',
|
||||
'\u{1f6d5}'..='\u{1f6d7}',
|
||||
'\u{1f6dc}'..='\u{1f6e5}',
|
||||
'\u{1f6e9}'..='\u{1f6e9}',
|
||||
'\u{1f6eb}'..='\u{1f6ec}',
|
||||
'\u{1f6f0}'..='\u{1f6f0}',
|
||||
'\u{1f6f3}'..='\u{1f6fc}',
|
||||
'\u{1f7e0}'..='\u{1f7eb}',
|
||||
'\u{1f7f0}'..='\u{1f7f0}',
|
||||
'\u{1f90c}'..='\u{1f93a}',
|
||||
'\u{1f93c}'..='\u{1f945}',
|
||||
'\u{1f947}'..='\u{1f9ff}',
|
||||
'\u{1fa70}'..='\u{1fa7c}',
|
||||
'\u{1fa80}'..='\u{1fa88}',
|
||||
'\u{1fa90}'..='\u{1fabd}',
|
||||
'\u{1fabf}'..='\u{1fac5}',
|
||||
'\u{1face}'..='\u{1fadb}',
|
||||
'\u{1fae0}'..='\u{1fae8}',
|
||||
'\u{1faf0}'..='\u{1faf8}',
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_blank() {
|
||||
assert_eq!(WordCharClass::from_char(' '), WordCharClass::Blank); // space
|
||||
assert_eq!(WordCharClass::from_char('\t'), WordCharClass::Blank); // tab
|
||||
assert_eq!(WordCharClass::from_char('\0'), WordCharClass::Blank); // NUL
|
||||
assert_eq!(WordCharClass::from_char('\u{a0}'), WordCharClass::Blank); // non-breaking space
|
||||
assert_eq!(WordCharClass::from_char('\u{3000}'), WordCharClass::Blank); // ideographic space
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_word() {
|
||||
assert_eq!(WordCharClass::from_char('a'), WordCharClass::Word); // 'a'
|
||||
assert_eq!(WordCharClass::from_char('Z'), WordCharClass::Word); // 'Z'
|
||||
assert_eq!(WordCharClass::from_char('0'), WordCharClass::Word); // '0'
|
||||
assert_eq!(WordCharClass::from_char('e'), WordCharClass::Word); // 'e' with acute
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_punctuation() {
|
||||
assert_eq!(WordCharClass::from_char('.'), WordCharClass::Punctuation);
|
||||
assert_eq!(WordCharClass::from_char(','), WordCharClass::Punctuation);
|
||||
assert_eq!(WordCharClass::from_char(';'), WordCharClass::Punctuation);
|
||||
assert_eq!(WordCharClass::from_char(','), WordCharClass::Punctuation); // ideographic comma
|
||||
assert_eq!(WordCharClass::from_char('_'), WordCharClass::Punctuation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cjk() {
|
||||
assert_eq!(WordCharClass::from_char('中'), WordCharClass::Cjk); // CJK character
|
||||
assert_eq!(WordCharClass::from_char('𠀀'), WordCharClass::Cjk); // CJK Extension B
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_japanese() {
|
||||
assert_eq!(WordCharClass::from_char('あ'), WordCharClass::Hiragana);
|
||||
assert_eq!(WordCharClass::from_char('ア'), WordCharClass::Katakana);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hangul() {
|
||||
assert_eq!(WordCharClass::from_char('한'), WordCharClass::Hangul);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emoji() {
|
||||
assert_eq!(WordCharClass::from_char('😀'), WordCharClass::Emoji); // grinning face
|
||||
assert_eq!(WordCharClass::from_char('🚀'), WordCharClass::Emoji); // rocket
|
||||
assert_eq!(WordCharClass::from_char('❤'), WordCharClass::Emoji); // red heart
|
||||
assert_eq!(WordCharClass::from_char('✅'), WordCharClass::Emoji); // check mark
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_special() {
|
||||
assert_eq!(WordCharClass::from_char('⁰'), WordCharClass::Superscript); // superscript zero
|
||||
assert_eq!(WordCharClass::from_char('₀'), WordCharClass::Subscript); // subscript zero
|
||||
assert_eq!(WordCharClass::from_char('\u{2800}'), WordCharClass::Braille); // braille blank
|
||||
}
|
||||
}
|
||||
18
crates/xtask/Cargo.toml
Normal file
18
crates/xtask/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "xtask"
|
||||
version = "0.0.0"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anstyle.workspace = true
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
fish-build-helper.workspace = true
|
||||
fish-common.workspace = true
|
||||
fish-tempfile.workspace = true
|
||||
fish-widestring.workspace = true
|
||||
ignore.workspace = true
|
||||
pcre2.workspace = true
|
||||
walkdir.workspace = true
|
||||
182
crates/xtask/src/format.rs
Normal file
182
crates/xtask/src/format.rs
Normal file
@@ -0,0 +1,182 @@
|
||||
use anstyle::{AnsiColor, Style};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use clap::Args;
|
||||
use std::{
|
||||
io::{ErrorKind, Write},
|
||||
path::PathBuf,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use crate::files_with_extension;
|
||||
|
||||
const GREEN: Style = AnsiColor::Green.on_default();
|
||||
const YELLOW: Style = AnsiColor::Yellow.on_default();
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct FormatArgs {
|
||||
/// Consider all eligible files.
|
||||
#[arg(long)]
|
||||
all: bool,
|
||||
/// Report files which are not formatted as expected, without modifying any files.
|
||||
#[arg(long)]
|
||||
check: bool,
|
||||
/// Format files even if uncommitted changes are detected.
|
||||
#[arg(long)]
|
||||
force: bool,
|
||||
paths: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
pub fn format(args: FormatArgs) -> Result<()> {
|
||||
if !args.all && args.paths.is_empty() {
|
||||
println!(
|
||||
"{YELLOW}warning: No paths specified. Nothing to do. Use the \"--all\" flag to consider all eligible files.{YELLOW:#}"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
if !args.force && !args.check {
|
||||
match Command::new("git")
|
||||
.args(["status", "--porcelain", "--short", "--untracked-files=all"])
|
||||
.output()
|
||||
{
|
||||
Ok(output) => {
|
||||
if !output.stdout.is_empty() {
|
||||
std::io::stdout()
|
||||
.write_all(&output.stdout)
|
||||
.context("Could not write to stdout.")?;
|
||||
print!(
|
||||
"You have uncommitted changes (listed above). Are you sure you want to format? (y/N): "
|
||||
);
|
||||
std::io::stdout()
|
||||
.flush()
|
||||
.context("Could not flush stdout.")?;
|
||||
let mut response = String::new();
|
||||
std::io::stdin()
|
||||
.read_line(&mut response)
|
||||
.context("Could not read from stdin.")?;
|
||||
if response.trim_end() != "y" {
|
||||
println!("Exiting without formatting.");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() == ErrorKind::NotFound {
|
||||
println!(
|
||||
"{YELLOW}warning: Did not find git, will proceed without checking for unstaged changes.{YELLOW:#}"
|
||||
)
|
||||
} else {
|
||||
bail!("Failed to run git status:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
format_fish(&args)?;
|
||||
format_python(&args)?;
|
||||
format_rust(&args)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_formatter(formatter: &mut Command, name: &str) -> Result<()> {
|
||||
println!("=== Running {GREEN}{name}{GREEN:#}");
|
||||
match formatter.status() {
|
||||
Ok(exit_status) => {
|
||||
if exit_status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("{name:?}: Files are not formatted correctly.");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
eprintln!(
|
||||
"{YELLOW}Formatter not found: {name:?}. Skipping associated files.{YELLOW:#}"
|
||||
);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(e).with_context(|| format!("Error occurred while running {name:?}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_fish(args: &FormatArgs) -> Result<()> {
|
||||
let mut fish_paths = files_with_extension(&args.paths, "fish")?;
|
||||
if args.all {
|
||||
let workspace_root = fish_build_helper::workspace_root();
|
||||
let fish_formatting_dirs = ["benchmarks", "build_tools", "etc", "share"];
|
||||
fish_paths.extend(files_with_extension(
|
||||
fish_formatting_dirs
|
||||
.iter()
|
||||
.map(|dir_name| workspace_root.join(dir_name)),
|
||||
"fish",
|
||||
)?);
|
||||
};
|
||||
if fish_paths.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
// TODO: make `fish_indent` available as a Rust library function, to avoid needing a
|
||||
// `fish_indent` binary in `$PATH`.
|
||||
let mut formatter = Command::new("fish_indent");
|
||||
if args.check {
|
||||
formatter.arg("--check");
|
||||
} else {
|
||||
formatter.arg("-w");
|
||||
}
|
||||
formatter.arg("--");
|
||||
formatter.args(fish_paths);
|
||||
run_formatter(&mut formatter, "fish_indent")
|
||||
}
|
||||
|
||||
fn format_python(args: &FormatArgs) -> Result<()> {
|
||||
let mut formatter = Command::new("ruff");
|
||||
formatter.arg("format");
|
||||
if args.check {
|
||||
formatter.arg("--check");
|
||||
}
|
||||
let mut python_files = files_with_extension(&args.paths, "py")?;
|
||||
|
||||
if args.all {
|
||||
python_files.push(fish_build_helper::workspace_root().to_owned());
|
||||
};
|
||||
if python_files.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
formatter.args(python_files);
|
||||
run_formatter(&mut formatter, "ruff format")
|
||||
}
|
||||
|
||||
fn format_rust(args: &FormatArgs) -> Result<()> {
|
||||
let rustfmt_status = Command::new("cargo")
|
||||
.arg("fmt")
|
||||
.arg("--version")
|
||||
.stdout(Stdio::null())
|
||||
.status()
|
||||
.context("Failed to run cargo")?;
|
||||
if !rustfmt_status.success() {
|
||||
eprintln!(
|
||||
"{YELLOW}Please install \"rustfmt\" to format Rust, e.g. via:\n\
|
||||
rustup component add rustfmt{YELLOW:#}"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
if args.all {
|
||||
let mut formatter = Command::new("cargo");
|
||||
formatter.arg("fmt");
|
||||
formatter.arg("--all");
|
||||
if args.check {
|
||||
formatter.arg("--check");
|
||||
}
|
||||
run_formatter(&mut formatter, "cargo fmt")?;
|
||||
}
|
||||
let rust_files = files_with_extension(&args.paths, "rs")?;
|
||||
if rust_files.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut formatter = Command::new("rustfmt");
|
||||
if args.check {
|
||||
formatter.arg("--check");
|
||||
formatter.arg("--files-with-diff");
|
||||
}
|
||||
formatter.args(rust_files);
|
||||
run_formatter(&mut formatter, "rustfmt")
|
||||
}
|
||||
548
crates/xtask/src/gettext.rs
Normal file
548
crates/xtask/src/gettext.rs
Normal file
@@ -0,0 +1,548 @@
|
||||
use crate::{CARGO, CommandExt, files_with_extension};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use clap::{Args, Subcommand};
|
||||
use fish_build_helper::po_dir;
|
||||
use pcre2::bytes::Regex;
|
||||
use std::{
|
||||
fs::OpenOptions,
|
||||
io::{Write as _, stdout},
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
thread::spawn,
|
||||
};
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct GettextArgs {
|
||||
/// Path to the directory into which the messages from the Rust sources have been extracted.
|
||||
/// If this is not specified, fish will be compiled with the `gettext-extract` feature to
|
||||
/// obtain the messages.
|
||||
#[arg(long)]
|
||||
rust_extraction_dir: Option<PathBuf>,
|
||||
|
||||
#[command(subcommand)]
|
||||
task: Task,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Task {
|
||||
/// Check whether the PO files are up to date.
|
||||
/// Prints a diff and exits non-zero if they are outdated.
|
||||
/// Considers all our PO files by default, also allows explicitly specifying which files to
|
||||
/// consider.
|
||||
Check { paths: Vec<PathBuf> },
|
||||
/// Add a PO file for a new language.
|
||||
New {
|
||||
/// An ISO 639-1 language identifier (ISO 639-2 if the former does not exits),
|
||||
/// optionally followed by and underscore and an ISO 3166-1 country code to specify the variant,
|
||||
/// e.g. `de` or `pt_BR`.
|
||||
language: String,
|
||||
},
|
||||
/// Update PO files.
|
||||
/// This will delete entries for msgids which are no longer used in the sources and introduce
|
||||
/// new, untranslated entries for messages which do not have an entry yet.
|
||||
/// This tool should run every time a change to the messages localized via gettext occurs,
|
||||
/// including fish script files, where many strings are implicitly localized.
|
||||
/// Considers all our PO files by default, also allows explicitly specifying which files to
|
||||
/// consider.
|
||||
Update { paths: Vec<PathBuf> },
|
||||
}
|
||||
|
||||
fn get_po_paths<P: AsRef<Path>>(specified_paths: &[P]) -> Result<Vec<PathBuf>> {
|
||||
let extension = "po";
|
||||
if specified_paths.is_empty() {
|
||||
files_with_extension([po_dir()], extension)
|
||||
} else {
|
||||
files_with_extension(specified_paths, extension)
|
||||
}
|
||||
}
|
||||
|
||||
fn update_po_file<P: AsRef<Path>, Q: AsRef<Path>>(file_to_update: P, template: Q) -> Result<()> {
|
||||
Command::new("msgmerge")
|
||||
.args([
|
||||
"--no-wrap",
|
||||
"--update",
|
||||
"--no-fuzzy-matching",
|
||||
"--backup=none",
|
||||
"--quiet",
|
||||
])
|
||||
.arg(file_to_update.as_ref())
|
||||
.arg(template.as_ref())
|
||||
.run()?;
|
||||
let msgattrib_output_file = fish_tempfile::new_file().context("Failed to create temp file")?;
|
||||
Command::new("msgattrib")
|
||||
.args(["--no-wrap", "--no-obsolete"])
|
||||
.arg("-o")
|
||||
.arg(msgattrib_output_file.path())
|
||||
.arg(file_to_update.as_ref())
|
||||
.run()?;
|
||||
crate::copy_file(msgattrib_output_file.path(), file_to_update.as_ref())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gettext(args: GettextArgs) -> Result<()> {
|
||||
let template = match args.rust_extraction_dir {
|
||||
Some(dir) => template::Template::new(dir)?,
|
||||
None => {
|
||||
let temp_dir = fish_tempfile::new_dir().context("Failed to create temp file")?;
|
||||
Command::new(CARGO)
|
||||
.args([
|
||||
"check",
|
||||
"--workspace",
|
||||
"--all-targets",
|
||||
"--features=gettext-extract",
|
||||
])
|
||||
.env("FISH_GETTEXT_EXTRACTION_DIR", temp_dir.path())
|
||||
.run()?;
|
||||
template::Template::new(temp_dir.path())?
|
||||
}
|
||||
};
|
||||
let mut template_file = fish_tempfile::new_file().context("Failed to create temp file")?;
|
||||
template_file
|
||||
.get_mut()
|
||||
.write_all(template.serialize())
|
||||
.with_context(|| format!("Failed to write to temp file {:?}", template_file.path()))?;
|
||||
template_file
|
||||
.get_mut()
|
||||
.flush()
|
||||
.with_context(|| format!("Failed to flush temporary file {:?}", template_file.path()))?;
|
||||
|
||||
match args.task {
|
||||
Task::Check { paths } => {
|
||||
let mut thread_handles = vec![];
|
||||
for path in get_po_paths(&paths)? {
|
||||
let template_path_buf = template_file.path().to_owned();
|
||||
let handle = spawn(move || -> Result<Option<Vec<u8>>> {
|
||||
let tmp_copy =
|
||||
fish_tempfile::new_file().context("Failed to create temp file")?;
|
||||
crate::copy_file(&path, tmp_copy.path())?;
|
||||
update_po_file(tmp_copy.path(), template_path_buf)?;
|
||||
let diff_output = Command::new("diff")
|
||||
.arg("-u")
|
||||
.arg(&path)
|
||||
.arg(tmp_copy.path())
|
||||
.output()
|
||||
.context("Failed to run diff")?;
|
||||
if diff_output.status.success() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(diff_output.stdout))
|
||||
}
|
||||
});
|
||||
thread_handles.push(handle);
|
||||
}
|
||||
let mut found_diff = false;
|
||||
let mut error = None;
|
||||
for handle in thread_handles {
|
||||
// SAFETY: `handle.join()` only returns `Err` if the thread panicked.
|
||||
// Our threads should not panic, and if they do, it's OK to deal with the unexpected
|
||||
// behavior by panicking here as well.
|
||||
match handle.join().unwrap() {
|
||||
Ok(None) => {}
|
||||
Ok(Some(diff)) => {
|
||||
found_diff = true;
|
||||
stdout()
|
||||
.write_all(&diff)
|
||||
.context("Could not write to stdout")?;
|
||||
}
|
||||
Err(e) => {
|
||||
error = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(e) = error {
|
||||
return Err(e);
|
||||
}
|
||||
if found_diff {
|
||||
bail!("Not all files are up to date");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Task::New { language } => {
|
||||
let language_regex = Regex::new("^[a-z]{2,3}(_[A-Z]{2})?$").unwrap();
|
||||
if !language_regex.is_match(language.as_bytes()).unwrap() {
|
||||
bail!(
|
||||
"The language name '{language}' does not match the expected format.\n\
|
||||
It needs to be a two-letter ISO 639-1 language code, \
|
||||
or a three-letter ISO 639-2 language code \
|
||||
if no ISO 639-1 code exists for the language.\n\
|
||||
Optionally, the language code can be followed be an underscore \
|
||||
followed by an ISO 3166-1 country code to indicate a regional variant.\n\
|
||||
Check the existing file names in {:?} for examples.",
|
||||
po_dir()
|
||||
);
|
||||
}
|
||||
// TODO (MSRV>=1.91): use with_added_extension instead of with_extension
|
||||
let po_path = po_dir().join(&language).with_extension("po");
|
||||
let mut new_po_file = OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(&po_path)
|
||||
.with_context(|| format!("Failed to create file at {po_path:?}"))?;
|
||||
let mut header = String::new();
|
||||
let line_prefix = "# fish-note-sections: ";
|
||||
let lines = [
|
||||
"Translations are divided into sections, each starting with a fish-section-* pseudo-message.",
|
||||
"The first few sections are more important.",
|
||||
"Ignore the tier3 sections unless you have a lot of time.",
|
||||
];
|
||||
for line in lines {
|
||||
use std::fmt::Write as _;
|
||||
let _ = writeln!(header, "{line_prefix}{line}");
|
||||
}
|
||||
new_po_file
|
||||
.write_all(header.as_bytes())
|
||||
.with_context(|| format!("Failed to write to {po_path:?}"))?;
|
||||
new_po_file
|
||||
.write_all(template.serialize())
|
||||
.with_context(|| format!("Failed to write to {po_path:?}"))?;
|
||||
Ok(())
|
||||
}
|
||||
Task::Update { paths } => {
|
||||
let mut thread_handles = vec![];
|
||||
for path in get_po_paths(&paths)? {
|
||||
let template_path_buf = template_file.path().to_owned();
|
||||
let handle =
|
||||
spawn(move || -> Result<()> { update_po_file(path, template_path_buf) });
|
||||
thread_handles.push(handle);
|
||||
}
|
||||
let mut error = None;
|
||||
for handle in thread_handles {
|
||||
// SAFETY: `handle.join()` only returns `Err` if the thread panicked.
|
||||
// Our threads should not panic, and if they do, it's OK to deal with the unexpected
|
||||
// behavior by panicking here as well.
|
||||
if let Err(e) = handle.join().unwrap() {
|
||||
error = Some(e);
|
||||
}
|
||||
}
|
||||
if let Some(e) = error {
|
||||
return Err(e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod template {
|
||||
use crate::{CommandExt as _, files_with_extension};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use fish_build_helper::workspace_root;
|
||||
use fish_common::{UnescapeFlags, unescape_string};
|
||||
use fish_widestring::{str2wcstring, wcs2bytes};
|
||||
use pcre2::bytes::Regex;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Display,
|
||||
fs::OpenOptions,
|
||||
io::Read as _,
|
||||
path::Path,
|
||||
process::Command,
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
// Gettext tools require this header to know which encoding is used.
|
||||
const MINIMAL_HEADER: &str = r#"msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
"#;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
|
||||
enum LocalizationTier {
|
||||
Tier1,
|
||||
Tier2,
|
||||
Tier3,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for LocalizationTier {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
"tier1" => Ok(Self::Tier1),
|
||||
"tier2" => Ok(Self::Tier2),
|
||||
"tier3" => Ok(Self::Tier3),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LocalizationTier {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::Tier1 => "tier1",
|
||||
Self::Tier2 => "tier2",
|
||||
Self::Tier3 => "tier3",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FishScriptMessages {
|
||||
explicit: HashSet<String>,
|
||||
implicit: HashSet<String>,
|
||||
}
|
||||
|
||||
pub struct Template {
|
||||
content: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Template {
|
||||
pub fn serialize(&self) -> &[u8] {
|
||||
&self.content
|
||||
}
|
||||
|
||||
/// Create a gettext template.
|
||||
/// `rust_extraction_dir` must be the path to a directory which contains the messages
|
||||
/// extracted from the Rust sources.
|
||||
pub fn new<P: AsRef<Path>>(rust_extraction_dir: P) -> Result<Self> {
|
||||
let mut template = Self {
|
||||
content: Vec::from(MINIMAL_HEADER.as_bytes()),
|
||||
};
|
||||
template.add_rust_messages(rust_extraction_dir)?;
|
||||
template.add_fish_script_messages()?;
|
||||
// TODO: keep internal set of msgids to avoid having to run msguniq. requires parsing
|
||||
// gettext-extraction output
|
||||
let msguniq_output = Command::new("msguniq")
|
||||
.args(["--no-wrap"])
|
||||
.run_with_stdio(template.content)?;
|
||||
Ok(Template {
|
||||
content: msguniq_output,
|
||||
})
|
||||
}
|
||||
|
||||
/// Expects `extraction_dir` to contain only files whose content are single PO entries which can be
|
||||
/// concatenated into a valid PO file.
|
||||
/// If this is the case, the messages are de-duplicated and sorted by `msguniq`.
|
||||
/// The result is appended to `template`, with a leading section marker.
|
||||
/// On failure, the process aborts.
|
||||
fn add_rust_messages<P: AsRef<Path>>(&mut self, extraction_dir: P) -> Result<()> {
|
||||
let extraction_dir = extraction_dir.as_ref();
|
||||
let mut concatenated_content = Vec::from(MINIMAL_HEADER.as_bytes());
|
||||
|
||||
// Concatenate the content of all files in `extraction_dir` into `concatenated_content`.
|
||||
for entry_result in extraction_dir
|
||||
.read_dir()
|
||||
.with_context(|| format!("Failed to read directory {extraction_dir:?}"))?
|
||||
{
|
||||
let entry = entry_result
|
||||
.with_context(|| format!("Failed to get entry in {extraction_dir:?}"))?;
|
||||
let entry_path = entry.path();
|
||||
if !entry
|
||||
.file_type()
|
||||
.with_context(|| format!("Failed to get file type of {entry_path:?}"))?
|
||||
.is_file()
|
||||
{
|
||||
bail!("Entry in {extraction_dir:?} is not a regular file");
|
||||
}
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&entry_path)
|
||||
.with_context(|| format!("Failed to open file {entry_path:?}"))?;
|
||||
file.read_to_end(&mut concatenated_content)
|
||||
.with_context(|| format!("Failed to read file {entry_path:?}"))?;
|
||||
}
|
||||
|
||||
// Get rid of duplicates and sort.
|
||||
let msguniq_output = Command::new("msguniq")
|
||||
.args(["--no-wrap", "--sort-output"])
|
||||
.env("LC_ALL", "C.UTF-8")
|
||||
.run_with_stdio(concatenated_content)?;
|
||||
// The Header entry needs to be removed again,
|
||||
// because it is added outside of this function.
|
||||
let expected_prefix = MINIMAL_HEADER.as_bytes();
|
||||
let actual_prefix = &msguniq_output[..expected_prefix.len()];
|
||||
if expected_prefix != actual_prefix {
|
||||
bail!(
|
||||
"Prefix of msguniq output does not match expected header.\nExpected bytes:\n{expected_prefix:02x?}\nActual bytes:\n{actual_prefix:02x?}"
|
||||
);
|
||||
}
|
||||
self.mark_section("tier1-from-rust");
|
||||
self.content
|
||||
.extend_from_slice(&msguniq_output[expected_prefix.len()..]);
|
||||
self.content.push(b'\n');
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mark_section(&mut self, section_name: &str) {
|
||||
self.content
|
||||
.extend_from_slice("msgid \"fish-section-".as_bytes());
|
||||
self.content.extend_from_slice(section_name.as_bytes());
|
||||
self.content
|
||||
.extend_from_slice("\"\nmsgstr \"\"\n\n".as_bytes());
|
||||
}
|
||||
|
||||
fn append_messages(&mut self, msgids: &HashSet<String>) -> Result<()> {
|
||||
let mut unescaped_msgids = HashSet::new();
|
||||
for msgid in msgids {
|
||||
let unescaped_wstring = unescape_string(
|
||||
&str2wcstring(msgid),
|
||||
fish_common::UnescapeStringStyle::Script(UnescapeFlags::default()),
|
||||
)
|
||||
.with_context(|| format!("Failed to unescape the following string:\n{msgid}"))?;
|
||||
unescaped_msgids.insert(
|
||||
String::from_utf8(wcs2bytes(&unescaped_wstring))
|
||||
.context("Parsed msgid is not valid UTF-8")?,
|
||||
);
|
||||
}
|
||||
let mut unescaped_msgids = Vec::from_iter(unescaped_msgids);
|
||||
unescaped_msgids.sort();
|
||||
for msgid in &unescaped_msgids {
|
||||
self.content
|
||||
.extend_from_slice(format_msgid_for_po(msgid).as_bytes());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_script_tier(
|
||||
&mut self,
|
||||
tier: LocalizationTier,
|
||||
messages: FishScriptMessages,
|
||||
) -> Result<()> {
|
||||
if !messages.explicit.is_empty() {
|
||||
self.mark_section(&format!("{tier}-from-script-explicitly-added"));
|
||||
self.append_messages(&messages.explicit)?;
|
||||
}
|
||||
if !messages.implicit.is_empty() {
|
||||
self.mark_section(&format!("{tier}-from-script-implicitly-added"));
|
||||
self.append_messages(&messages.implicit)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_fish_script_messages(&mut self) -> Result<()> {
|
||||
let share_dir = workspace_root().join("share");
|
||||
let relevant_file_paths = files_with_extension(
|
||||
[
|
||||
share_dir.join("config.fish"),
|
||||
share_dir.join("completions"),
|
||||
share_dir.join("functions"),
|
||||
],
|
||||
"fish",
|
||||
)?;
|
||||
let mut extracted_messages = HashMap::new();
|
||||
for path in relevant_file_paths {
|
||||
extract_messages_from_fish_script(path, &mut extracted_messages)?;
|
||||
}
|
||||
let mut messages_sorted_by_tier: Vec<_> = extracted_messages.into_iter().collect();
|
||||
messages_sorted_by_tier.sort_by_key(|(tier, _)| *tier);
|
||||
for (tier, messages) in messages_sorted_by_tier {
|
||||
self.add_script_tier(tier, messages)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn find_localization_tier<P: AsRef<Path>>(
|
||||
input: &str,
|
||||
path: P,
|
||||
) -> Result<Option<LocalizationTier>> {
|
||||
static L10N_ANNOTATION: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?:^|\n)# localization: (?<annotation_value>.*)\n").unwrap()
|
||||
});
|
||||
if let Some(annotation) = L10N_ANNOTATION.captures(input.as_bytes()).unwrap() {
|
||||
// SAFETY: `tier` is the name of a capture group in the regex whose captures we are looking
|
||||
// at. The capture is done on the bytes of an UTF-8 encoded string, so the result will also
|
||||
// be UTF-8 encoded, and the sub-slice we are looking at will start and end at codepoint
|
||||
// boundaries.
|
||||
let annotation_value =
|
||||
std::str::from_utf8(annotation.name("annotation_value").unwrap().as_bytes())
|
||||
.unwrap();
|
||||
if let Ok(tier) = LocalizationTier::try_from(annotation_value) {
|
||||
return Ok(Some(tier));
|
||||
}
|
||||
if annotation_value.starts_with("skip") {
|
||||
return Ok(None);
|
||||
}
|
||||
bail!(
|
||||
"Unexpected localization annotation in file {:?}: {annotation_value}",
|
||||
path.as_ref()
|
||||
);
|
||||
}
|
||||
let dirname = path
|
||||
.as_ref()
|
||||
.parent()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Tried to get the parent of a path which does not have a parent: {:?}",
|
||||
path.as_ref()
|
||||
)
|
||||
})?
|
||||
.file_name()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"The parent of {:?} does not have a filename component",
|
||||
path.as_ref()
|
||||
)
|
||||
})?;
|
||||
let command_name = path
|
||||
.as_ref()
|
||||
.file_stem()
|
||||
.with_context(|| format!("The path {:?} does not have a file stem", path.as_ref()))?;
|
||||
if dirname == "functions"
|
||||
&& command_name
|
||||
.to_str()
|
||||
.is_some_and(|name| name.starts_with("fish_"))
|
||||
{
|
||||
return Ok(Some(LocalizationTier::Tier1));
|
||||
}
|
||||
if dirname != "completions" {
|
||||
bail!(
|
||||
"Missing localization tier for function file {:?}",
|
||||
path.as_ref()
|
||||
);
|
||||
}
|
||||
// TODO (MSRV>=1.91): use with_added_extension instead of with_extension
|
||||
let doc_path = workspace_root()
|
||||
.join("doc_src")
|
||||
.join("cmds")
|
||||
.join(command_name)
|
||||
.with_extension("rst");
|
||||
let doc_path_exists = std::fs::exists(&doc_path)
|
||||
.with_context(|| format!("Failed to check whether a file exists at {doc_path:?}"))?;
|
||||
Ok(Some(if doc_path_exists {
|
||||
LocalizationTier::Tier1
|
||||
} else {
|
||||
LocalizationTier::Tier3
|
||||
}))
|
||||
}
|
||||
|
||||
fn extract_messages_from_fish_script<P: AsRef<Path>>(
|
||||
path: P,
|
||||
extracted_messages: &mut HashMap<LocalizationTier, FishScriptMessages>,
|
||||
) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
let file_content = std::fs::read_to_string(path)
|
||||
.with_context(|| format!("Failed to read from {path:?}"))?;
|
||||
let Some(tier) = find_localization_tier(&file_content, path)? else {
|
||||
return Ok(());
|
||||
};
|
||||
// TODO: use proper parser instead of regex
|
||||
static EXPLICIT_MESSAGE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r#"\( *_ (?<message>(['"]).+?(?<!\\)\2) *\)"#).unwrap());
|
||||
static IMPLICIT_MESSAGE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r#"(?:^|\n)(?:\s|and |or )*(?:complete|function).*? (?:-d|--description) (?<message>(['"]).+?(?<!\\)\2)"#).unwrap()
|
||||
});
|
||||
let messages_at_tier = extracted_messages.entry(tier).or_default();
|
||||
for message in EXPLICIT_MESSAGE.captures_iter(file_content.as_bytes()) {
|
||||
let message =
|
||||
std::str::from_utf8(message.unwrap().name("message").unwrap().as_bytes()).unwrap();
|
||||
messages_at_tier.explicit.insert(message.to_owned());
|
||||
}
|
||||
for message in IMPLICIT_MESSAGE.captures_iter(file_content.as_bytes()) {
|
||||
let message =
|
||||
std::str::from_utf8(message.unwrap().name("message").unwrap().as_bytes()).unwrap();
|
||||
messages_at_tier.implicit.insert(message.to_owned());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_msgid_for_po(msgid: &str) -> String {
|
||||
let escaped_msgid = msgid.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||
format!(
|
||||
"\
|
||||
msgid \"{escaped_msgid}\"\n\
|
||||
msgstr \"\"\n\
|
||||
\n\
|
||||
"
|
||||
)
|
||||
}
|
||||
}
|
||||
101
crates/xtask/src/lib.rs
Normal file
101
crates/xtask/src/lib.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub mod format;
|
||||
pub mod gettext;
|
||||
pub mod shellcheck;
|
||||
|
||||
pub trait CommandExt {
|
||||
fn run(&mut self) -> Result<()>;
|
||||
fn run_with_stdio(&mut self, stdin: Vec<u8>) -> Result<Vec<u8>>;
|
||||
}
|
||||
|
||||
impl CommandExt for Command {
|
||||
fn run(&mut self) -> Result<()> {
|
||||
if !self
|
||||
.status()
|
||||
.with_context(|| format!("Failed to run {:?}", self.get_program()))?
|
||||
.success()
|
||||
{
|
||||
bail!("Command did not run successfully: {:?}", self.get_program())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_with_stdio(&mut self, stdin: Vec<u8>) -> Result<Vec<u8>> {
|
||||
let command_name = self.get_program().to_owned();
|
||||
let mut child = self
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.with_context(|| format!("Failed to run {command_name:?}"))?;
|
||||
child
|
||||
.stdin
|
||||
.take()
|
||||
.unwrap()
|
||||
.write_all(&stdin)
|
||||
.with_context(|| format!("Failed to write to stdin of {command_name:?}"))?;
|
||||
let command_output = child
|
||||
.wait_with_output()
|
||||
.with_context(|| format!("Failed to read stdout of {command_name:?}"))?;
|
||||
if !command_output.status.success() {
|
||||
bail!("{command_name:?} failed");
|
||||
}
|
||||
Ok(command_output.stdout)
|
||||
}
|
||||
}
|
||||
|
||||
pub const CARGO: &str = env!("CARGO");
|
||||
|
||||
pub fn cargo<I, S>(cargo_args: I) -> Result<()>
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
Command::new(CARGO).args(cargo_args).run()
|
||||
}
|
||||
|
||||
fn get_matching_files<P: AsRef<Path>, I: IntoIterator<Item = P>, M: Fn(&Path) -> bool>(
|
||||
all_paths: I,
|
||||
matcher: M,
|
||||
) -> Result<Vec<PathBuf>> {
|
||||
let mut matching_files = vec![];
|
||||
for path in all_paths {
|
||||
for dir_entry in WalkDir::new(path.as_ref()) {
|
||||
let dir_entry = dir_entry
|
||||
.with_context(|| format!("Failed to check paths at {:?}", path.as_ref()))?;
|
||||
let path = dir_entry.path();
|
||||
if dir_entry.file_type().is_file() && matcher(path) {
|
||||
matching_files.push(path.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(matching_files)
|
||||
}
|
||||
|
||||
fn files_with_extension<P: AsRef<Path>, I: IntoIterator<Item = P>>(
|
||||
all_paths: I,
|
||||
extension: &str,
|
||||
) -> Result<Vec<PathBuf>> {
|
||||
let matcher = |p: &Path| p.extension().is_some_and(|e| e == extension);
|
||||
get_matching_files(all_paths, matcher)
|
||||
}
|
||||
|
||||
fn copy_file<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
|
||||
std::fs::copy(&from, &to)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to copy from {:?} to {:?}",
|
||||
from.as_ref(),
|
||||
to.as_ref()
|
||||
)
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
111
crates/xtask/src/main.rs
Normal file
111
crates/xtask/src/main.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use anyhow::{Context, Result};
|
||||
use clap::{Parser, Subcommand};
|
||||
use fish_build_helper::as_os_strs;
|
||||
use std::{path::PathBuf, process::Command};
|
||||
use xtask::{CommandExt, cargo, format::FormatArgs, gettext::GettextArgs, shellcheck::shellcheck};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
name = "cargo xtask",
|
||||
about = "Wrapper for running various utilities",
|
||||
arg_required_else_help(true)
|
||||
)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
task: Task,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Task {
|
||||
/// Run various checks on the repo.
|
||||
Check,
|
||||
/// Format files or check if they are correctly formatted.
|
||||
Format(FormatArgs),
|
||||
/// Work on the gettext PO files.
|
||||
Gettext(GettextArgs),
|
||||
/// Build HTML docs
|
||||
HtmlDocs {
|
||||
/// Path to a fish_indent executable. If none is specified, fish_indent will be built.
|
||||
#[arg(long)]
|
||||
fish_indent: Option<PathBuf>,
|
||||
},
|
||||
/// Build man pages
|
||||
ManPages,
|
||||
/// Run ShellCheck on non-fish shell scripts
|
||||
#[command(name = "shellcheck")]
|
||||
ShellCheck,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
match cli.task {
|
||||
Task::Check => run_checks(),
|
||||
Task::Format(format_args) => xtask::format::format(format_args),
|
||||
Task::Gettext(gettext_args) => xtask::gettext::gettext(gettext_args),
|
||||
Task::HtmlDocs { fish_indent } => build_html_docs(fish_indent),
|
||||
Task::ManPages => cargo(["build", "--package", "fish-build-man-pages"]),
|
||||
Task::ShellCheck => shellcheck(),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_checks() -> Result<()> {
|
||||
let repo_root_dir = fish_build_helper::workspace_root();
|
||||
let check_script = repo_root_dir.join("build_tools").join("check.sh");
|
||||
Command::new(check_script).run()
|
||||
}
|
||||
|
||||
fn build_html_docs(fish_indent: Option<PathBuf>) -> Result<()> {
|
||||
let fish_indent_path = match fish_indent {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
// Build fish_indent if no existing one is specified.
|
||||
cargo([
|
||||
"build",
|
||||
"--bin",
|
||||
"fish_indent",
|
||||
"--profile",
|
||||
"dev",
|
||||
"--no-default-features",
|
||||
])?;
|
||||
fish_build_helper::fish_build_dir()
|
||||
.join("debug")
|
||||
.join("fish_indent")
|
||||
}
|
||||
};
|
||||
// Set path so `sphinx-build` can find `fish_indent`.
|
||||
// Create tempdir to store symlink to fish_indent.
|
||||
// This is done to avoid adding other binaries to the PATH.
|
||||
let tempdir = fish_tempfile::new_dir().context("Failed to create tempdir")?;
|
||||
std::os::unix::fs::symlink(
|
||||
std::fs::canonicalize(&fish_indent_path).with_context(|| {
|
||||
format!("Failed to canonicalize path to `fish_indent`: {fish_indent_path:?}")
|
||||
})?,
|
||||
tempdir.path().join("fish_indent"),
|
||||
)
|
||||
.context("Failed to create symlink for fish_indent")?;
|
||||
let mut new_path = tempdir.path().as_os_str().to_owned();
|
||||
if let Some(current_path) = std::env::var_os("PATH") {
|
||||
new_path.push(":");
|
||||
new_path.push(current_path);
|
||||
}
|
||||
let doc_src_dir = fish_build_helper::workspace_root().join("doc_src");
|
||||
let doctrees_dir = fish_build_helper::fish_doc_dir().join(".doctrees-html");
|
||||
let html_dir = fish_build_helper::fish_doc_dir().join("html");
|
||||
let args = as_os_strs![
|
||||
"-j",
|
||||
"auto",
|
||||
"-q",
|
||||
"-b",
|
||||
"html",
|
||||
"-c",
|
||||
&doc_src_dir,
|
||||
"-d",
|
||||
&doctrees_dir,
|
||||
&doc_src_dir,
|
||||
&html_dir,
|
||||
];
|
||||
Command::new(option_env!("FISH_SPHINX").unwrap_or("sphinx-build"))
|
||||
.env("PATH", new_path)
|
||||
.args(args)
|
||||
.run()
|
||||
}
|
||||
52
crates/xtask/src/shellcheck.rs
Normal file
52
crates/xtask/src/shellcheck.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use anyhow::{Context, Result};
|
||||
use fish_build_helper::workspace_root;
|
||||
use ignore::Walk;
|
||||
use pcre2::bytes::Regex;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use crate::CommandExt;
|
||||
|
||||
pub fn shellcheck() -> Result<()> {
|
||||
Command::new("shellcheck")
|
||||
.args(files_to_check()?)
|
||||
.current_dir(workspace_root())
|
||||
.run()
|
||||
}
|
||||
|
||||
fn is_shell_script<P: AsRef<Path>>(path: P) -> Result<bool> {
|
||||
let file = File::open(&path).with_context(|| format!("Failed to open {:?}", path.as_ref()))?;
|
||||
let mut first_line = String::new();
|
||||
let Ok(_) = BufReader::new(file).read_line(&mut first_line) else {
|
||||
return Ok(false);
|
||||
};
|
||||
static SHEBANG_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("^#!.*[^i]sh").unwrap());
|
||||
Ok(SHEBANG_REGEX
|
||||
.is_match(first_line.trim().as_bytes())
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
fn files_to_check() -> Result<Vec<PathBuf>> {
|
||||
let mut files = vec![];
|
||||
for dir_entry in Walk::new(workspace_root()) {
|
||||
let dir_entry = dir_entry.context("Error traversing workspace")?;
|
||||
if !dir_entry
|
||||
.file_type()
|
||||
.with_context(|| format!("Failed to determine file type of {dir_entry:?}"))?
|
||||
.is_file()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let path = dir_entry.into_path();
|
||||
if !is_shell_script(&path)? {
|
||||
continue;
|
||||
}
|
||||
files.push(path);
|
||||
}
|
||||
Ok(files)
|
||||
}
|
||||
32
deny.toml
32
deny.toml
@@ -3,22 +3,22 @@
|
||||
confidence-threshold = 0.93
|
||||
unused-allowed-license = "allow" # don't warn for unused licenses in this list
|
||||
allow = [
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"BSL-1.0",
|
||||
"CC0-1.0",
|
||||
"GPL-2.0",
|
||||
"GPL-2.0-only",
|
||||
"ISC",
|
||||
"LGPL-2.0",
|
||||
"LGPL-2.0-or-later",
|
||||
"MIT",
|
||||
"MPL-2.0",
|
||||
"PSF-2.0",
|
||||
"Unicode-DFS-2016",
|
||||
"Unicode-3.0",
|
||||
"WTFPL",
|
||||
"Zlib",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"BSL-1.0",
|
||||
"CC0-1.0",
|
||||
"GPL-2.0",
|
||||
"GPL-2.0-only",
|
||||
"ISC",
|
||||
"LGPL-2.0",
|
||||
"LGPL-2.0-or-later",
|
||||
"MIT",
|
||||
"MPL-2.0",
|
||||
"PSF-2.0",
|
||||
"Unicode-DFS-2016",
|
||||
"Unicode-3.0",
|
||||
"WTFPL",
|
||||
"Zlib",
|
||||
]
|
||||
|
||||
[sources.allow-org]
|
||||
|
||||
@@ -10,7 +10,7 @@ Synopsis
|
||||
[--set-cursor[=MARKER]] ([-f | --function FUNCTION] | EXPANSION)
|
||||
abbr --erase [ [-c | --command COMMAND]... ] NAME ...
|
||||
abbr --rename [ [-c | --command COMMAND]... ] OLD_WORD NEW_WORD
|
||||
abbr --show
|
||||
abbr [--show] [--color WHEN]
|
||||
abbr --list
|
||||
abbr --query NAME ...
|
||||
|
||||
@@ -75,7 +75,6 @@ With **--set-cursor=MARKER**, the cursor is moved to the first occurrence of **M
|
||||
|
||||
With **-f FUNCTION** or **--function FUNCTION**, **FUNCTION** is treated as the name of a fish function instead of a literal replacement. When the abbreviation matches, the function will be called with the matching token as an argument. If the function's exit status is 0 (success), the token will be replaced by the function's output; otherwise the token will be left unchanged. No **EXPANSION** may be given separately.
|
||||
|
||||
|
||||
Examples
|
||||
########
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ The following ``argparse`` options are available. They must appear before all *O
|
||||
In contrast, if the known option comes first (and does not take any arguments), the known option will be recognised (e.g. ``argparse --move-unknown h -- -ho`` *will* set ``$_flag_h`` to ``-h``)
|
||||
|
||||
**-i** or **--ignore-unknown**
|
||||
Deprecated. This is like **--move-unknown**, except that unknown options and their arguments are kept in ``$argv`` and not moved to ``$argv_opts``. Unlike **--move-unknown**, this option makes it impossible to distinguish between an unknown option and non-option argument that starts with a ``-`` (since any ``--`` seperator in ``$argv`` will be removed).
|
||||
Deprecated. This is like **--move-unknown**, except that unknown options and their arguments are kept in ``$argv`` and not moved to ``$argv_opts``. Unlike **--move-unknown**, this option makes it impossible to distinguish between an unknown option and non-option argument that starts with a ``-`` (since any ``--`` separator in ``$argv`` will be removed).
|
||||
|
||||
**-S** or **--strict-longopts**
|
||||
This makes the parsing of long options more strict. In particular, *without* this flag, if ``long`` is a known long option flag, ``--long`` and ``--long=<value>`` can be abbreviated as:
|
||||
@@ -251,7 +251,7 @@ Some *OPTION_SPEC* examples:
|
||||
|
||||
- ``n/name=?`` means that both ``-n`` and ``--name`` are valid. It accepts an optional value and can be used at most once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the value associated with the flag if one was provided else it will be set with no values.
|
||||
|
||||
- ``n/name=*`` is similar, but the flag can be used more than once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the values associated with each occurence. Each value will be the value given to the option, or the empty string if no value was given.
|
||||
- ``n/name=*`` is similar, but the flag can be used more than once. If the flag is seen then ``_flag_n`` and ``_flag_name`` will be set with the values associated with each occurrence. Each value will be the value given to the option, or the empty string if no value was given.
|
||||
|
||||
- ``name=+`` means that only ``--name`` is valid. It requires a value and can be used more than once. If the flag is seen then ``_flag_name`` will be set with the values associated with each occurrence.
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user