#![cfg(any(feature = "linebased", feature = "deb822"))]
#[derive(Debug)]
pub enum ParseError {
#[cfg(feature = "linebased")]
LineBased(crate::linebased::ParseError),
#[cfg(feature = "deb822")]
Deb822(crate::deb822::ParseError),
UnknownVersion,
FeatureNotEnabled(String),
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
#[cfg(feature = "linebased")]
ParseError::LineBased(e) => write!(f, "{}", e),
#[cfg(feature = "deb822")]
ParseError::Deb822(e) => write!(f, "{}", e),
ParseError::UnknownVersion => write!(f, "Could not detect watch file version"),
ParseError::FeatureNotEnabled(msg) => write!(f, "{}", msg),
}
}
}
impl std::error::Error for ParseError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WatchFileVersion {
LineBased(u32),
Deb822,
}
pub fn detect_version(content: &str) -> Option<WatchFileVersion> {
let trimmed = content.trim_start();
if trimmed.starts_with("Version:") || trimmed.starts_with("version:") {
if let Some(first_line) = trimmed.lines().next() {
if let Some(colon_pos) = first_line.find(':') {
let version_str = first_line[colon_pos + 1..].trim();
if version_str == "5" {
return Some(WatchFileVersion::Deb822);
}
}
}
}
for line in trimmed.lines() {
let line = line.trim();
if line.starts_with('#') || line.is_empty() {
continue;
}
if line.starts_with("version=") || line.starts_with("version =") {
let version_part = if line.starts_with("version=") {
&line[8..]
} else {
&line[9..]
};
if let Ok(version) = version_part.trim().parse::<u32>() {
return Some(WatchFileVersion::LineBased(version));
}
}
break;
}
Some(WatchFileVersion::LineBased(crate::DEFAULT_VERSION))
}
#[derive(Debug)]
pub enum ParsedWatchFile {
#[cfg(feature = "linebased")]
LineBased(crate::linebased::WatchFile),
#[cfg(feature = "deb822")]
Deb822(crate::deb822::WatchFile),
}
#[derive(Debug)]
pub enum ParsedEntry {
#[cfg(feature = "linebased")]
LineBased(crate::linebased::Entry),
#[cfg(feature = "deb822")]
Deb822(crate::deb822::Entry),
}
impl ParsedWatchFile {
pub fn new(version: u32) -> Result<Self, ParseError> {
match version {
#[cfg(feature = "deb822")]
5 => Ok(ParsedWatchFile::Deb822(crate::deb822::WatchFile::new())),
#[cfg(not(feature = "deb822"))]
5 => Err(ParseError::FeatureNotEnabled(
"deb822 feature required for v5 format".to_string(),
)),
#[cfg(feature = "linebased")]
v @ 1..=4 => Ok(ParsedWatchFile::LineBased(
crate::linebased::WatchFile::new(Some(v)),
)),
#[cfg(not(feature = "linebased"))]
v @ 1..=4 => Err(ParseError::FeatureNotEnabled(format!(
"linebased feature required for v{} format",
v
))),
v => Err(ParseError::FeatureNotEnabled(format!(
"unsupported watch file version: {}",
v
))),
}
}
pub fn version(&self) -> u32 {
match self {
#[cfg(feature = "linebased")]
ParsedWatchFile::LineBased(wf) => wf.version(),
#[cfg(feature = "deb822")]
ParsedWatchFile::Deb822(wf) => wf.version(),
}
}
pub fn entries(&self) -> impl Iterator<Item = ParsedEntry> + '_ {
let entries: Vec<_> = match self {
#[cfg(feature = "linebased")]
ParsedWatchFile::LineBased(wf) => wf.entries().map(ParsedEntry::LineBased).collect(),
#[cfg(feature = "deb822")]
ParsedWatchFile::Deb822(wf) => wf.entries().map(ParsedEntry::Deb822).collect(),
};
entries.into_iter()
}
pub fn add_entry(&mut self, source: &str, matching_pattern: &str) -> ParsedEntry {
match self {
#[cfg(feature = "linebased")]
ParsedWatchFile::LineBased(wf) => {
let entry = crate::linebased::EntryBuilder::new(source)
.matching_pattern(matching_pattern)
.build();
let added_entry = wf.add_entry(entry);
ParsedEntry::LineBased(added_entry)
}
#[cfg(feature = "deb822")]
ParsedWatchFile::Deb822(wf) => {
let added_entry = wf.add_entry(source, matching_pattern);
ParsedEntry::Deb822(added_entry)
}
}
}
pub fn version_range(&self) -> Option<rowan::TextRange> {
match self {
#[cfg(feature = "linebased")]
ParsedWatchFile::LineBased(wf) => wf.version_node().map(|v| v.text_range()),
#[cfg(feature = "deb822")]
ParsedWatchFile::Deb822(wf) => {
let first = wf.as_deb822().paragraphs().next()?;
first.get_entry("Version").map(|e| e.text_range())
}
}
}
}
impl ParsedEntry {
pub fn url(&self) -> String {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(e) => e.url(),
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(e) => e.source().unwrap_or(None).unwrap_or_default(),
}
}
pub fn matching_pattern(&self) -> Option<String> {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(e) => e.matching_pattern(),
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(e) => e.matching_pattern().unwrap_or(None),
}
}
pub fn get_option(&self, key: &str) -> Option<String> {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(e) => e.get_option(key),
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(e) => {
e.get_field(key).or_else(|| {
let mut chars = key.chars();
if let Some(first) = chars.next() {
let capitalized = first.to_uppercase().chain(chars).collect::<String>();
e.get_field(&capitalized)
} else {
None
}
})
}
}
}
pub fn has_option(&self, key: &str) -> bool {
self.get_option(key).is_some()
}
pub fn url_range(&self) -> Option<rowan::TextRange> {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(e) => e.url_node().map(|n| n.text_range()),
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(e) => deb822_field_range(e.as_deb822(), &["Source", "URL"]),
}
}
pub fn matching_pattern_range(&self) -> Option<rowan::TextRange> {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(e) => e.matching_pattern_node().map(|n| n.text_range()),
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(e) => deb822_field_range(e.as_deb822(), &["Matching-Pattern"]),
}
}
pub fn option_range(&self, key: &str) -> Option<rowan::TextRange> {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(e) => {
let list = e.option_list()?;
let opt = list.find_option(key)?;
Some(opt.text_range())
}
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(e) => {
if let Some(r) = deb822_field_range(e.as_deb822(), &[key]) {
return Some(r);
}
let mut chars = key.chars();
if let Some(first) = chars.next() {
let capitalized = first.to_uppercase().chain(chars).collect::<String>();
deb822_field_range(e.as_deb822(), &[capitalized.as_str()])
} else {
None
}
}
}
}
pub fn version_policy_range(&self) -> Option<rowan::TextRange> {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(e) => e.version_node().map(|n| n.text_range()),
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(_) => None,
}
}
pub fn template_range(&self) -> Option<rowan::TextRange> {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(_) => None,
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(e) => deb822_field_range(e.as_deb822(), &["Template"]),
}
}
pub fn template_kind(&self) -> Option<String> {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(_) => None,
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(e) => e.as_deb822().get("Template"),
}
}
pub fn script(&self) -> Option<String> {
self.get_option("script")
}
pub fn component(&self) -> Option<String> {
self.get_option("component")
}
pub fn format_url(
&self,
package: impl FnOnce() -> String,
component: impl FnOnce() -> String,
) -> Result<url::Url, url::ParseError> {
crate::subst::subst(&self.url(), package, component).parse()
}
pub fn user_agent(&self) -> Option<String> {
self.get_option("user-agent")
}
pub fn pagemangle(&self) -> Option<String> {
self.get_option("pagemangle")
}
pub fn uversionmangle(&self) -> Option<String> {
self.get_option("uversionmangle")
}
pub fn downloadurlmangle(&self) -> Option<String> {
self.get_option("downloadurlmangle")
}
pub fn pgpsigurlmangle(&self) -> Option<String> {
self.get_option("pgpsigurlmangle")
}
pub fn filenamemangle(&self) -> Option<String> {
self.get_option("filenamemangle")
}
pub fn oversionmangle(&self) -> Option<String> {
self.get_option("oversionmangle")
}
pub fn searchmode(&self) -> crate::types::SearchMode {
self.get_option("searchmode")
.and_then(|s| s.parse().ok())
.unwrap_or_default()
}
pub fn set_option(&mut self, option: crate::types::WatchOption) {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(e) => {
e.set_option(option);
}
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(e) => {
e.set_option(option);
}
}
}
pub fn set_url(&mut self, url: &str) {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(e) => e.set_url(url),
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(e) => e.set_source(url),
}
}
pub fn set_matching_pattern(&mut self, pattern: &str) {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(e) => e.set_matching_pattern(pattern),
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(e) => e.set_matching_pattern(pattern),
}
}
pub fn line(&self) -> usize {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(e) => e.line(),
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(e) => e.line(),
}
}
pub fn remove_option(&mut self, option: crate::types::WatchOption) {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(e) => e.del_opt(option),
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(e) => e.delete_option(option),
}
}
pub fn mode(&self) -> Result<crate::types::Mode, crate::types::ParseError> {
match self {
#[cfg(feature = "linebased")]
ParsedEntry::LineBased(e) => e.try_mode(),
#[cfg(feature = "deb822")]
ParsedEntry::Deb822(e) => e.mode(),
}
}
}
#[cfg(feature = "deb822")]
fn deb822_field_range(
paragraph: &deb822_lossless::Paragraph,
names: &[&str],
) -> Option<rowan::TextRange> {
for name in names {
if let Some(entry) = paragraph.get_entry(name) {
return Some(entry.text_range());
}
}
None
}
impl std::fmt::Display for ParsedWatchFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#[cfg(feature = "linebased")]
ParsedWatchFile::LineBased(wf) => write!(f, "{}", wf),
#[cfg(feature = "deb822")]
ParsedWatchFile::Deb822(wf) => write!(f, "{}", wf),
}
}
}
pub fn parse(content: &str) -> Result<ParsedWatchFile, ParseError> {
let version = detect_version(content).ok_or(ParseError::UnknownVersion)?;
match version {
#[cfg(feature = "linebased")]
WatchFileVersion::LineBased(_v) => {
let wf: crate::linebased::WatchFile = content.parse().map_err(ParseError::LineBased)?;
Ok(ParsedWatchFile::LineBased(wf))
}
#[cfg(not(feature = "linebased"))]
WatchFileVersion::LineBased(_v) => Err(ParseError::FeatureNotEnabled(
"linebased feature required for v1-4 formats".to_string(),
)),
#[cfg(feature = "deb822")]
WatchFileVersion::Deb822 => {
let wf: crate::deb822::WatchFile = content.parse().map_err(ParseError::Deb822)?;
Ok(ParsedWatchFile::Deb822(wf))
}
#[cfg(not(feature = "deb822"))]
WatchFileVersion::Deb822 => Err(ParseError::FeatureNotEnabled(
"deb822 feature required for v5 format".to_string(),
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_version_v1_default() {
let content = "https://example.com/ .*.tar.gz";
assert_eq!(
detect_version(content),
Some(WatchFileVersion::LineBased(1))
);
}
#[test]
fn test_detect_version_v4() {
let content = "version=4\nhttps://example.com/ .*.tar.gz";
assert_eq!(
detect_version(content),
Some(WatchFileVersion::LineBased(4))
);
}
#[test]
fn test_detect_version_v4_with_spaces() {
let content = "version = 4\nhttps://example.com/ .*.tar.gz";
assert_eq!(
detect_version(content),
Some(WatchFileVersion::LineBased(4))
);
}
#[test]
fn test_detect_version_v5() {
let content = "Version: 5\n\nSource: https://example.com/";
assert_eq!(detect_version(content), Some(WatchFileVersion::Deb822));
}
#[test]
fn test_detect_version_v5_lowercase() {
let content = "version: 5\n\nSource: https://example.com/";
assert_eq!(detect_version(content), Some(WatchFileVersion::Deb822));
}
#[test]
fn test_detect_version_with_leading_comments() {
let content = "# This is a comment\nversion=4\nhttps://example.com/ .*.tar.gz";
assert_eq!(
detect_version(content),
Some(WatchFileVersion::LineBased(4))
);
}
#[test]
fn test_detect_version_with_leading_whitespace() {
let content = " \n version=3\nhttps://example.com/ .*.tar.gz";
assert_eq!(
detect_version(content),
Some(WatchFileVersion::LineBased(3))
);
}
#[test]
fn test_detect_version_v2() {
let content = "version=2\nhttps://example.com/ .*.tar.gz";
assert_eq!(
detect_version(content),
Some(WatchFileVersion::LineBased(2))
);
}
#[cfg(feature = "linebased")]
#[test]
fn test_parse_linebased() {
let content = "version=4\nhttps://example.com/ .*.tar.gz";
let parsed = parse(content).unwrap();
assert_eq!(parsed.version(), 4);
}
#[cfg(feature = "deb822")]
#[test]
fn test_parse_deb822() {
let content = "Version: 5\n\nSource: https://example.com/\nMatching-Pattern: .*.tar.gz";
let parsed = parse(content).unwrap();
assert_eq!(parsed.version(), 5);
}
#[cfg(all(feature = "linebased", feature = "deb822"))]
#[test]
fn test_parse_both_formats() {
let v4_content = "version=4\nhttps://example.com/ .*.tar.gz";
let v4_parsed = parse(v4_content).unwrap();
assert_eq!(v4_parsed.version(), 4);
let v5_content = "Version: 5\n\nSource: https://example.com/\nMatching-Pattern: .*.tar.gz";
let v5_parsed = parse(v5_content).unwrap();
assert_eq!(v5_parsed.version(), 5);
}
#[cfg(feature = "linebased")]
#[test]
fn test_parse_roundtrip() {
let content = "version=4\n# Comment\nhttps://example.com/ .*.tar.gz";
let parsed = parse(content).unwrap();
let output = parsed.to_string();
let reparsed = parse(&output).unwrap();
assert_eq!(reparsed.version(), 4);
}
#[cfg(feature = "deb822")]
#[test]
fn test_parsed_watch_file_new_v5() {
let wf = ParsedWatchFile::new(5).unwrap();
assert_eq!(wf.version(), 5);
assert_eq!(wf.entries().count(), 0);
}
#[cfg(feature = "linebased")]
#[test]
fn test_parsed_watch_file_new_v4() {
let wf = ParsedWatchFile::new(4).unwrap();
assert_eq!(wf.version(), 4);
assert_eq!(wf.entries().count(), 0);
}
#[cfg(feature = "deb822")]
#[test]
fn test_parsed_watch_file_add_entry_v5() {
let mut wf = ParsedWatchFile::new(5).unwrap();
let mut entry = wf.add_entry("https://github.com/foo/bar/tags", r".*/v?([\d.]+)\.tar\.gz");
assert_eq!(wf.entries().count(), 1);
assert_eq!(entry.url(), "https://github.com/foo/bar/tags");
assert_eq!(
entry.matching_pattern(),
Some(r".*/v?([\d.]+)\.tar\.gz".to_string())
);
entry.set_option(crate::types::WatchOption::Component("upstream".to_string()));
entry.set_option(crate::types::WatchOption::Compression(
crate::types::Compression::Xz,
));
assert_eq!(entry.get_option("Component"), Some("upstream".to_string()));
assert_eq!(entry.get_option("Compression"), Some("xz".to_string()));
}
#[cfg(feature = "linebased")]
#[test]
fn test_parsed_watch_file_add_entry_v4() {
let mut wf = ParsedWatchFile::new(4).unwrap();
let entry = wf.add_entry("https://github.com/foo/bar/tags", r".*/v?([\d.]+)\.tar\.gz");
assert_eq!(wf.entries().count(), 1);
assert_eq!(entry.url(), "https://github.com/foo/bar/tags");
assert_eq!(
entry.matching_pattern(),
Some(r".*/v?([\d.]+)\.tar\.gz".to_string())
);
}
#[cfg(feature = "deb822")]
#[test]
fn test_parsed_watch_file_roundtrip_with_add_entry() {
let mut wf = ParsedWatchFile::new(5).unwrap();
let mut entry = wf.add_entry(
"https://github.com/owner/repo/tags",
r".*/v?([\d.]+)\.tar\.gz",
);
entry.set_option(crate::types::WatchOption::Compression(
crate::types::Compression::Xz,
));
let output = wf.to_string();
let reparsed = parse(&output).unwrap();
assert_eq!(reparsed.version(), 5);
let entries: Vec<_> = reparsed.entries().collect();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].url(), "https://github.com/owner/repo/tags");
assert_eq!(entries[0].get_option("Compression"), Some("xz".to_string()));
}
#[cfg(feature = "linebased")]
#[test]
fn test_parsed_entry_set_url_v4() {
let mut wf = ParsedWatchFile::new(4).unwrap();
let mut entry = wf.add_entry("https://github.com/foo/bar/tags", r".*/v?([\d.]+)\.tar\.gz");
assert_eq!(entry.url(), "https://github.com/foo/bar/tags");
entry.set_url("https://github.com/foo/bar/releases");
assert_eq!(entry.url(), "https://github.com/foo/bar/releases");
}
#[cfg(feature = "deb822")]
#[test]
fn test_parsed_entry_set_url_v5() {
let mut wf = ParsedWatchFile::new(5).unwrap();
let mut entry = wf.add_entry("https://github.com/foo/bar/tags", r".*/v?([\d.]+)\.tar\.gz");
assert_eq!(entry.url(), "https://github.com/foo/bar/tags");
entry.set_url("https://github.com/foo/bar/releases");
assert_eq!(entry.url(), "https://github.com/foo/bar/releases");
}
#[cfg(feature = "linebased")]
#[test]
fn test_parsed_entry_set_matching_pattern_v4() {
let mut wf = ParsedWatchFile::new(4).unwrap();
let mut entry = wf.add_entry("https://github.com/foo/bar/tags", r".*/v?([\d.]+)\.tar\.gz");
assert_eq!(
entry.matching_pattern(),
Some(r".*/v?([\d.]+)\.tar\.gz".to_string())
);
entry.set_matching_pattern(r".*/release-([\d.]+)\.tar\.gz");
assert_eq!(
entry.matching_pattern(),
Some(r".*/release-([\d.]+)\.tar\.gz".to_string())
);
}
#[cfg(feature = "deb822")]
#[test]
fn test_parsed_entry_set_matching_pattern_v5() {
let mut wf = ParsedWatchFile::new(5).unwrap();
let mut entry = wf.add_entry("https://github.com/foo/bar/tags", r".*/v?([\d.]+)\.tar\.gz");
assert_eq!(
entry.matching_pattern(),
Some(r".*/v?([\d.]+)\.tar\.gz".to_string())
);
entry.set_matching_pattern(r".*/release-([\d.]+)\.tar\.gz");
assert_eq!(
entry.matching_pattern(),
Some(r".*/release-([\d.]+)\.tar\.gz".to_string())
);
}
#[cfg(feature = "linebased")]
#[test]
fn test_parsed_entry_line_v4() {
let content = "version=4\nhttps://example.com/ .*.tar.gz\nhttps://example2.com/ .*.tar.gz";
let wf = parse(content).unwrap();
let entries: Vec<_> = wf.entries().collect();
assert_eq!(entries[0].line(), 1); assert_eq!(entries[1].line(), 2); }
#[cfg(feature = "deb822")]
#[test]
fn test_parsed_entry_line_v5() {
let content = r#"Version: 5
Source: https://example.com/repo1
Matching-Pattern: .*\.tar\.gz
Source: https://example.com/repo2
Matching-Pattern: .*\.tar\.xz
"#;
let wf = parse(content).unwrap();
let entries: Vec<_> = wf.entries().collect();
assert_eq!(entries[0].line(), 2); assert_eq!(entries[1].line(), 5); }
#[cfg(feature = "linebased")]
#[test]
fn test_url_range_linebased() {
let content = "version=4\nhttps://example.com/ .*-([\\d.]+)\\.tar\\.gz\n";
let wf = parse(content).unwrap();
let entry = wf.entries().next().unwrap();
let range = entry.url_range().expect("entry has url");
let start: usize = range.start().into();
let end: usize = range.end().into();
assert_eq!(&content[start..end], "https://example.com/");
}
#[cfg(feature = "linebased")]
#[test]
fn test_matching_pattern_range_linebased() {
let content = "version=4\nhttps://example.com/ .*-([\\d.]+)\\.tar\\.gz\n";
let wf = parse(content).unwrap();
let entry = wf.entries().next().unwrap();
let range = entry.matching_pattern_range().expect("has pattern");
let start: usize = range.start().into();
let end: usize = range.end().into();
assert_eq!(&content[start..end], ".*-([\\d.]+)\\.tar\\.gz");
}
#[cfg(feature = "linebased")]
#[test]
fn test_option_range_linebased() {
let content = "version=4\nopts=mode=git,pretty=raw https://example.com/ .*\n";
let wf = parse(content).unwrap();
let entry = wf.entries().next().unwrap();
let mode = entry.option_range("mode").expect("mode option");
let start: usize = mode.start().into();
let end: usize = mode.end().into();
assert_eq!(&content[start..end], "mode=git");
let pretty = entry.option_range("pretty").expect("pretty option");
let start: usize = pretty.start().into();
let end: usize = pretty.end().into();
assert_eq!(&content[start..end], "pretty=raw");
assert!(entry.option_range("not-a-real-option").is_none());
}
#[cfg(feature = "linebased")]
#[test]
fn test_version_range_linebased() {
let content = "version=4\nhttps://example.com/ .*\n";
let wf = parse(content).unwrap();
let range = wf.version_range().expect("has version");
let start: usize = range.start().into();
let end: usize = range.end().into();
assert_eq!(&content[start..end], "version=4\n");
}
#[cfg(feature = "deb822")]
#[test]
fn test_url_range_deb822() {
let content =
"Version: 5\n\nSource: https://example.com/foo\nMatching-Pattern: .*\\.tar\\.gz\n";
let wf = parse(content).unwrap();
let entry = wf.entries().next().unwrap();
let range = entry.url_range().expect("has source");
let start: usize = range.start().into();
let end: usize = range.end().into();
assert_eq!(&content[start..end], "Source: https://example.com/foo\n");
}
#[cfg(feature = "deb822")]
#[test]
fn test_matching_pattern_range_deb822() {
let content =
"Version: 5\n\nSource: https://example.com/foo\nMatching-Pattern: v(.+)\\.tar\\.gz\n";
let wf = parse(content).unwrap();
let entry = wf.entries().next().unwrap();
let range = entry.matching_pattern_range().expect("has pattern");
let start: usize = range.start().into();
let end: usize = range.end().into();
assert_eq!(
&content[start..end],
"Matching-Pattern: v(.+)\\.tar\\.gz\n"
);
}
#[cfg(feature = "deb822")]
#[test]
fn test_option_range_deb822_lookup_capitalises_key() {
let content =
"Version: 5\n\nSource: https://example.com/foo\nMatching-Pattern: x\nMode: git\n";
let wf = parse(content).unwrap();
let entry = wf.entries().next().unwrap();
let range = entry.option_range("mode").expect("mode field");
let start: usize = range.start().into();
let end: usize = range.end().into();
assert_eq!(&content[start..end], "Mode: git\n");
}
#[cfg(feature = "deb822")]
#[test]
fn test_version_range_deb822() {
let content =
"Version: 5\n\nSource: https://example.com/foo\nMatching-Pattern: x\n";
let wf = parse(content).unwrap();
let range = wf.version_range().expect("has version");
let start: usize = range.start().into();
let end: usize = range.end().into();
assert_eq!(&content[start..end], "Version: 5\n");
}
#[cfg(feature = "deb822")]
#[test]
fn test_template_range_deb822() {
let content = "Version: 5\n\nSource: https://github.com/foo/bar\nTemplate: GitHub\n";
let wf = parse(content).unwrap();
let entry = wf.entries().next().unwrap();
let range = entry.template_range().expect("has template");
let start: usize = range.start().into();
let end: usize = range.end().into();
assert_eq!(&content[start..end], "Template: GitHub\n");
assert_eq!(entry.template_kind(), Some("GitHub".to_string()));
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Parse {
inner: ParseInner,
}
#[derive(Clone, PartialEq, Eq)]
enum ParseInner {
#[cfg(feature = "linebased")]
LineBased(crate::linebased::Parse<crate::linebased::WatchFile>),
#[cfg(feature = "deb822")]
Deb822(deb822_lossless::Parse<deb822_lossless::Deb822>),
}
impl Parse {
pub fn parse(text: &str) -> Self {
let version = detect_version(text);
let inner = match version {
#[cfg(feature = "linebased")]
Some(WatchFileVersion::LineBased(_)) => {
ParseInner::LineBased(crate::linebased::parse_watch_file(text))
}
#[cfg(feature = "deb822")]
Some(WatchFileVersion::Deb822) => {
ParseInner::Deb822(deb822_lossless::Deb822::parse(text))
}
#[cfg(not(feature = "linebased"))]
Some(WatchFileVersion::LineBased(_)) => {
#[cfg(feature = "deb822")]
{
ParseInner::Deb822(deb822_lossless::Deb822::parse(text))
}
#[cfg(not(feature = "deb822"))]
{
panic!("No watch file parsing features enabled")
}
}
#[cfg(not(feature = "deb822"))]
Some(WatchFileVersion::Deb822) => {
#[cfg(feature = "linebased")]
{
ParseInner::LineBased(crate::linebased::parse_watch_file(text))
}
#[cfg(not(feature = "linebased"))]
{
panic!("No watch file parsing features enabled")
}
}
None => {
#[cfg(feature = "linebased")]
{
ParseInner::LineBased(crate::linebased::parse_watch_file(text))
}
#[cfg(not(feature = "linebased"))]
#[cfg(feature = "deb822")]
{
ParseInner::Deb822(deb822_lossless::Deb822::parse(text))
}
#[cfg(not(any(feature = "linebased", feature = "deb822")))]
{
panic!("No watch file parsing features enabled")
}
}
};
Parse { inner }
}
pub fn to_watch_file(&self) -> ParsedWatchFile {
match &self.inner {
#[cfg(feature = "linebased")]
ParseInner::LineBased(parse) => ParsedWatchFile::LineBased(parse.tree()),
#[cfg(feature = "deb822")]
ParseInner::Deb822(parse) => {
let deb822 = parse.tree();
ParsedWatchFile::Deb822(crate::deb822::WatchFile::from_deb822(deb822))
}
}
}
pub fn version(&self) -> u32 {
match &self.inner {
#[cfg(feature = "linebased")]
ParseInner::LineBased(parse) => parse.tree().version(),
#[cfg(feature = "deb822")]
ParseInner::Deb822(_) => 5,
}
}
}
unsafe impl Send for Parse {}
unsafe impl Sync for Parse {}