1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
use std::collections::btree_map::{BTreeMap, Entry as BTreeMapEntry}; use self::super::super::{HrxEntryData, HrxEntry, HrxError, HrxPath}; use linked_hash_map::LinkedHashMap; use std::num::NonZeroUsize; /// Search the specified for the length of the first `boundary`. /// /// Returns `None` if no valid boundary exists. /// /// # Examples /// /// ``` /// # use hrx::parse::discover_first_boundary_length; /// # use std::num::NonZeroUsize; /// assert_eq!(discover_first_boundary_length("<=====>"), NonZeroUsize::new(5)); /// assert_eq!(discover_first_boundary_length("henlo\n<===> menlo"), NonZeroUsize::new(3)); /// /// assert_eq!(discover_first_boundary_length("<>"), None); /// assert_eq!(discover_first_boundary_length("коммунизм"), None); /// ``` pub fn discover_first_boundary_length<S: AsRef<str>>(in_data: S) -> Option<NonZeroUsize> { discover_first_boundary_length_impl(in_data.as_ref()) } fn discover_first_boundary_length_impl(in_data: &str) -> Option<NonZeroUsize> { let begin = ascii_chars!('<').find(in_data)?; let length = ascii_chars!('>').find(&in_data[begin + 1..])?; // Searching from start of "====="s, so 0-based insdex of ">" will be their length NonZeroUsize::new(length) } /// Convert a collexion of `(path, entry)` pairs into a `path -> entry` map, erroring on any duplicates and file-as-dir usages. /// /// # Examples /// /// Dupe: /// /// ``` /// # use hrx::{HrxEntryData, HrxEntry, HrxError, HrxPath}; /// # use hrx::parse::reduce_raw_entries_and_validate_directory_tree; /// # use std::num::NonZeroUsize; /// let mut source_material = vec![("file1.txt".parse().unwrap(), /// HrxEntry { /// comment: None, /// data: HrxEntryData::File { /// body: Some("First file's contents".to_string()) /// } /// }), /// ("file2.txt".parse().unwrap(), /// HrxEntry { /// comment: None, /// data: HrxEntryData::File { /// body: Some("Second file's contents".to_string()) /// } /// })]; /// /// // The no-dupe case /// assert_eq!(reduce_raw_entries_and_validate_directory_tree(source_material.clone()), /// Ok(source_material.iter().cloned().collect())); /// /// // Introducing a dupe, now both files have the same paths /// source_material[1].0 = source_material[0].0.clone(); /// /// assert_eq!(reduce_raw_entries_and_validate_directory_tree(source_material.clone()), /// Err(HrxError::DuplicateEntry(source_material[0].0.to_string()))); /// // i.e. /// assert_eq!(reduce_raw_entries_and_validate_directory_tree(source_material.clone()), /// Err(HrxError::DuplicateEntry("file1.txt".to_string()))); /// ``` /// /// File as directory: /// /// ``` /// # use hrx::{HrxEntryData, HrxEntry, HrxError, HrxPath}; /// # use hrx::parse::reduce_raw_entries_and_validate_directory_tree; /// # use std::num::NonZeroUsize; /// let mut source_material = vec![("file1.txt".parse().unwrap(), /// HrxEntry { /// comment: None, /// data: HrxEntryData::File { /// body: Some("First file's contents".to_string()) /// } /// }), /// ("file1.txt/subfile.txt".parse().unwrap(), /// HrxEntry { /// comment: None, /// data: HrxEntryData::File { /// body: Some("Second file's contents, using the first file as directory".to_string()) /// } /// })]; /// /// assert_eq!(reduce_raw_entries_and_validate_directory_tree(source_material.clone()), /// Err(HrxError::FileAsDirectory(source_material[0].0.to_string(), source_material[1].0.to_string()))); /// // i.e. /// assert_eq!(reduce_raw_entries_and_validate_directory_tree(source_material.clone()), /// Err(HrxError::FileAsDirectory("file1.txt".to_string(), "file1.txt/subfile.txt".to_string()))); /// ``` pub fn reduce_raw_entries_and_validate_directory_tree<Ii: IntoIterator<Item = (HrxPath, HrxEntry)>>(iter: Ii) -> Result<LinkedHashMap<HrxPath, HrxEntry>, HrxError> { let iter = iter.into_iter(); let mut map = LinkedHashMap::with_capacity(iter.size_hint().0); let mut paths = BTreeMap::new(); for (k, v) in iter { reduce_raw_entry_and_validate_its_directory_tree(k, v, &mut map, &mut paths)?; } Ok(map) } fn reduce_raw_entry_and_validate_its_directory_tree(k: HrxPath, v: HrxEntry, map: &mut LinkedHashMap<HrxPath, HrxEntry>, paths: &mut BTreeMap<String, bool>) -> Result<(), HrxError> { for (slash_i, _) in k.0.match_indices('/') { match paths.entry(k.0[0..slash_i].to_string()) { BTreeMapEntry::Vacant(ve) => { ve.insert(true); } BTreeMapEntry::Occupied(oe) => { if !oe.get() { return Err(HrxError::FileAsDirectory(oe.key().to_string(), k.0.clone())); } } } } match paths.entry(k.0) { BTreeMapEntry::Vacant(ve) => { let is_dir = v.data == HrxEntryData::Directory; map.insert(HrxPath(ve.key().clone()), v); ve.insert(is_dir); } BTreeMapEntry::Occupied(oe) => { return Err(HrxError::DuplicateEntry(oe.remove_entry().0)); } } Ok(()) }