1#![doc = include_str!("../README.md")]
2
3#[cfg(test)]
4mod tests;
5
6use std::{
7 collections::HashMap,
8 error::Error,
9 ffi::{OsStr, OsString},
10 fmt::{self, Display},
11 fs, io,
12 iter::FusedIterator,
13 path::{Path, PathBuf},
14 str::{self, FromStr},
15};
16
17const VARIABLE_RECURSION_LIMIT: usize = 1000;
18
19#[derive(Debug)]
21pub enum OpenError {
22 Io(io::Error),
23 Parse(ParseError),
24 Encoding(EncodingError),
25}
26
27impl Display for OpenError {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 match self {
30 Self::Io(e) => Display::fmt(&e, f),
31 Self::Parse(e) => Display::fmt(&e, f),
32 Self::Encoding(e) => Display::fmt(&e, f),
33 }
34 }
35}
36
37impl Error for OpenError {}
38
39impl From<io::Error> for OpenError {
40 fn from(value: io::Error) -> Self {
41 Self::Io(value)
42 }
43}
44
45impl From<ParseError> for OpenError {
46 fn from(value: ParseError) -> Self {
47 Self::Parse(value)
48 }
49}
50
51impl From<EncodingError> for OpenError {
52 fn from(value: EncodingError) -> Self {
53 Self::Encoding(value)
54 }
55}
56
57#[derive(Clone, Copy, Debug)]
59pub struct EncodingError;
60
61impl Display for EncodingError {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 write!(f, "data must be valid utf-8 on this platform")
64 }
65}
66
67impl Error for EncodingError {}
68
69#[derive(Clone, Copy, Debug)]
71pub struct ParseError {
72 line: usize,
73 description: &'static str,
74}
75
76impl ParseError {
77 pub fn new(line: usize, description: &'static str) -> Self {
78 Self { line, description }
79 }
80}
81
82impl Display for ParseError {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 write!(f, "parse error at line {}: {}", self.line, self.description)
85 }
86}
87
88impl Error for ParseError {}
89
90#[derive(Debug)]
92pub enum VariableError {
93 Undefined(Vec<u8>),
94 RecursionLimit,
95}
96
97impl Display for VariableError {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match self {
100 Self::Undefined(var) => Display::fmt(&VariableErrorRef::Undefined(var), f),
101 Self::RecursionLimit => Display::fmt(&VariableErrorRef::RecursionLimit, f),
102 }
103 }
104}
105
106impl Error for VariableError {}
107
108impl From<VariableErrorRef<'_>> for VariableError {
109 fn from(value: VariableErrorRef) -> Self {
110 match value {
111 VariableErrorRef::Undefined(var) => VariableError::Undefined(var.to_owned()),
112 VariableErrorRef::RecursionLimit => VariableError::RecursionLimit,
113 }
114 }
115}
116
117#[derive(Debug)]
118enum VariableErrorRef<'a> {
119 Undefined(&'a [u8]),
120 RecursionLimit,
121}
122
123impl Display for VariableErrorRef<'_> {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 match self {
126 Self::Undefined(var) => {
127 write!(f, "undefined variable: `{}`", String::from_utf8_lossy(var))
128 }
129 Self::RecursionLimit => write!(f, "recursion limit reached"),
130 }
131 }
132}
133
134impl Error for VariableErrorRef<'_> {}
135
136fn split_bytes_once(bytes: &[u8], m: impl Fn(u8) -> bool) -> Option<(&[u8], &[u8])> {
137 bytes
138 .iter()
139 .copied()
140 .position(m)
141 .map(|i| (&bytes[..i], &bytes[i + 1..]))
142}
143
144fn trim_bytes(bytes: &[u8]) -> &[u8] {
145 let Some(s) = bytes.iter().position(|b| !b.is_ascii_whitespace()) else {
146 return &bytes[bytes.len()..];
147 };
148 let e = bytes
149 .iter()
150 .rev()
151 .position(|b| !b.is_ascii_whitespace())
152 .unwrap();
153 &bytes[s..bytes.len() - e]
154}
155
156fn bytes_to_pathbuf(bytes: &[u8]) -> Result<PathBuf, EncodingError> {
157 byte_vec_to_pathbuf(bytes.to_owned())
158}
159
160fn byte_vec_to_pathbuf(bytes: Vec<u8>) -> Result<PathBuf, EncodingError> {
161 Ok(PathBuf::from(OsString::from_byte_vec(bytes)?))
162}
163
164fn escape_bytes(bytes: &[u8]) -> Vec<u8> {
165 let mut escaped = Vec::new();
166 for b in bytes.iter().copied() {
167 match b {
168 b'\\' | b'\"' | b' ' => {
169 escaped.push(b'\\');
170 escaped.push(b);
171 }
172 _ => escaped.push(b),
173 }
174 }
175 escaped
176}
177
178trait BytesConv {
179 fn as_bytes(&self) -> Result<&[u8], EncodingError>;
180 fn from_bytes(bytes: &[u8]) -> Result<&Self, EncodingError>;
181}
182
183trait ByteVecConv: Sized {
184 #[allow(dead_code)]
185 fn into_byte_vec(self) -> Result<Vec<u8>, EncodingError>;
186 fn from_byte_vec(bytes: Vec<u8>) -> Result<Self, EncodingError>;
187}
188
189#[cfg(unix)]
190impl BytesConv for OsStr {
191 fn as_bytes(&self) -> Result<&[u8], EncodingError> {
192 Ok(std::os::unix::ffi::OsStrExt::as_bytes(self))
193 }
194
195 fn from_bytes(bytes: &[u8]) -> Result<&Self, EncodingError> {
196 Ok(std::os::unix::ffi::OsStrExt::from_bytes(bytes))
197 }
198}
199
200#[cfg(unix)]
201impl ByteVecConv for OsString {
202 fn into_byte_vec(self) -> Result<Vec<u8>, EncodingError> {
203 Ok(std::os::unix::ffi::OsStringExt::into_vec(self))
204 }
205
206 fn from_byte_vec(bytes: Vec<u8>) -> Result<Self, EncodingError> {
207 Ok(std::os::unix::ffi::OsStringExt::from_vec(bytes))
208 }
209}
210
211#[cfg(not(unix))]
212impl BytesConv for OsStr {
213 fn as_bytes(&self) -> Result<&[u8], EncodingError> {
214 self.to_str().map(|s| s.as_bytes()).ok_or(EncodingError)
215 }
216
217 fn from_bytes(bytes: &[u8]) -> Result<&Self, EncodingError> {
218 Ok(OsStr::new(
219 str::from_utf8(bytes).map_err(|_| EncodingError)?,
220 ))
221 }
222}
223
224#[cfg(not(unix))]
225impl ByteVecConv for OsString {
226 fn into_byte_vec(self) -> Result<Vec<u8>, EncodingError> {
227 Ok(self.into_string().map_err(|_| EncodingError)?.into_bytes())
228 }
229
230 fn from_byte_vec(bytes: Vec<u8>) -> Result<Self, EncodingError> {
231 Ok(String::from_utf8(bytes).map_err(|_| EncodingError)?.into())
232 }
233}
234
235#[non_exhaustive]
237#[derive(Clone, Debug)]
238pub enum Link {
239 SearchLib(PathBuf),
241
242 SearchFramework(PathBuf),
244
245 Lib(PathBuf),
247
248 Framework(PathBuf),
250
251 WeakFramework(PathBuf),
253
254 Rpath(PathBuf),
256
257 Verbatim(PathBuf),
260}
261
262#[derive(Clone, Default)]
264pub struct PkgConfig {
265 variables: HashMap<Vec<u8>, Vec<u8>>,
266 keys: HashMap<Vec<u8>, Vec<u8>>,
267}
268
269impl PkgConfig {
270 pub fn open(path: &Path) -> Result<Self, OpenError> {
272 let mut cfg = Self::default();
273 cfg.set_variable_escaped(
274 "pcfiledir",
275 path.parent().unwrap().as_os_str().as_bytes()?,
276 true,
277 );
278 cfg.extend_from_bytes(&fs::read(path)?)?;
279 Ok(cfg)
280 }
281
282 pub fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
284 let mut cfg = Self::default();
285 cfg.extend_from_bytes(bytes)?;
286 Ok(cfg)
287 }
288
289 fn extend_from_bytes(&mut self, bytes: &[u8]) -> Result<(), ParseError> {
290 for (line_no, line) in PcLines::new(bytes) {
291 let line = trim_bytes(&line);
292 if line.is_empty() {
293 continue;
294 }
295
296 OsStr::from_bytes(line).map_err(|_| {
297 ParseError::new(line_no, "data must be valid utf-8 on this platform")
298 })?;
299
300 if let Some(i) = line.iter().position(|b| matches!(b, b':' | b'=')) {
301 if line[i] == b'=' {
302 let (key, value) = split_bytes_once(line, |b| b == b'=').unwrap();
304 let key = trim_bytes(key);
305 if key.iter().any(u8::is_ascii_whitespace) {
306 return Err(ParseError::new(line_no, "space in variable name"));
307 }
308 if !self.set_variable(key, value, false) {
309 return Err(ParseError::new(line_no, "duplicate variable definition"));
310 }
311 } else {
312 let (key, value) = split_bytes_once(line, |b| b == b':').unwrap();
314 let mut key = trim_bytes(key).to_owned();
315 if key == b"CFlags" {
316 key[1] = b'f';
318 }
319 let value = trim_bytes(value).to_owned();
320 if self.keys.insert(key, value).is_some() {
321 return Err(ParseError::new(line_no, "key already set"));
322 }
323 }
324 } else {
325 return Err(ParseError::new(line_no, "not a variable or key definition"));
326 }
327 }
328 Ok(())
329 }
330
331 pub fn libs(&self) -> Result<Links, VariableError> {
333 Ok(Links {
334 split: self
335 .key_bytes_expanded_unescaped_split("Libs")?
336 .unwrap_or(UnescapeAndSplit::new(b"")),
337 })
338 }
339
340 pub fn libs_private(&self) -> Result<Links, VariableError> {
342 Ok(Links {
343 split: self
344 .key_bytes_expanded_unescaped_split("Libs.private")?
345 .unwrap_or(UnescapeAndSplit::new(b"")),
346 })
347 }
348
349 pub fn libs_with_private(
350 &self,
351 include_private: bool,
352 ) -> Result<impl FusedIterator<Item = Link>, VariableError> {
353 Ok(self.libs()?.chain(Links {
354 split: if include_private {
355 self.key_bytes_expanded_unescaped_split("Libs.private")?
356 .unwrap_or(UnescapeAndSplit::new(b""))
357 } else {
358 UnescapeAndSplit::new(b"")
359 },
360 }))
361 }
362
363 pub fn variable(&self, var: impl AsRef<[u8]>) -> Option<&[u8]> {
365 self.variables.get(var.as_ref()).map(|v| v as &[u8])
366 }
367
368 pub fn set_variable(
370 &mut self,
371 var: impl AsRef<[u8]>,
372 value: impl AsRef<[u8]>,
373 allow_overwrite: bool,
374 ) -> bool {
375 self.variables
376 .insert(
377 trim_bytes(var.as_ref()).to_owned(),
378 trim_bytes(value.as_ref()).to_owned(),
379 )
380 .is_none()
381 || allow_overwrite
382 }
383
384 pub fn set_variable_escaped(
386 &mut self,
387 var: impl AsRef<[u8]>,
388 value: impl AsRef<[u8]>,
389 allow_overwrite: bool,
390 ) -> bool {
391 self.set_variable(var, escape_bytes(value.as_ref()), allow_overwrite)
392 }
393
394 pub fn expand_variable(&self, var: impl AsRef<[u8]>) -> Result<Vec<u8>, VariableError> {
396 Ok(self.expand_variable_r(var.as_ref(), VARIABLE_RECURSION_LIMIT)?)
397 }
398
399 fn expand_variable_r<'e, 's: 'e, 'k: 'e>(
400 &'s self,
401 var: &'k [u8],
402 limit: usize,
403 ) -> Result<Vec<u8>, VariableErrorRef<'e>> {
404 self.expand_variables_in_byte_string_r(
405 self.variable(var).ok_or(VariableErrorRef::Undefined(var))?,
406 limit,
407 )
408 }
409
410 pub fn expand_variables_in(&self, input: impl AsRef<[u8]>) -> Result<Vec<u8>, VariableError> {
412 Ok(self.expand_variables_in_byte_string_r(input.as_ref(), VARIABLE_RECURSION_LIMIT)?)
413 }
414
415 fn expand_variables_in_byte_string_r<'e, 's: 'e, 'k: 'e>(
416 &'s self,
417 input: &'k [u8],
418 limit: usize,
419 ) -> Result<Vec<u8>, VariableErrorRef<'e>> {
420 let mut output = Vec::new();
421 let mut bytes = input.iter().copied().enumerate();
422 while let Some((_, b)) = bytes.next() {
426 if b == b'$' {
427 if let Some((ni, nb)) = bytes.next() {
428 match nb {
429 b'$' => output.push(b),
430 b'{' => {
431 if limit == 0 {
432 return Err(VariableErrorRef::RecursionLimit);
433 }
434 let e = 'end_pos: {
435 for (nni, nnb) in bytes.by_ref() {
436 if nnb == b'}' {
437 break 'end_pos nni;
438 }
439 }
440 input.len()
442 };
443 output.extend(self.expand_variable_r(&input[ni + 1..e], limit - 1)?);
444 }
445 _ => {
446 output.push(b);
447 output.push(nb);
448 }
449 }
450 }
451 } else {
452 output.push(b);
453 }
454 }
455 Ok(output)
456 }
457
458 pub fn key_bytes(&self, key: impl AsRef<[u8]>) -> Option<&[u8]> {
460 self.keys.get(key.as_ref()).map(|v| v as &[u8])
461 }
462
463 pub fn key_bytes_expanded(
465 &self,
466 key: impl AsRef<[u8]>,
467 ) -> Result<Option<Vec<u8>>, VariableError> {
468 if let Some(bytes) = self.key_bytes(key) {
469 Ok(Some(self.expand_variables_in(bytes)?))
470 } else {
471 Ok(None)
472 }
473 }
474
475 pub fn key_bytes_expanded_unescaped_split(
477 &self,
478 key: impl AsRef<[u8]>,
479 ) -> Result<Option<UnescapeAndSplit>, VariableError> {
480 Ok(self.key_bytes_expanded(key)?.map(UnescapeAndSplit::new))
481 }
482}
483
484impl FromStr for PkgConfig {
485 type Err = ParseError;
486
487 fn from_str(s: &str) -> Result<Self, Self::Err> {
488 Self::from_bytes(s.as_bytes())
489 }
490}
491
492pub struct UnescapeAndSplit {
494 bytes: Vec<u8>,
495 index: usize,
496}
497
498impl UnescapeAndSplit {
499 fn new(bytes: impl AsRef<[u8]>) -> Self {
500 Self {
501 bytes: trim_bytes(bytes.as_ref()).to_owned(),
502 index: 0,
503 }
504 }
505}
506
507impl FusedIterator for UnescapeAndSplit {}
508
509impl Iterator for UnescapeAndSplit {
510 type Item = Vec<u8>;
511
512 fn next(&mut self) -> Option<Self::Item> {
513 let i = self.bytes[self.index..]
514 .iter()
515 .position(|b| !b.is_ascii_whitespace())
516 .unwrap_or(self.bytes.len() - self.index);
517 self.index += i;
518
519 if self.bytes.len() == self.index {
520 return None;
521 }
522
523 let mut it = self.bytes[self.index..].iter().copied().enumerate();
524 let mut quoted = false;
525 let mut item = Vec::new();
526
527 while let Some((i, b)) = it.next() {
528 match b {
529 b'\\' => {
530 if let Some((_, b2)) = it.next() {
531 match b2 {
532 b'\\' | b'\"' | b' ' => {
533 item.push(b2)
535 }
536 _ => {
537 item.push(b);
539 item.push(b2);
540 }
541 }
542 } else {
543 item.push(b);
545 }
546 }
547
548 b'\"' => {
549 quoted = !quoted;
550 }
551
552 _ => {
553 if !quoted && b.is_ascii_whitespace() {
554 self.index += i;
555 return Some(item);
556 } else {
557 item.push(b);
558 }
559 }
560 }
561 }
562
563 self.index = self.bytes.len();
564 Some(item)
565 }
566
567 fn size_hint(&self) -> (usize, Option<usize>) {
568 (0, Some((self.bytes.len() + 1) / 2))
569 }
570}
571
572pub struct Links {
574 split: UnescapeAndSplit,
575}
576
577impl FusedIterator for Links {}
578
579impl Iterator for Links {
580 type Item = Link;
581
582 fn next(&mut self) -> Option<Self::Item> {
583 if let Some(part) = self.split.next() {
584 if let Some(part) = part.strip_prefix(b"-L") {
585 Some(Link::SearchLib(bytes_to_pathbuf(part).unwrap()))
586 } else if let Some(part) = part.strip_prefix(b"-F") {
587 Some(Link::SearchFramework(bytes_to_pathbuf(part).unwrap()))
588 } else if let Some(part) = part.strip_prefix(b"-l") {
589 Some(Link::Lib(bytes_to_pathbuf(part).unwrap()))
590 } else if let Some(part) = part.strip_prefix(b"-Wl,-framework,") {
591 Some(Link::Framework(bytes_to_pathbuf(part).unwrap()))
592 } else if part == b"-framework" {
593 self.split
594 .next()
595 .map(|part| Link::Framework(byte_vec_to_pathbuf(part).unwrap()))
596 } else if let Some(part) = part.strip_prefix(b"-Wl,-weak_framework,") {
597 Some(Link::WeakFramework(bytes_to_pathbuf(part).unwrap()))
598 } else if part == b"-weak_framework" {
599 self.split
600 .next()
601 .map(|part| Link::WeakFramework(byte_vec_to_pathbuf(part).unwrap()))
602 } else if let Some(part) = part.strip_prefix(b"-Wl,-rpath,") {
603 Some(Link::Rpath(bytes_to_pathbuf(part).unwrap()))
604 } else if part == b"-rpath" {
605 self.split
606 .next()
607 .map(|part| Link::Rpath(byte_vec_to_pathbuf(part).unwrap()))
608 } else {
609 Some(Link::Verbatim(byte_vec_to_pathbuf(part).unwrap()))
610 }
611 } else {
612 None
613 }
614 }
615}
616
617struct PcLines<'a> {
618 bytes: &'a [u8],
619 line_no: usize,
620}
621
622impl<'a> PcLines<'a> {
623 fn new(bytes: &'a [u8]) -> Self {
624 Self { bytes, line_no: 0 }
625 }
626}
627
628impl Iterator for PcLines<'_> {
629 type Item = (usize, Vec<u8>);
630
631 fn next(&mut self) -> Option<Self::Item> {
632 if self.bytes.is_empty() {
637 return None;
638 }
639 let mut it = self.bytes.iter().copied().enumerate().peekable();
640 let mut line = Vec::new();
641 self.line_no += 1;
642 let line_no = self.line_no;
643 while let Some((i, b)) = it.next() {
644 match b {
645 b'\\' => {
646 if let Some((_, nb)) = it.next() {
648 match nb {
649 b'#' => line.push(nb),
650 b'\r' | b'\n' => {
651 if let Some((_, nnb)) = it.peek() {
652 if *nnb == if nb == b'\r' { b'\n' } else { b'\r' } {
653 it.next();
654 }
655 }
656 self.line_no += 1;
657 }
658 _ => {
659 line.push(b);
660 line.push(nb);
661 }
662 }
663 } else {
664 line.push(b);
665 self.bytes = &self.bytes[i + 1..];
666 return Some((line_no, line));
667 }
668 }
669 b'#' => {
670 while let Some((ni, nc)) = it.next() {
672 if matches!(nc, b'\r' | b'\n') {
673 self.bytes = &self.bytes[ni + 1..];
674 if let Some((_, nnc)) = it.peek() {
675 if *nnc == if nc == b'\r' { b'\n' } else { b'\r' } {
676 self.bytes = &self.bytes[1..];
677 }
678 }
679 break;
680 }
681 }
682 return Some((line_no, line));
683 }
684 b'\r' | b'\n' => {
685 if let Some((_, nc)) = it.next() {
687 if nc == if b == b'\r' { b'\n' } else { b'\r' } {
688 self.bytes = &self.bytes[i + 2..];
689 } else {
690 self.bytes = &self.bytes[i + 1..];
691 }
692 return Some((line_no, line));
693 } else {
694 self.bytes = &self.bytes[i + 1..];
695 return Some((line_no, line));
696 }
697 }
698 _ => line.push(b),
699 }
700 }
701 Some((line_no, line))
702 }
703}