plist-1.7.0/.cargo_vcs_info.json0000644000000001360000000000100121740ustar { "git": { "sha1": "f0a8cd66fbb81dc6dccc097b0e5df5da5fbcb672" }, "path_in_vcs": "" }plist-1.7.0/.editorconfig000064400000000000000000000004311046102023000134370ustar 00000000000000# EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 4 plist-1.7.0/.github/workflows/ci.yml000064400000000000000000000072771046102023000155140ustar 00000000000000on: push: branches: - master pull_request: branches: - master name: CI env: CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse jobs: build_and_test: name: Build and Test runs-on: ${{ matrix.os }} strategy: matrix: include: - os: ubuntu-22.04 target: x86_64-unknown-linux-gnu channel: 1.68.0 - os: ubuntu-22.04 target: x86_64-unknown-linux-gnu channel: stable - os: ubuntu-22.04 target: x86_64-unknown-linux-gnu channel: beta - os: ubuntu-22.04 target: x86_64-unknown-linux-gnu channel: nightly steps: - name: Checkout uses: actions/checkout@v3 - name: Install Rust Toolchain run: | rustup default "$TOOLCHAIN" rustup update "$TOOLCHAIN" env: TOOLCHAIN: ${{ matrix.channel }}-${{ matrix.target }} - name: Build and Test (Debug, No Default Features) run: cargo test --no-default-features - name: Build and Test (Debug, enable_unstable_features_that_may_break_with_minor_version_bumps) run: cargo test --no-default-features --features enable_unstable_features_that_may_break_with_minor_version_bumps - name: Build and Test (Debug, serde) run: cargo test --no-default-features --features serde - name: Build and Test (Release, All Features) run: cargo test --release --all-features - name: Build and Test (Minimal Versions, All Features) if: matrix.channel == 'nightly' run: cargo test --all-features -Z minimal-versions lint: name: Lint runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v3 - name: Check for broken intra-doc links run: cargo doc --all-features --document-private-items --no-deps ci_succeeded: name: Build and Test Succeeded if: always() needs: [build_and_test, lint] runs-on: ubuntu-22.04 outputs: release: ${{ steps.release_check.outputs.release }} tag_name: ${{ steps.release_check.outputs.tag_name }} steps: - name: Fail if Any Previous Job Failed if: contains(needs.*.result, 'failure') run: exit 1 - name: Checkout uses: actions/checkout@v3 - name: Check if Release Needed id: release_check run: | set -euo pipefail TAG_NAME="v$(cargo metadata --no-deps --format-version=1 | jq -er '.packages[] | select(.name=="plist") | .version')" echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT" if git ls-remote --exit-code origin "refs/tags/$TAG_NAME" then echo "release=false" >> "$GITHUB_OUTPUT" else echo "release=true" >> "$GITHUB_OUTPUT" fi release: name: Release needs: ci_succeeded if: github.ref == 'refs/heads/master' && needs.ci_succeeded.outputs.release == 'true' runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v3 - name: Install cargo-semver-checks run: cargo install cargo-semver-checks --locked - name: Run cargo-semver-checks run: cargo semver-checks check-release - name: Publish Crate run: | cargo publish --token "$CRATES_IO_TOKEN" env: CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} - name: Create GitHub Release uses: actions/create-release@latest env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ needs.ci_succeeded.outputs.tag_name }} release_name: ${{ needs.ci_succeeded.outputs.tag_name }} draft: false prerelease: false plist-1.7.0/.gitignore000064400000000000000000000000301046102023000127450ustar 00000000000000target Cargo.lock .idea plist-1.7.0/Cargo.toml0000644000000026070000000000100101770ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.68" name = "plist" version = "1.7.0" authors = ["Ed Barnard "] description = "A rusty plist parser. Supports Serde serialization." documentation = "https://docs.rs/plist/" readme = "README.md" keywords = [ "plist", "parser", ] categories = [ "config", "encoding", "parser-implementations", ] license = "MIT" repository = "https://github.com/ebarnard/rust-plist/" [dependencies.base64] version = "0.22.0" [dependencies.indexmap] version = "2.1.0" [dependencies.quick_xml] version = "0.32.0" package = "quick-xml" [dependencies.serde] version = "1.0.2" optional = true [dependencies.time] version = "0.3.30" features = [ "parsing", "formatting", ] [dev-dependencies.serde_derive] version = "1.0.2" [dev-dependencies.serde_yaml] version = "0.8.21" [features] default = ["serde"] enable_unstable_features_that_may_break_with_minor_version_bumps = [] plist-1.7.0/Cargo.toml.orig000064400000000000000000000014511046102023000136540ustar 00000000000000[package] name = "plist" version = "1.7.0" authors = ["Ed Barnard "] description = "A rusty plist parser. Supports Serde serialization." license = "MIT" repository = "https://github.com/ebarnard/rust-plist/" documentation = "https://docs.rs/plist/" keywords = ["plist", "parser"] categories = ["config", "encoding", "parser-implementations"] edition = "2021" rust-version = "1.68" [features] default = ["serde"] enable_unstable_features_that_may_break_with_minor_version_bumps = [] [dependencies] base64 = "0.22.0" time = { version = "0.3.30", features = ["parsing", "formatting"] } indexmap = "2.1.0" quick_xml = { package = "quick-xml", version = "0.32.0" } serde = { version = "1.0.2", optional = true } [dev-dependencies] serde_derive = { version = "1.0.2" } serde_yaml = "0.8.21" plist-1.7.0/LICENCE000064400000000000000000000020411046102023000117460ustar 00000000000000Copyright (c) 2015 Edward Barnard Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.plist-1.7.0/README.md000064400000000000000000000007211046102023000122430ustar 00000000000000# Plist A rusty plist parser. Many features from previous versions are now hidden behind the `enable_unstable_features_that_may_break_with_minor_version_bumps` feature. These will break in minor version releases after the 1.0 release. If you really really must use them you should specify a tilde requirement e.g. `plist = "~1.0.3"` in you `Cargo.toml` so that the plist crate is not automatically updated to version 1.1. [Documentation](https://docs.rs/plist/) plist-1.7.0/rustfmt.toml000064400000000000000000000000341046102023000133620ustar 00000000000000imports_granularity="Crate" plist-1.7.0/src/data.rs000064400000000000000000000067551046102023000130470ustar 00000000000000use std::fmt; use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine}; use crate::stream::xml_encode_data_base64; /// A byte buffer used for serialization to and from the plist data type. /// /// You use it in types with derived `Serialize`/`Deserialize` traits. /// /// ## Examples /// /// ```rust /// extern crate plist; /// #[macro_use] /// extern crate serde_derive; /// /// # fn main() { /// #[derive(Deserialize, Serialize)] /// struct Info { /// blob: plist::Data, /// } /// /// let actual = Info { blob: plist::Data::new(vec![1, 2, 3, 4]) }; /// /// let mut xml_byte_buffer: Vec = vec![]; /// plist::to_writer_xml(&mut xml_byte_buffer, &actual) /// .expect("serialize into xml"); /// /// let expected: Info = plist::from_reader_xml(xml_byte_buffer.as_slice()) /// .expect("deserialize from xml"); /// /// assert_eq!(actual.blob, expected.blob); /// # } /// ``` #[derive(Clone, PartialEq, Eq)] pub struct Data { inner: Vec, } /// An error indicating a string was not valid XML data. #[derive(Debug)] pub struct InvalidXmlData(base64::DecodeError); impl Data { /// Creates a new `Data` from vec of bytes. pub fn new(bytes: Vec) -> Self { Data { inner: bytes } } /// Create a `Data` object from an XML plist (Base-64) encoded string. pub fn from_xml_format(b64_str: &str) -> Result { BASE64_STANDARD .decode(b64_str) .map_err(InvalidXmlData) .map(Data::new) } /// Converts the `Data` to an XML plist (Base-64) string. pub fn to_xml_format(&self) -> String { xml_encode_data_base64(&self.inner) } } impl From> for Data { fn from(from: Vec) -> Self { Data { inner: from } } } impl From for Vec { fn from(from: Data) -> Self { from.inner } } impl AsRef<[u8]> for Data { fn as_ref(&self) -> &[u8] { self.inner.as_ref() } } impl AsMut<[u8]> for Data { fn as_mut(&mut self) -> &mut [u8] { self.inner.as_mut() } } impl fmt::Debug for Data { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.inner.fmt(f) } } impl fmt::Display for InvalidXmlData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Invalid XML data: '{}'", self.0) } } impl std::error::Error for InvalidXmlData {} pub mod serde_impls { use serde::{de, ser}; use std::fmt; use crate::Data; impl ser::Serialize for Data { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { serializer.serialize_bytes(self.as_ref()) } } struct DataVisitor; impl<'de> de::Visitor<'de> for DataVisitor { type Value = Data; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a byte array") } fn visit_bytes(self, v: &[u8]) -> Result where E: de::Error, { self.visit_byte_buf(v.to_owned()) } fn visit_byte_buf(self, v: Vec) -> Result where E: de::Error, { Ok(v.into()) } } impl<'de> de::Deserialize<'de> for Data { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { deserializer.deserialize_byte_buf(DataVisitor) } } } plist-1.7.0/src/date.rs000064400000000000000000000133731046102023000130450ustar 00000000000000use std::{ fmt, time::{Duration, SystemTime, UNIX_EPOCH}, }; use time::{format_description::well_known::Rfc3339, OffsetDateTime, UtcOffset}; /// A UTC timestamp used for serialization to and from the plist date type. /// /// Note that while this type implements `Serialize` and `Deserialize` it will behave strangely if /// used with serializers from outside this crate. #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub struct Date { inner: SystemTime, } /// An error indicating that a string was not a valid XML plist date. #[derive(Debug)] #[non_exhaustive] pub struct InvalidXmlDate; pub(crate) struct InfiniteOrNanDate; impl Date { /// The unix timestamp of the plist epoch. const PLIST_EPOCH_UNIX_TIMESTAMP: Duration = Duration::from_secs(978_307_200); /// Converts an XML plist date string to a `Date`. pub fn from_xml_format(date: &str) -> Result { let offset: OffsetDateTime = OffsetDateTime::parse(date, &Rfc3339) .map_err(|_| InvalidXmlDate)? .to_offset(UtcOffset::UTC); Ok(Date { inner: offset.into(), }) } /// Converts the `Date` to an XML plist date string. pub fn to_xml_format(&self) -> String { let datetime: OffsetDateTime = self.inner.into(); datetime.format(&Rfc3339).unwrap() } pub(crate) fn from_seconds_since_plist_epoch( timestamp: f64, ) -> Result { // `timestamp` is the number of seconds since the plist epoch of 1/1/2001 00:00:00. let plist_epoch = UNIX_EPOCH + Date::PLIST_EPOCH_UNIX_TIMESTAMP; if !timestamp.is_finite() { return Err(InfiniteOrNanDate); } let is_negative = timestamp < 0.0; let timestamp = timestamp.abs(); let seconds = timestamp.floor() as u64; let subsec_nanos = (timestamp.fract() * 1e9) as u32; let dur_since_plist_epoch = Duration::new(seconds, subsec_nanos); let inner = if is_negative { plist_epoch.checked_sub(dur_since_plist_epoch) } else { plist_epoch.checked_add(dur_since_plist_epoch) }; let inner = inner.ok_or(InfiniteOrNanDate)?; Ok(Date { inner }) } pub(crate) fn as_seconds_since_plist_epoch(&self) -> f64 { // needed until #![feature(duration_float)] is stabilized fn as_secs_f64(d: Duration) -> f64 { const NANOS_PER_SEC: f64 = 1_000_000_000.00; (d.as_secs() as f64) + f64::from(d.subsec_nanos()) / NANOS_PER_SEC } let plist_epoch = UNIX_EPOCH + Date::PLIST_EPOCH_UNIX_TIMESTAMP; match self.inner.duration_since(plist_epoch) { Ok(dur_since_plist_epoch) => as_secs_f64(dur_since_plist_epoch), Err(err) => -as_secs_f64(err.duration()), } } } impl fmt::Debug for Date { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "{}", self.to_xml_format()) } } impl From for Date { fn from(date: SystemTime) -> Self { Date { inner: date } } } impl From for SystemTime { fn from(val: Date) -> Self { val.inner } } impl fmt::Display for InvalidXmlDate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("String was not a valid XML plist date") } } impl std::error::Error for InvalidXmlDate {} #[cfg(feature = "serde")] pub mod serde_impls { use serde::{ de::{Deserialize, Deserializer, Error, Unexpected, Visitor}, ser::{Serialize, Serializer}, }; use std::fmt; use crate::Date; pub const DATE_NEWTYPE_STRUCT_NAME: &str = "PLIST-DATE"; impl Serialize for Date { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let date_str = self.to_xml_format(); serializer.serialize_newtype_struct(DATE_NEWTYPE_STRUCT_NAME, &date_str) } } struct DateNewtypeVisitor; impl<'de> Visitor<'de> for DateNewtypeVisitor { type Value = Date; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a plist date newtype") } fn visit_str(self, v: &str) -> Result where E: Error, { DateStrVisitor.visit_str(v) } fn visit_newtype_struct(self, deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_str(DateStrVisitor) } } struct DateStrVisitor; impl<'de> Visitor<'de> for DateStrVisitor { type Value = Date; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a plist date string") } fn visit_str(self, v: &str) -> Result where E: Error, { Date::from_xml_format(v).map_err(|_| E::invalid_value(Unexpected::Str(v), &self)) } } impl<'de> Deserialize<'de> for Date { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_newtype_struct(DATE_NEWTYPE_STRUCT_NAME, DateNewtypeVisitor) } } } #[cfg(test)] mod testing { use super::*; #[test] fn date_roundtrip() { let date_str = "1981-05-16T11:32:06Z"; let date = Date::from_xml_format(date_str).expect("should parse"); let generated_str = date.to_xml_format(); assert_eq!(date_str, generated_str); } #[test] fn far_past_date() { let date_str = "1920-01-01T00:00:00Z"; Date::from_xml_format(date_str).expect("should parse"); } } plist-1.7.0/src/de.rs000064400000000000000000000333711046102023000125200ustar 00000000000000use serde::de::{ self, value::{MapAccessDeserializer, MapDeserializer}, IntoDeserializer, }; use std::{ borrow::Cow, fmt::Display, fs::File, io::{BufReader, Cursor, Read, Seek}, iter::Peekable, mem, path::Path, }; use crate::{ date::serde_impls::DATE_NEWTYPE_STRUCT_NAME, error::{self, Error, ErrorKind, EventKind}, stream::{self, Event}, u64_to_usize, uid::serde_impls::UID_NEWTYPE_STRUCT_NAME, value::serde_impls::VALUE_NEWTYPE_STRUCT_NAME, Value, }; macro_rules! expect { ($next:expr, $kind:expr) => { match $next { Some(Ok(ref event)) if EventKind::of_event(event) != $kind => { return Err(error::unexpected_event_type($kind, event))?; } Some(Ok(event)) => event, Some(Err(err)) => return Err(err), None => return Err(ErrorKind::UnexpectedEndOfEventStream.without_position()), } }; } macro_rules! try_next { ($next:expr) => { match $next { Some(Ok(event)) => event, Some(Err(err)) => return Err(err)?, None => return Err(ErrorKind::UnexpectedEndOfEventStream.without_position())?, } }; } #[doc(hidden)] impl de::Error for Error { fn custom(msg: T) -> Self { ErrorKind::Serde(msg.to_string()).without_position() } } enum OptionMode { Root, StructField, Explicit, } /// A structure that deserializes plist event streams into Rust values. pub struct Deserializer<'event, I> where I: IntoIterator, Error>>, { events: Peekable<::IntoIter>, option_mode: OptionMode, in_plist_value: bool, } impl<'event, I> Deserializer<'event, I> where I: IntoIterator, Error>>, { pub fn new(iter: I) -> Deserializer<'event, I> { Deserializer { events: iter.into_iter().peekable(), option_mode: OptionMode::Root, in_plist_value: false, } } fn with_option_mode) -> Result>( &mut self, option_mode: OptionMode, f: F, ) -> Result { let prev_option_mode = mem::replace(&mut self.option_mode, option_mode); let ret = f(&mut *self); self.option_mode = prev_option_mode; ret } fn enter_plist_value) -> Result>( &mut self, f: F, ) -> Result { let prev = mem::replace(&mut self.in_plist_value, true); let ret = f(&mut *self); self.in_plist_value = prev; ret } } impl<'de, 'a, 'event, I> de::Deserializer<'de> for &'a mut Deserializer<'event, I> where I: IntoIterator, Error>>, { type Error = Error; fn deserialize_any(self, visitor: V) -> Result where V: de::Visitor<'de>, { match try_next!(self.events.next()) { Event::StartArray(len) => { let len = len.and_then(u64_to_usize); let ret = visitor.visit_seq(MapAndSeqAccess::new(self, false, len))?; expect!(self.events.next(), EventKind::EndCollection); Ok(ret) } Event::StartDictionary(len) => { let len = len.and_then(u64_to_usize); let ret = visitor.visit_map(MapAndSeqAccess::new(self, false, len))?; expect!(self.events.next(), EventKind::EndCollection); Ok(ret) } event @ Event::EndCollection => Err(error::unexpected_event_type( EventKind::ValueOrStartCollection, &event, )), Event::Boolean(v) => visitor.visit_bool(v), Event::Data(Cow::Borrowed(v)) => visitor.visit_bytes(v), Event::Data(Cow::Owned(v)) => visitor.visit_byte_buf(v), Event::Date(v) if self.in_plist_value => { visitor.visit_enum(MapAccessDeserializer::new(MapDeserializer::new( [(DATE_NEWTYPE_STRUCT_NAME, v.to_xml_format())].into_iter(), ))) } Event::Date(v) => visitor.visit_string(v.to_xml_format()), Event::Integer(v) => { if let Some(v) = v.as_unsigned() { visitor.visit_u64(v) } else if let Some(v) = v.as_signed() { visitor.visit_i64(v) } else { unreachable!() } } Event::Real(v) => visitor.visit_f64(v), Event::String(Cow::Borrowed(v)) => visitor.visit_str(v), Event::String(Cow::Owned(v)) => visitor.visit_string(v), Event::Uid(v) if self.in_plist_value => visitor.visit_enum(MapAccessDeserializer::new( MapDeserializer::new([(UID_NEWTYPE_STRUCT_NAME, v.get())].into_iter()), )), Event::Uid(v) => visitor.visit_u64(v.get()), } } forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq bytes byte_buf map unit_struct tuple_struct tuple ignored_any identifier } fn deserialize_unit(self, visitor: V) -> Result where V: de::Visitor<'de>, { expect!(self.events.next(), EventKind::String); visitor.visit_unit() } fn deserialize_option(self, visitor: V) -> Result where V: de::Visitor<'de>, { match self.option_mode { OptionMode::Root => { if self.events.peek().is_none() { visitor.visit_none::() } else { self.with_option_mode(OptionMode::Explicit, |this| visitor.visit_some(this)) } } OptionMode::StructField => { // None struct values are ignored so if we're here the value must be Some. self.with_option_mode(OptionMode::Explicit, |this| visitor.visit_some(this)) } OptionMode::Explicit => { expect!(self.events.next(), EventKind::StartDictionary); let ret = match try_next!(self.events.next()) { Event::String(ref s) if &s[..] == "None" => { expect!(self.events.next(), EventKind::String); visitor.visit_none::()? } Event::String(ref s) if &s[..] == "Some" => visitor.visit_some(&mut *self)?, event => return Err(error::unexpected_event_type(EventKind::String, &event))?, }; expect!(self.events.next(), EventKind::EndCollection); Ok(ret) } } } fn deserialize_newtype_struct( self, name: &'static str, visitor: V, ) -> Result where V: de::Visitor<'de>, { if name == VALUE_NEWTYPE_STRUCT_NAME { self.enter_plist_value(|this| visitor.visit_newtype_struct(this)) } else { visitor.visit_newtype_struct(self) } } fn deserialize_struct( self, _name: &'static str, _fields: &'static [&'static str], visitor: V, ) -> Result where V: de::Visitor<'de>, { expect!(self.events.next(), EventKind::StartDictionary); let ret = visitor.visit_map(MapAndSeqAccess::new(self, true, None))?; expect!(self.events.next(), EventKind::EndCollection); Ok(ret) } fn deserialize_enum( self, name: &'static str, variants: &'static [&'static str], visitor: V, ) -> Result where V: de::Visitor<'de>, { let event = self.events.next(); // `plist` since v1.1 serialises unit enum variants as plain strings. if let Some(Ok(Event::String(s))) = event { return match s { Cow::Borrowed(s) => s .into_deserializer() .deserialize_enum(name, variants, visitor), Cow::Owned(s) => s .into_deserializer() .deserialize_enum(name, variants, visitor), }; } expect!(event, EventKind::StartDictionary); let ret = visitor.visit_enum(&mut *self)?; expect!(self.events.next(), EventKind::EndCollection); Ok(ret) } } impl<'de, 'a, 'event, I> de::EnumAccess<'de> for &'a mut Deserializer<'event, I> where I: IntoIterator, Error>>, { type Error = Error; type Variant = Self; fn variant_seed(self, seed: V) -> Result<(V::Value, Self), Error> where V: de::DeserializeSeed<'de>, { Ok((seed.deserialize(&mut *self)?, self)) } } impl<'de, 'a, 'event, I> de::VariantAccess<'de> for &'a mut Deserializer<'event, I> where I: IntoIterator, Error>>, { type Error = Error; fn unit_variant(self) -> Result<(), Error> { <() as de::Deserialize>::deserialize(self) } fn newtype_variant_seed(self, seed: T) -> Result where T: de::DeserializeSeed<'de>, { seed.deserialize(self) } fn tuple_variant(self, len: usize, visitor: V) -> Result where V: de::Visitor<'de>, { de::Deserializer::deserialize_tuple(self, len, visitor) } fn struct_variant( self, fields: &'static [&'static str], visitor: V, ) -> Result where V: de::Visitor<'de>, { let name = ""; de::Deserializer::deserialize_struct(self, name, fields, visitor) } } struct MapAndSeqAccess<'a, 'event, I> where I: 'a + IntoIterator, Error>>, { de: &'a mut Deserializer<'event, I>, is_struct: bool, remaining: Option, } impl<'a, 'event, I> MapAndSeqAccess<'a, 'event, I> where I: 'a + IntoIterator, Error>>, { fn new( de: &'a mut Deserializer<'event, I>, is_struct: bool, len: Option, ) -> MapAndSeqAccess<'a, 'event, I> { MapAndSeqAccess { de, is_struct, remaining: len, } } } impl<'de, 'a, 'event, I> de::SeqAccess<'de> for MapAndSeqAccess<'a, 'event, I> where I: 'a + IntoIterator, Error>>, { type Error = Error; fn next_element_seed(&mut self, seed: T) -> Result, Error> where T: de::DeserializeSeed<'de>, { if let Some(&Ok(Event::EndCollection)) = self.de.events.peek() { return Ok(None); } self.remaining = self.remaining.map(|r| r.saturating_sub(1)); self.de .with_option_mode(OptionMode::Explicit, |this| seed.deserialize(this)) .map(Some) } fn size_hint(&self) -> Option { self.remaining } } impl<'de, 'a, 'event, I> de::MapAccess<'de> for MapAndSeqAccess<'a, 'event, I> where I: 'a + IntoIterator, Error>>, { type Error = Error; fn next_key_seed(&mut self, seed: K) -> Result, Error> where K: de::DeserializeSeed<'de>, { if let Some(&Ok(Event::EndCollection)) = self.de.events.peek() { return Ok(None); } self.remaining = self.remaining.map(|r| r.saturating_sub(1)); self.de .with_option_mode(OptionMode::Explicit, |this| seed.deserialize(this)) .map(Some) } fn next_value_seed(&mut self, seed: V) -> Result where V: de::DeserializeSeed<'de>, { let option_mode = if self.is_struct { OptionMode::StructField } else { OptionMode::Explicit }; self.de .with_option_mode(option_mode, |this| seed.deserialize(this)) } fn size_hint(&self) -> Option { self.remaining } } /// Deserializes an instance of type `T` from a byte slice. pub fn from_bytes(bytes: &[u8]) -> Result { let cursor = Cursor::new(bytes); from_reader(cursor) } /// Deserializes an instance of type `T` from a plist file of any encoding. pub fn from_file, T: de::DeserializeOwned>(path: P) -> Result { let file = File::open(path).map_err(error::from_io_without_position)?; from_reader(BufReader::new(file)) } /// Deserializes an instance of type `T` from a seekable byte stream containing a plist of any encoding. pub fn from_reader(reader: R) -> Result { let reader = stream::Reader::new(reader); let mut de = Deserializer::new(reader); de::Deserialize::deserialize(&mut de) } /// Deserializes an instance of type `T` from a byte stream containing an ASCII encoded plist. pub fn from_reader_ascii(reader: R) -> Result { let reader = stream::AsciiReader::new(reader); let mut de = Deserializer::new(reader); de::Deserialize::deserialize(&mut de) } /// Deserializes an instance of type `T` from a byte stream containing an XML encoded plist. pub fn from_reader_xml(reader: R) -> Result { let reader = stream::XmlReader::new(BufReader::new(reader)); let mut de = Deserializer::new(reader); de::Deserialize::deserialize(&mut de) } /// Interprets a [`Value`] as an instance of type `T`. pub fn from_value(value: &Value) -> Result { let events = value.events().map(Ok); let mut de = Deserializer::new(events); de::Deserialize::deserialize(&mut de) } plist-1.7.0/src/dictionary.rs000064400000000000000000000471331046102023000142760ustar 00000000000000//! A map of String to plist::Value. //! //! The map is currently backed by an [`IndexMap`]. This may be changed in a future minor release. //! //! [`IndexMap`]: https://docs.rs/indexmap/latest/indexmap/map/struct.IndexMap.html use indexmap::{map, IndexMap}; use std::{ fmt::{self, Debug}, ops, }; use crate::Value; /// Represents a plist dictionary type. #[derive(Clone, Default, PartialEq)] pub struct Dictionary { map: IndexMap, } impl Dictionary { /// Makes a new empty `Dictionary`. #[inline] pub fn new() -> Self { Dictionary { map: IndexMap::new(), } } /// Clears the dictionary, removing all values. #[inline] pub fn clear(&mut self) { self.map.clear() } /// Returns a reference to the value corresponding to the key. #[inline] pub fn get(&self, key: &str) -> Option<&Value> { self.map.get(key) } /// Returns true if the dictionary contains a value for the specified key. #[inline] pub fn contains_key(&self, key: &str) -> bool { self.map.contains_key(key) } /// Returns a mutable reference to the value corresponding to the key. #[inline] pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> { self.map.get_mut(key) } /// Inserts a key-value pair into the dictionary. /// /// If the dictionary did not have this key present, `None` is returned. /// /// If the dictionary did have this key present, the value is updated, and the old value is /// returned. #[inline] pub fn insert(&mut self, k: String, v: Value) -> Option { self.map.insert(k, v) } /// Removes a key from the dictionary, returning the value at the key if the key was previously /// in the dictionary. #[inline] pub fn remove(&mut self, key: &str) -> Option { self.map.swap_remove(key) } /// Scan through each key-value pair in the map and keep those where the /// closure `keep` returns `true`. #[inline] pub fn retain(&mut self, keep: F) where F: FnMut(&String, &mut Value) -> bool, { self.map.retain(keep) } /// Sort the dictionary keys. /// /// This uses the default ordering defined on [`str`]. /// /// This function is useful if you are serializing to XML, and wish to /// ensure a consistent key order. #[inline] pub fn sort_keys(&mut self) { self.map.sort_keys() } /// Gets the given key's corresponding entry in the dictionary for in-place manipulation. // Entry functionality is unstable until I can figure out how to use either Cow or // T: AsRef + Into #[cfg(any( test, feature = "enable_unstable_features_that_may_break_with_minor_version_bumps" ))] pub fn entry(&mut self, key: S) -> Entry where S: Into, { match self.map.entry(key.into()) { map::Entry::Vacant(vacant) => Entry::Vacant(VacantEntry { vacant }), map::Entry::Occupied(occupied) => Entry::Occupied(OccupiedEntry { occupied }), } } /// Returns the number of elements in the dictionary. #[inline] pub fn len(&self) -> usize { self.map.len() } /// Returns true if the dictionary contains no elements. #[inline] pub fn is_empty(&self) -> bool { self.map.is_empty() } /// Gets an iterator over the entries of the dictionary. #[inline] pub fn iter(&self) -> Iter { Iter { iter: self.map.iter(), } } /// Gets a mutable iterator over the entries of the dictionary. #[inline] pub fn iter_mut(&mut self) -> IterMut { IterMut { iter: self.map.iter_mut(), } } /// Gets an iterator over the keys of the dictionary. #[inline] pub fn keys(&self) -> Keys { Keys { iter: self.map.keys(), } } /// Gets an iterator over the values of the dictionary. #[inline] pub fn values(&self) -> Values { Values { iter: self.map.values(), } } /// Gets an iterator over mutable values of the dictionary. #[inline] pub fn values_mut(&mut self) -> ValuesMut { ValuesMut { iter: self.map.values_mut(), } } } /// Access an element of this dictionary. Panics if the given key is not present in the dictionary. /// /// ``` /// # use plist::Value; /// # /// # let val = &Value::String("".to_owned()); /// # let _ = /// match *val { /// Value::Array(ref arr) => arr[0].as_string(), /// Value::Dictionary(ref dict) => dict["type"].as_string(), /// Value::String(ref s) => Some(s.as_str()), /// _ => None, /// } /// # ; /// ``` impl<'a> ops::Index<&'a str> for Dictionary { type Output = Value; fn index(&self, index: &str) -> &Value { self.map.index(index) } } /// Mutably access an element of this dictionary. Panics if the given key is not present in the /// dictionary. /// /// ``` /// # let mut dict = plist::Dictionary::new(); /// # dict.insert("key".to_owned(), plist::Value::Boolean(false)); /// # /// dict["key"] = "value".into(); /// ``` impl<'a> ops::IndexMut<&'a str> for Dictionary { fn index_mut(&mut self, index: &str) -> &mut Value { self.map.get_mut(index).expect("no entry found for key") } } impl Debug for Dictionary { #[inline] fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { self.map.fmt(formatter) } } impl, V: Into> FromIterator<(K, V)> for Dictionary { fn from_iter>(iter: I) -> Self { Dictionary { map: iter .into_iter() .map(|(k, v)| (k.into(), v.into())) .collect(), } } } impl Extend<(String, Value)> for Dictionary { fn extend(&mut self, iter: T) where T: IntoIterator, { self.map.extend(iter); } } macro_rules! delegate_iterator { (($name:ident $($generics:tt)*) => $item:ty) => { impl $($generics)* Iterator for $name $($generics)* { type Item = $item; #[inline] fn next(&mut self) -> Option { self.iter.next() } #[inline] fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } } /*impl $($generics)* DoubleEndedIterator for $name $($generics)* { #[inline] fn next_back(&mut self) -> Option { self.iter.next_back() } }*/ impl $($generics)* ExactSizeIterator for $name $($generics)* { #[inline] fn len(&self) -> usize { self.iter.len() } } } } ////////////////////////////////////////////////////////////////////////////// /// A view into a single entry in a dictionary, which may either be vacant or occupied. /// This enum is constructed from the [`entry`] method on [`Dictionary`]. /// /// [`entry`]: struct.Dictionary.html#method.entry /// [`Dictionary`]: struct.Dictionary.html #[cfg(any( test, feature = "enable_unstable_features_that_may_break_with_minor_version_bumps" ))] pub enum Entry<'a> { /// A vacant Entry. Vacant(VacantEntry<'a>), /// An occupied Entry. Occupied(OccupiedEntry<'a>), } /// A vacant Entry. It is part of the [`Entry`] enum. /// /// [`Entry`]: enum.Entry.html #[cfg(any( test, feature = "enable_unstable_features_that_may_break_with_minor_version_bumps" ))] pub struct VacantEntry<'a> { vacant: map::VacantEntry<'a, String, Value>, } /// An occupied Entry. It is part of the [`Entry`] enum. /// /// [`Entry`]: enum.Entry.html #[cfg(any( test, feature = "enable_unstable_features_that_may_break_with_minor_version_bumps" ))] pub struct OccupiedEntry<'a> { occupied: map::OccupiedEntry<'a, String, Value>, } #[cfg(any( test, feature = "enable_unstable_features_that_may_break_with_minor_version_bumps" ))] impl<'a> Entry<'a> { /// Returns a reference to this entry's key. /// /// # Examples /// /// ``` /// let mut dict = plist::Dictionary::new(); /// assert_eq!(dict.entry("serde").key(), &"serde"); /// ``` pub fn key(&self) -> &String { match *self { Entry::Vacant(ref e) => e.key(), Entry::Occupied(ref e) => e.key(), } } /// Ensures a value is in the entry by inserting the default if empty, and returns a mutable /// reference to the value in the entry. /// /// # Examples /// /// ``` /// let mut dict = plist::Dictionary::new(); /// dict.entry("serde").or_insert(12.into()); /// /// assert_eq!(dict["serde"], 12.into()); /// ``` pub fn or_insert(self, default: Value) -> &'a mut Value { match self { Entry::Vacant(entry) => entry.insert(default), Entry::Occupied(entry) => entry.into_mut(), } } /// Ensures a value is in the entry by inserting the result of the default function if empty, /// and returns a mutable reference to the value in the entry. /// /// # Examples /// /// ``` /// let mut dict = plist::Dictionary::new(); /// dict.entry("serde").or_insert_with(|| "hoho".into()); /// /// assert_eq!(dict["serde"], "hoho".into()); /// ``` pub fn or_insert_with(self, default: F) -> &'a mut Value where F: FnOnce() -> Value, { match self { Entry::Vacant(entry) => entry.insert(default()), Entry::Occupied(entry) => entry.into_mut(), } } } #[cfg(any( test, feature = "enable_unstable_features_that_may_break_with_minor_version_bumps" ))] impl<'a> VacantEntry<'a> { /// Gets a reference to the key that would be used when inserting a value through the /// VacantEntry. /// /// # Examples /// /// ``` /// use plist::dictionary::Entry; /// /// let mut dict = plist::Dictionary::new(); /// /// match dict.entry("serde") { /// Entry::Vacant(vacant) => assert_eq!(vacant.key(), &"serde"), /// Entry::Occupied(_) => unimplemented!(), /// } /// ``` #[inline] pub fn key(&self) -> &String { self.vacant.key() } /// Sets the value of the entry with the VacantEntry's key, and returns a mutable reference /// to it. /// /// # Examples /// /// ``` /// use plist::dictionary::Entry; /// /// let mut dict = plist::Dictionary::new(); /// /// match dict.entry("serde") { /// Entry::Vacant(vacant) => vacant.insert("hoho".into()), /// Entry::Occupied(_) => unimplemented!(), /// }; /// ``` #[inline] pub fn insert(self, value: Value) -> &'a mut Value { self.vacant.insert(value) } } #[cfg(any( test, feature = "enable_unstable_features_that_may_break_with_minor_version_bumps" ))] impl<'a> OccupiedEntry<'a> { /// Gets a reference to the key in the entry. /// /// # Examples /// /// ``` /// use plist::dictionary::Entry; /// /// let mut dict = plist::Dictionary::new(); /// dict.insert("serde".to_owned(), 12.into()); /// /// match dict.entry("serde") { /// Entry::Occupied(occupied) => assert_eq!(occupied.key(), &"serde"), /// Entry::Vacant(_) => unimplemented!(), /// } /// ``` #[inline] pub fn key(&self) -> &String { self.occupied.key() } /// Gets a reference to the value in the entry. /// /// # Examples /// /// ``` /// use plist::Value; /// use plist::dictionary::Entry; /// /// let mut dict = plist::Dictionary::new(); /// dict.insert("serde".to_owned(), 12.into()); /// /// match dict.entry("serde") { /// Entry::Occupied(occupied) => assert_eq!(occupied.get(), &Value::from(12)), /// Entry::Vacant(_) => unimplemented!(), /// } /// ``` #[inline] pub fn get(&self) -> &Value { self.occupied.get() } /// Gets a mutable reference to the value in the entry. /// /// # Examples /// /// ``` /// use plist::Value; /// use plist::dictionary::Entry; /// /// let mut dict = plist::Dictionary::new(); /// dict.insert("serde".to_owned(), Value::Array(vec![1.into(), 2.into(), 3.into()])); /// /// match dict.entry("serde") { /// Entry::Occupied(mut occupied) => { /// occupied.get_mut().as_array_mut().unwrap().push(4.into()); /// } /// Entry::Vacant(_) => unimplemented!(), /// } /// /// assert_eq!(dict["serde"].as_array().unwrap().len(), 4); /// ``` #[inline] pub fn get_mut(&mut self) -> &mut Value { self.occupied.get_mut() } /// Converts the entry into a mutable reference to its value. /// /// # Examples /// /// ``` /// use plist::Value; /// use plist::dictionary::Entry; /// /// let mut dict = plist::Dictionary::new(); /// dict.insert("serde".to_owned(), Value::Array(vec![1.into(), 2.into(), 3.into()])); /// /// match dict.entry("serde") { /// Entry::Occupied(mut occupied) => { /// occupied.into_mut().as_array_mut().unwrap().push(4.into()); /// } /// Entry::Vacant(_) => unimplemented!(), /// } /// /// assert_eq!(dict["serde"].as_array().unwrap().len(), 4); /// ``` #[inline] pub fn into_mut(self) -> &'a mut Value { self.occupied.into_mut() } /// Sets the value of the entry with the `OccupiedEntry`'s key, and returns /// the entry's old value. /// /// # Examples /// /// ``` /// use plist::Value; /// use plist::dictionary::Entry; /// /// let mut dict = plist::Dictionary::new(); /// dict.insert("serde".to_owned(), 12.into()); /// /// match dict.entry("serde") { /// Entry::Occupied(mut occupied) => { /// assert_eq!(occupied.insert(13.into()), 12.into()); /// assert_eq!(occupied.get(), &Value::from(13)); /// } /// Entry::Vacant(_) => unimplemented!(), /// } /// ``` #[inline] pub fn insert(&mut self, value: Value) -> Value { self.occupied.insert(value) } /// Takes the value of the entry out of the dictionary, and returns it. /// /// # Examples /// /// ``` /// use plist::dictionary::Entry; /// /// let mut dict = plist::Dictionary::new(); /// dict.insert("serde".to_owned(), 12.into()); /// /// match dict.entry("serde") { /// Entry::Occupied(occupied) => assert_eq!(occupied.remove(), 12.into()), /// Entry::Vacant(_) => unimplemented!(), /// } /// ``` #[inline] pub fn remove(self) -> Value { self.occupied.swap_remove() } } ////////////////////////////////////////////////////////////////////////////// impl<'a> IntoIterator for &'a Dictionary { type Item = (&'a String, &'a Value); type IntoIter = Iter<'a>; #[inline] fn into_iter(self) -> Self::IntoIter { Iter { iter: self.map.iter(), } } } /// An iterator over a plist::Dictionary's entries. pub struct Iter<'a> { iter: IterImpl<'a>, } type IterImpl<'a> = map::Iter<'a, String, Value>; delegate_iterator!((Iter<'a>) => (&'a String, &'a Value)); ////////////////////////////////////////////////////////////////////////////// impl<'a> IntoIterator for &'a mut Dictionary { type Item = (&'a String, &'a mut Value); type IntoIter = IterMut<'a>; #[inline] fn into_iter(self) -> Self::IntoIter { IterMut { iter: self.map.iter_mut(), } } } /// A mutable iterator over a plist::Dictionary's entries. pub struct IterMut<'a> { iter: map::IterMut<'a, String, Value>, } delegate_iterator!((IterMut<'a>) => (&'a String, &'a mut Value)); ////////////////////////////////////////////////////////////////////////////// impl IntoIterator for Dictionary { type Item = (String, Value); type IntoIter = IntoIter; #[inline] fn into_iter(self) -> Self::IntoIter { IntoIter { iter: self.map.into_iter(), } } } /// An owning iterator over a plist::Dictionary's entries. pub struct IntoIter { iter: map::IntoIter, } delegate_iterator!((IntoIter) => (String, Value)); ////////////////////////////////////////////////////////////////////////////// /// An iterator over a plist::Dictionary's keys. pub struct Keys<'a> { iter: map::Keys<'a, String, Value>, } delegate_iterator!((Keys<'a>) => &'a String); ////////////////////////////////////////////////////////////////////////////// /// An iterator over a plist::Dictionary's values. pub struct Values<'a> { iter: map::Values<'a, String, Value>, } delegate_iterator!((Values<'a>) => &'a Value); ////////////////////////////////////////////////////////////////////////////// /// A mutable iterator over a plist::Dictionary's values. pub struct ValuesMut<'a> { iter: map::ValuesMut<'a, String, Value>, } delegate_iterator!((ValuesMut<'a>) => &'a mut Value); #[cfg(feature = "serde")] pub mod serde_impls { use serde::{de, ser}; use std::fmt; use crate::Dictionary; impl ser::Serialize for Dictionary { #[inline] fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { use serde::ser::SerializeMap; let mut map = serializer.serialize_map(Some(self.len()))?; for (k, v) in self { map.serialize_key(k)?; map.serialize_value(v)?; } map.end() } } impl<'de> de::Deserialize<'de> for Dictionary { #[inline] fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = Dictionary; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a map") } #[inline] fn visit_unit(self) -> Result where E: de::Error, { Ok(Dictionary::new()) } #[inline] fn visit_map(self, mut visitor: V) -> Result where V: de::MapAccess<'de>, { let mut values = Dictionary::new(); while let Some((key, value)) = visitor.next_entry()? { values.insert(key, value); } Ok(values) } } deserializer.deserialize_map(Visitor) } } } #[cfg(test)] mod tests { use super::Dictionary; #[test] fn from_hash_map_to_dict() { let dict: Dictionary = [ ("Doge", "Shiba Inu"), ("Cheems", "Shiba Inu"), ("Walter", "Bull Terrier"), ("Perro", "Golden Retriever"), ] .into_iter() .collect(); assert_eq!( dict.get("Doge").and_then(|v| v.as_string()), Some("Shiba Inu") ); assert_eq!( dict.get("Cheems").and_then(|v| v.as_string()), Some("Shiba Inu") ); assert_eq!( dict.get("Walter").and_then(|v| v.as_string()), Some("Bull Terrier") ); assert_eq!( dict.get("Perro").and_then(|v| v.as_string()), Some("Golden Retriever") ); } } plist-1.7.0/src/error.rs000064400000000000000000000160441046102023000132570ustar 00000000000000use std::{error, fmt, io}; #[cfg(feature = "serde")] use crate::stream::Event; use crate::{InvalidXmlDate, Value}; /// This type represents all possible errors that can occur when working with plist data. #[derive(Debug)] pub struct Error { inner: Box, } #[derive(Debug)] pub(crate) struct ErrorImpl { kind: ErrorKind, file_position: Option, } #[derive(Debug)] pub(crate) enum ErrorKind { UnexpectedEof, UnexpectedEndOfEventStream, UnexpectedEventType { // Used by the `Debug` implementation. #[allow(dead_code)] expected: EventKind, #[allow(dead_code)] found: EventKind, }, ExpectedEndOfEventStream { // Used by the `Debug` implementation. #[allow(dead_code)] found: EventKind, }, // Ascii format-specific errors UnclosedString, IncompleteComment, InvalidUtf8AsciiStream, InvalidOctalString, // Xml format-specific errors UnclosedXmlElement, UnexpectedXmlCharactersExpectedElement, UnexpectedXmlOpeningTag, UnknownXmlElement, InvalidXmlSyntax, InvalidXmlUtf8, InvalidDataString, InvalidDateString, InvalidIntegerString, InvalidRealString, UidNotSupportedInXmlPlist, // Binary format-specific errors ObjectTooLarge, InvalidMagic, InvalidTrailerObjectOffsetSize, // the size of byte offsets to objects in the object table InvalidTrailerObjectReferenceSize, // the size of indices into the object table InvalidObjectLength, ObjectReferenceTooLarge, ObjectOffsetTooLarge, RecursiveObject, NullObjectUnimplemented, FillObjectUnimplemented, IntegerOutOfRange, InfiniteOrNanDate, InvalidUtf8String, InvalidUtf16String, UnknownObjectType( // Used by the `Debug` implementation. #[allow(dead_code)] u8, ), Io(io::Error), #[cfg(feature = "serde")] Serde( // Used by the `Debug` implementation. #[allow(dead_code)] String, ), } #[derive(Debug, Clone, Copy)] pub(crate) struct FilePosition(pub(crate) u64); #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub(crate) enum EventKind { StartArray, StartDictionary, EndCollection, Boolean, Data, Date, Integer, Real, String, Uid, ValueOrStartCollection, DictionaryKeyOrEndCollection, } impl Error { /// Returns true if this error was caused by a failure to read or write bytes on an IO stream. pub fn is_io(&self) -> bool { self.as_io().is_some() } /// Returns true if this error was caused by prematurely reaching the end of the input data. pub fn is_eof(&self) -> bool { matches!(self.inner.kind, ErrorKind::UnexpectedEof) } /// Returns the underlying error if it was caused by a failure to read or write bytes on an IO /// stream. pub fn as_io(&self) -> Option<&io::Error> { if let ErrorKind::Io(err) = &self.inner.kind { Some(err) } else { None } } /// Returns the underlying error if it was caused by a failure to read or write bytes on an IO /// stream or `self` if it was not. pub fn into_io(self) -> Result { if let ErrorKind::Io(err) = self.inner.kind { Ok(err) } else { Err(self) } } } impl error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self.inner.kind { ErrorKind::Io(err) => Some(err), _ => None, } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(position) = &self.inner.file_position { write!(f, "{:?} ({})", &self.inner.kind, position) } else { fmt::Debug::fmt(&self.inner.kind, f) } } } impl fmt::Display for FilePosition { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "offset {}", self.0) } } impl From for Error { fn from(error: InvalidXmlDate) -> Self { ErrorKind::from(error).without_position() } } impl ErrorKind { pub fn with_byte_offset(self, offset: u64) -> Error { self.with_position(FilePosition(offset)) } pub fn with_position(self, pos: FilePosition) -> Error { Error { inner: Box::new(ErrorImpl { kind: self, file_position: Some(pos), }), } } pub fn without_position(self) -> Error { Error { inner: Box::new(ErrorImpl { kind: self, file_position: None, }), } } } impl From for ErrorKind { fn from(_: InvalidXmlDate) -> Self { ErrorKind::InvalidDateString } } impl EventKind { #[cfg(feature = "serde")] pub fn of_event(event: &Event) -> EventKind { match event { Event::StartArray(_) => EventKind::StartArray, Event::StartDictionary(_) => EventKind::StartDictionary, Event::EndCollection => EventKind::EndCollection, Event::Boolean(_) => EventKind::Boolean, Event::Data(_) => EventKind::Data, Event::Date(_) => EventKind::Date, Event::Integer(_) => EventKind::Integer, Event::Real(_) => EventKind::Real, Event::String(_) => EventKind::String, Event::Uid(_) => EventKind::Uid, } } pub fn of_value(event: &Value) -> EventKind { match event { Value::Array(_) => EventKind::StartArray, Value::Dictionary(_) => EventKind::StartDictionary, Value::Boolean(_) => EventKind::Boolean, Value::Data(_) => EventKind::Data, Value::Date(_) => EventKind::Date, Value::Integer(_) => EventKind::Integer, Value::Real(_) => EventKind::Real, Value::String(_) => EventKind::String, Value::Uid(_) => EventKind::Uid, } } } impl fmt::Display for EventKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { EventKind::StartArray => "StartArray", EventKind::StartDictionary => "StartDictionary", EventKind::EndCollection => "EndCollection", EventKind::Boolean => "Boolean", EventKind::Data => "Data", EventKind::Date => "Date", EventKind::Integer => "Integer", EventKind::Real => "Real", EventKind::String => "String", EventKind::Uid => "Uid", EventKind::ValueOrStartCollection => "value or start collection", EventKind::DictionaryKeyOrEndCollection => "dictionary key or end collection", } .fmt(f) } } pub(crate) fn from_io_without_position(err: io::Error) -> Error { ErrorKind::Io(err).without_position() } #[cfg(feature = "serde")] pub(crate) fn unexpected_event_type(expected: EventKind, found: &Event) -> Error { let found = EventKind::of_event(found); ErrorKind::UnexpectedEventType { expected, found }.without_position() } plist-1.7.0/src/integer.rs000064400000000000000000000116321046102023000135610ustar 00000000000000use std::{fmt, num::ParseIntError}; /// An integer that can be represented by either an `i64` or a `u64`. #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct Integer { value: i128, } impl Integer { /// Returns the value as an `i64` if it can be represented by that type. pub fn as_signed(self) -> Option { if self.value >= i128::from(i64::min_value()) && self.value <= i128::from(i64::max_value()) { Some(self.value as i64) } else { None } } /// Returns the value as a `u64` if it can be represented by that type. pub fn as_unsigned(self) -> Option { if self.value >= 0 && self.value <= i128::from(u64::max_value()) { Some(self.value as u64) } else { None } } pub(crate) fn from_str(s: &str) -> Result { if s.starts_with("0x") { // NetBSD dialect adds the `0x` numeric objects, // which are always unsigned. // See the `PROP_NUMBER(3)` man page let s = s.trim_start_matches("0x"); u64::from_str_radix(s, 16).map(Into::into) } else { // Match Apple's implementation in CFPropertyList.h - always try to parse as an i64 first. // TODO: Use IntErrorKind once stable and retry parsing on overflow only. Ok(match s.parse::() { Ok(v) => v.into(), Err(_) => s.parse::()?.into(), }) } } } impl fmt::Debug for Integer { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.value.fmt(f) } } impl fmt::Display for Integer { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.value.fmt(f) } } impl From for Integer { fn from(value: i64) -> Integer { Integer { value: value.into(), } } } impl From for Integer { fn from(value: i32) -> Integer { Integer { value: value.into(), } } } impl From for Integer { fn from(value: i16) -> Integer { Integer { value: value.into(), } } } impl From for Integer { fn from(value: i8) -> Integer { Integer { value: value.into(), } } } impl From for Integer { fn from(value: u64) -> Integer { Integer { value: value.into(), } } } impl From for Integer { fn from(value: u32) -> Integer { Integer { value: value.into(), } } } impl From for Integer { fn from(value: u16) -> Integer { Integer { value: value.into(), } } } impl From for Integer { fn from(value: u8) -> Integer { Integer { value: value.into(), } } } #[cfg(feature = "serde")] pub mod serde_impls { use serde::{ de::{Deserialize, Deserializer, Error, Visitor}, ser::{Serialize, Serializer}, }; use std::fmt; use crate::Integer; impl Serialize for Integer { fn serialize(&self, serializer: S) -> Result where S: Serializer, { if let Some(v) = self.as_unsigned() { serializer.serialize_u64(v) } else if let Some(v) = self.as_signed() { serializer.serialize_i64(v) } else { unreachable!(); } } } struct IntegerVisitor; impl<'de> Visitor<'de> for IntegerVisitor { type Value = Integer; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a plist integer") } fn visit_i64(self, v: i64) -> Result where E: Error, { Ok(Integer::from(v)) } fn visit_u64(self, v: u64) -> Result where E: Error, { Ok(Integer::from(v)) } } impl<'de> Deserialize<'de> for Integer { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_any(IntegerVisitor) } } } #[cfg(test)] mod tests { use super::Integer; #[test] fn from_str_limits() { assert_eq!(Integer::from_str("-1"), Ok((-1).into())); assert_eq!(Integer::from_str("0"), Ok(0.into())); assert_eq!(Integer::from_str("1"), Ok(1.into())); assert_eq!( Integer::from_str("-9223372036854775808"), Ok((-9223372036854775808i64).into()) ); assert!(Integer::from_str("-9223372036854775809").is_err()); assert_eq!( Integer::from_str("18446744073709551615"), Ok(18446744073709551615u64.into()) ); assert!(Integer::from_str("18446744073709551616").is_err()); } } plist-1.7.0/src/lib.rs000064400000000000000000000063421046102023000126740ustar 00000000000000//! # Plist //! //! A rusty plist parser. //! //! ## Usage //! //! Put this in your `Cargo.toml`: //! //! ```toml //! [dependencies] //! plist = "1" //! ``` //! //! And put this in your crate root: //! //! ```rust //! extern crate plist; //! ``` //! //! ## Examples //! //! ### Using `serde` //! //! ```rust //! extern crate plist; //! # #[cfg(feature = "serde")] //! #[macro_use] //! extern crate serde_derive; //! //! # #[cfg(feature = "serde")] //! # fn main() { //! #[derive(Deserialize)] //! #[serde(rename_all = "PascalCase")] //! struct Book { //! title: String, //! author: String, //! excerpt: String, //! copies_sold: u64, //! } //! //! let book: Book = plist::from_file("tests/data/book.plist") //! .expect("failed to read book.plist"); //! //! assert_eq!(book.title, "Great Expectations"); //! # } //! # //! # #[cfg(not(feature = "serde"))] //! # fn main() {} //! ``` //! //! ### Using `Value` //! //! ```rust //! use plist::Value; //! //! let book = Value::from_file("tests/data/book.plist") //! .expect("failed to read book.plist"); //! //! let title = book //! .as_dictionary() //! .and_then(|dict| dict.get("Title")) //! .and_then(|title| title.as_string()); //! //! assert_eq!(title, Some("Great Expectations")); //! ``` //! //! ## Unstable Features //! //! Many features from previous versions are now hidden behind the //! `enable_unstable_features_that_may_break_with_minor_version_bumps` feature. These will break in //! minor version releases after the 1.0 release. If you really really must use them you should //! specify a tilde requirement e.g. `plist = "~1.0.3"` in you `Cargo.toml` so that the plist crate //! is not automatically updated to version 1.1. #![deny(warnings)] // Treat all warnings as errors #![deny(rustdoc::broken_intra_doc_links)] pub mod dictionary; #[cfg(feature = "enable_unstable_features_that_may_break_with_minor_version_bumps")] pub mod stream; #[cfg(not(feature = "enable_unstable_features_that_may_break_with_minor_version_bumps"))] mod stream; #[cfg(feature = "serde")] mod data; mod date; mod error; mod integer; mod uid; mod value; #[cfg(feature = "serde")] pub use data::Data; pub use date::{Date, InvalidXmlDate}; pub use dictionary::Dictionary; pub use error::Error; pub use integer::Integer; pub use stream::XmlWriteOptions; pub use uid::Uid; pub use value::Value; // Optional serde module #[cfg(feature = "serde")] #[macro_use] extern crate serde; #[cfg(feature = "serde")] mod de; #[cfg(feature = "serde")] mod ser; #[cfg(all( feature = "serde", any( test, feature = "enable_unstable_features_that_may_break_with_minor_version_bumps" ) ))] pub use self::{de::Deserializer, ser::Serializer}; #[cfg(feature = "serde")] pub use self::{ de::{from_bytes, from_file, from_reader, from_reader_ascii, from_reader_xml, from_value}, ser::{ to_file_binary, to_file_xml, to_value, to_writer_binary, to_writer_xml, to_writer_xml_with_options, }, }; #[cfg(all(test, feature = "serde"))] #[macro_use] extern crate serde_derive; #[cfg(all(test, feature = "serde"))] mod serde_tests; fn u64_to_usize(len_u64: u64) -> Option { let len = len_u64 as usize; if len as u64 != len_u64 { return None; // Too long } Some(len) } plist-1.7.0/src/ser.rs000064400000000000000000000566431046102023000127300ustar 00000000000000use serde::ser; use std::{ borrow::Cow, fmt::Display, fs::File, io::{BufWriter, Write}, mem, path::Path, }; use crate::{ date::serde_impls::DATE_NEWTYPE_STRUCT_NAME, error::{self, Error, ErrorKind}, stream::{self, Writer}, uid::serde_impls::UID_NEWTYPE_STRUCT_NAME, Date, Integer, Uid, Value, XmlWriteOptions, }; #[doc(hidden)] impl ser::Error for Error { fn custom(msg: T) -> Self { ErrorKind::Serde(msg.to_string()).without_position() } } enum OptionMode { Root, StructField(&'static str), StructFieldNameWritten, Explicit, } /// A structure that serializes Rust values plist event streams. pub struct Serializer { writer: W, option_mode: OptionMode, } impl Serializer { pub fn new(writer: W) -> Serializer { Serializer { writer, option_mode: OptionMode::Root, } } pub fn into_inner(self) -> W { self.writer } fn serialize_with_option_mode( &mut self, option_mode: OptionMode, value: &T, ) -> Result<(), Error> { let prev_option_mode = mem::replace(&mut self.option_mode, option_mode); let result = value.serialize(&mut *self); self.option_mode = prev_option_mode; result } fn maybe_write_pending_struct_field_name(&mut self) -> Result<(), Error> { if let OptionMode::StructField(field_name) = self.option_mode { self.option_mode = OptionMode::StructFieldNameWritten; self.writer.write_string(Cow::Borrowed(field_name))?; } Ok(()) } fn write_start_array(&mut self, len: Option) -> Result<(), Error> { self.maybe_write_pending_struct_field_name()?; self.writer.write_start_array(len) } fn write_start_dictionary(&mut self, len: Option) -> Result<(), Error> { self.maybe_write_pending_struct_field_name()?; self.writer.write_start_dictionary(len) } fn write_end_collection(&mut self) -> Result<(), Error> { self.maybe_write_pending_struct_field_name()?; self.writer.write_end_collection() } fn write_boolean(&mut self, value: bool) -> Result<(), Error> { self.maybe_write_pending_struct_field_name()?; self.writer.write_boolean(value) } fn write_data(&mut self, value: Cow<[u8]>) -> Result<(), Error> { self.maybe_write_pending_struct_field_name()?; self.writer.write_data(value) } fn write_date(&mut self, value: Date) -> Result<(), Error> { self.maybe_write_pending_struct_field_name()?; self.writer.write_date(value) } fn write_integer(&mut self, value: Integer) -> Result<(), Error> { self.maybe_write_pending_struct_field_name()?; self.writer.write_integer(value) } fn write_real(&mut self, value: f64) -> Result<(), Error> { self.maybe_write_pending_struct_field_name()?; self.writer.write_real(value) } fn write_string<'a, T: Into>>(&mut self, value: T) -> Result<(), Error> { self.maybe_write_pending_struct_field_name()?; self.writer.write_string(value.into()) } fn write_uid(&mut self, value: Uid) -> Result<(), Error> { self.maybe_write_pending_struct_field_name()?; self.writer.write_uid(value) } } impl<'a, W: Writer> ser::Serializer for &'a mut Serializer { type Ok = (); type Error = Error; type SerializeSeq = Compound<'a, W>; type SerializeTuple = Compound<'a, W>; type SerializeTupleStruct = Compound<'a, W>; type SerializeTupleVariant = Compound<'a, W>; type SerializeMap = Compound<'a, W>; type SerializeStruct = Compound<'a, W>; type SerializeStructVariant = Compound<'a, W>; fn serialize_bool(self, v: bool) -> Result<(), Self::Error> { self.write_boolean(v) } fn serialize_i8(self, v: i8) -> Result<(), Error> { self.serialize_i64(v.into()) } fn serialize_i16(self, v: i16) -> Result<(), Error> { self.serialize_i64(v.into()) } fn serialize_i32(self, v: i32) -> Result<(), Error> { self.serialize_i64(v.into()) } fn serialize_i64(self, v: i64) -> Result<(), Self::Error> { self.write_integer(v.into()) } fn serialize_u8(self, v: u8) -> Result<(), Error> { self.serialize_u64(v.into()) } fn serialize_u16(self, v: u16) -> Result<(), Error> { self.serialize_u64(v.into()) } fn serialize_u32(self, v: u32) -> Result<(), Error> { self.serialize_u64(v.into()) } fn serialize_u64(self, v: u64) -> Result<(), Self::Error> { self.write_integer(v.into()) } fn serialize_f32(self, v: f32) -> Result<(), Error> { self.serialize_f64(v.into()) } fn serialize_f64(self, v: f64) -> Result<(), Error> { self.write_real(v) } fn serialize_char(self, v: char) -> Result<(), Self::Error> { let mut buf = [0; 4]; let v = v.encode_utf8(&mut buf); self.write_string(&*v) } fn serialize_str(self, v: &str) -> Result<(), Error> { self.write_string(v) } fn serialize_bytes(self, v: &[u8]) -> Result<(), Error> { self.write_data(Cow::Borrowed(v)) } fn serialize_none(self) -> Result<(), Error> { match self.option_mode { OptionMode::Root | OptionMode::StructField(_) => (), OptionMode::StructFieldNameWritten => unreachable!(), OptionMode::Explicit => { self.write_start_dictionary(Some(1))?; self.write_string("None")?; self.serialize_unit()?; self.write_end_collection()?; } } Ok(()) } fn serialize_some(self, value: &T) -> Result<(), Error> { match self.option_mode { OptionMode::Root => self.serialize_with_option_mode(OptionMode::Explicit, value)?, OptionMode::StructField(field_name) => { self.option_mode = OptionMode::StructFieldNameWritten; self.write_string(field_name)?; self.serialize_with_option_mode(OptionMode::Explicit, value)?; } OptionMode::StructFieldNameWritten => unreachable!(), OptionMode::Explicit => { self.write_start_dictionary(Some(1))?; self.write_string("Some")?; value.serialize(&mut *self)?; self.write_end_collection()?; } } Ok(()) } fn serialize_unit(self) -> Result<(), Error> { self.write_string("") } fn serialize_unit_struct(self, _name: &'static str) -> Result<(), Error> { self.serialize_unit() } fn serialize_unit_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, ) -> Result<(), Error> { // `plist` since v1.1 serialises unit enum variants as plain strings. self.write_string(variant) } fn serialize_newtype_struct( self, name: &'static str, value: &T, ) -> Result<(), Error> { match name { DATE_NEWTYPE_STRUCT_NAME => value.serialize(DateSerializer { ser: &mut *self }), UID_NEWTYPE_STRUCT_NAME => value.serialize(UidSerializer { ser: &mut *self }), _ => value.serialize(self), } } fn serialize_newtype_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, value: &T, ) -> Result<(), Error> { self.write_start_dictionary(Some(1))?; self.write_string(variant)?; value.serialize(&mut *self)?; self.write_end_collection() } fn serialize_seq(self, len: Option) -> Result { let len = len.map(|len| len as u64); self.write_start_array(len)?; Ok(Compound { ser: self }) } fn serialize_tuple(self, len: usize) -> Result { self.serialize_seq(Some(len)) } fn serialize_tuple_struct( self, _name: &'static str, len: usize, ) -> Result { self.serialize_tuple(len) } fn serialize_tuple_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, len: usize, ) -> Result { self.write_start_dictionary(Some(1))?; self.write_string(variant)?; self.serialize_tuple(len) } fn serialize_map(self, len: Option) -> Result { let len = len.map(|len| len as u64); self.write_start_dictionary(len)?; Ok(Compound { ser: self }) } fn serialize_struct( self, _name: &'static str, _len: usize, ) -> Result { // The number of struct fields is not known as fields with None values are ignored. self.serialize_map(None) } fn serialize_struct_variant( self, name: &'static str, _variant_index: u32, variant: &'static str, len: usize, ) -> Result { self.write_start_dictionary(Some(1))?; self.write_string(variant)?; self.serialize_struct(name, len) } } struct DateSerializer<'a, W: 'a + Writer> { ser: &'a mut Serializer, } impl<'a, W: Writer> DateSerializer<'a, W> { fn expecting_date_error(&self) -> Error { ser::Error::custom("plist date string expected") } } impl<'a, W: Writer> ser::Serializer for DateSerializer<'a, W> { type Ok = (); type Error = Error; type SerializeSeq = ser::Impossible<(), Error>; type SerializeTuple = ser::Impossible<(), Error>; type SerializeTupleStruct = ser::Impossible<(), Error>; type SerializeTupleVariant = ser::Impossible<(), Error>; type SerializeMap = ser::Impossible<(), Error>; type SerializeStruct = ser::Impossible<(), Error>; type SerializeStructVariant = ser::Impossible<(), Error>; fn serialize_bool(self, _: bool) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_i8(self, _: i8) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_i16(self, _: i16) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_i32(self, _: i32) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_i64(self, _: i64) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_u8(self, _: u8) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_u16(self, _: u16) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_u32(self, _: u32) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_u64(self, _: u64) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_f32(self, _: f32) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_f64(self, _: f64) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_char(self, _: char) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_str(self, v: &str) -> Result<(), Error> { let date = Date::from_xml_format(v).map_err(|_| self.expecting_date_error())?; self.ser.write_date(date) } fn serialize_bytes(self, _: &[u8]) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_none(self) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_some(self, _: &T) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_unit(self) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_unit_struct(self, _: &'static str) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_unit_variant(self, _: &'static str, _: u32, _: &'static str) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_newtype_struct( self, _: &'static str, _: &T, ) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_newtype_variant( self, _: &'static str, _: u32, _: &'static str, _: &T, ) -> Result<(), Error> { Err(self.expecting_date_error()) } fn serialize_seq(self, _: Option) -> Result { Err(self.expecting_date_error()) } fn serialize_tuple(self, _: usize) -> Result { Err(self.expecting_date_error()) } fn serialize_tuple_struct( self, _: &'static str, _: usize, ) -> Result { Err(self.expecting_date_error()) } fn serialize_tuple_variant( self, _: &'static str, _: u32, _: &'static str, _: usize, ) -> Result { Err(self.expecting_date_error()) } fn serialize_map(self, _: Option) -> Result { Err(self.expecting_date_error()) } fn serialize_struct(self, _: &'static str, _: usize) -> Result { Err(self.expecting_date_error()) } fn serialize_struct_variant( self, _: &'static str, _: u32, _: &'static str, _: usize, ) -> Result { Err(self.expecting_date_error()) } } struct UidSerializer<'a, W: 'a + Writer> { ser: &'a mut Serializer, } impl<'a, W: Writer> UidSerializer<'a, W> { fn expecting_uid_error(&self) -> Error { ser::Error::custom("plist uid expected") } } impl<'a, W: Writer> ser::Serializer for UidSerializer<'a, W> { type Ok = (); type Error = Error; type SerializeSeq = ser::Impossible<(), Error>; type SerializeTuple = ser::Impossible<(), Error>; type SerializeTupleStruct = ser::Impossible<(), Error>; type SerializeTupleVariant = ser::Impossible<(), Error>; type SerializeMap = ser::Impossible<(), Error>; type SerializeStruct = ser::Impossible<(), Error>; type SerializeStructVariant = ser::Impossible<(), Error>; fn serialize_bool(self, _: bool) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_i8(self, _: i8) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_i16(self, _: i16) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_i32(self, _: i32) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_i64(self, _: i64) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_u8(self, _: u8) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_u16(self, _: u16) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_u32(self, _: u32) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_u64(self, v: u64) -> Result<(), Error> { self.ser.write_uid(Uid::new(v)) } fn serialize_f32(self, _: f32) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_f64(self, _: f64) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_char(self, _: char) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_str(self, _: &str) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_bytes(self, _: &[u8]) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_none(self) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_some(self, _: &T) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_unit(self) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_unit_struct(self, _: &'static str) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_unit_variant(self, _: &'static str, _: u32, _: &'static str) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_newtype_struct( self, _: &'static str, _: &T, ) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_newtype_variant( self, _: &'static str, _: u32, _: &'static str, _: &T, ) -> Result<(), Error> { Err(self.expecting_uid_error()) } fn serialize_seq(self, _: Option) -> Result { Err(self.expecting_uid_error()) } fn serialize_tuple(self, _: usize) -> Result { Err(self.expecting_uid_error()) } fn serialize_tuple_struct( self, _: &'static str, _: usize, ) -> Result { Err(self.expecting_uid_error()) } fn serialize_tuple_variant( self, _: &'static str, _: u32, _: &'static str, _: usize, ) -> Result { Err(self.expecting_uid_error()) } fn serialize_map(self, _: Option) -> Result { Err(self.expecting_uid_error()) } fn serialize_struct(self, _: &'static str, _: usize) -> Result { Err(self.expecting_uid_error()) } fn serialize_struct_variant( self, _: &'static str, _: u32, _: &'static str, _: usize, ) -> Result { Err(self.expecting_uid_error()) } } #[doc(hidden)] pub struct Compound<'a, W: 'a + Writer> { ser: &'a mut Serializer, } impl<'a, W: Writer> ser::SerializeSeq for Compound<'a, W> { type Ok = (); type Error = Error; fn serialize_element(&mut self, value: &T) -> Result<(), Error> { self.ser .serialize_with_option_mode(OptionMode::Explicit, value) } fn end(self) -> Result { self.ser.write_end_collection() } } impl<'a, W: Writer> ser::SerializeTuple for Compound<'a, W> { type Ok = (); type Error = Error; fn serialize_element(&mut self, value: &T) -> Result<(), Error> { self.ser .serialize_with_option_mode(OptionMode::Explicit, value) } fn end(self) -> Result<(), Error> { self.ser.write_end_collection() } } impl<'a, W: Writer> ser::SerializeTupleStruct for Compound<'a, W> { type Ok = (); type Error = Error; fn serialize_field(&mut self, value: &T) -> Result<(), Error> { self.ser .serialize_with_option_mode(OptionMode::Explicit, value) } fn end(self) -> Result<(), Error> { self.ser.write_end_collection() } } impl<'a, W: Writer> ser::SerializeTupleVariant for Compound<'a, W> { type Ok = (); type Error = Error; fn serialize_field(&mut self, value: &T) -> Result<(), Error> { self.ser .serialize_with_option_mode(OptionMode::Explicit, value) } fn end(self) -> Result { self.ser.write_end_collection()?; self.ser.write_end_collection() } } impl<'a, W: Writer> ser::SerializeMap for Compound<'a, W> { type Ok = (); type Error = Error; fn serialize_key(&mut self, key: &T) -> Result<(), Error> { self.ser .serialize_with_option_mode(OptionMode::Explicit, key) } fn serialize_value(&mut self, value: &T) -> Result<(), Error> { self.ser .serialize_with_option_mode(OptionMode::Explicit, value) } fn end(self) -> Result { self.ser.write_end_collection() } } impl<'a, W: Writer> ser::SerializeStruct for Compound<'a, W> { type Ok = (); type Error = Error; fn serialize_field( &mut self, key: &'static str, value: &T, ) -> Result<(), Error> { // We don't want to serialize None if the Option is a struct field as this is how null // fields are represented in plists. self.ser .serialize_with_option_mode(OptionMode::StructField(key), value) } fn end(self) -> Result<(), Error> { self.ser.write_end_collection() } } impl<'a, W: Writer> ser::SerializeStructVariant for Compound<'a, W> { type Ok = (); type Error = Error; fn serialize_field( &mut self, key: &'static str, value: &T, ) -> Result<(), Error> { self.ser .serialize_with_option_mode(OptionMode::StructField(key), value) } fn end(self) -> Result<(), Error> { self.ser.write_end_collection()?; self.ser.write_end_collection() } } /// Serializes the given data structure to a file as a binary encoded plist. pub fn to_file_binary, T: ser::Serialize>(path: P, value: &T) -> Result<(), Error> { let mut file = File::create(path).map_err(error::from_io_without_position)?; to_writer_binary(BufWriter::new(&mut file), value)?; file.sync_all().map_err(error::from_io_without_position)?; Ok(()) } /// Serializes the given data structure to a file as an XML encoded plist. pub fn to_file_xml, T: ser::Serialize>(path: P, value: &T) -> Result<(), Error> { let mut file = File::create(path).map_err(error::from_io_without_position)?; to_writer_xml(BufWriter::new(&mut file), value)?; file.sync_all().map_err(error::from_io_without_position)?; Ok(()) } /// Serializes the given data structure to a byte stream as a binary encoded plist. pub fn to_writer_binary(writer: W, value: &T) -> Result<(), Error> { let writer = stream::BinaryWriter::new(writer); let mut ser = Serializer::new(writer); value.serialize(&mut ser) } /// Serializes the given data structure to a byte stream as an XML encoded plist. pub fn to_writer_xml(writer: W, value: &T) -> Result<(), Error> { to_writer_xml_with_options(writer, value, &XmlWriteOptions::default()) } /// Serializes to a byte stream as an XML encoded plist, using custom [`XmlWriteOptions`]. pub fn to_writer_xml_with_options( writer: W, value: &T, options: &XmlWriteOptions, ) -> Result<(), Error> { let writer = stream::XmlWriter::new_with_options(writer, options); let mut ser = Serializer::new(writer); value.serialize(&mut ser) } /// Converts a `T` into a [`Value`] which can represent any valid plist. pub fn to_value(value: &T) -> Result { let writer = crate::value::Builder::default(); let mut ser = Serializer::new(writer); value.serialize(&mut ser)?; ser.into_inner().finish() } plist-1.7.0/src/serde_tests.rs000064400000000000000000000665741046102023000144670ustar 00000000000000use serde::{ de::{Deserialize, DeserializeOwned}, ser::Serialize, }; use std::{borrow::Cow, collections::BTreeMap, fmt::Debug, fs::File, io::Cursor}; use crate::{ from_value, stream::{private::Sealed, Event, OwnedEvent, Writer}, to_value, Data, Date, Deserializer, Dictionary, Error, Integer, Serializer, Uid, Value, }; struct VecWriter { events: Vec, } impl VecWriter { pub fn new() -> VecWriter { VecWriter { events: Vec::new() } } pub fn into_inner(self) -> Vec { self.events } } impl Writer for VecWriter { fn write_start_array(&mut self, len: Option) -> Result<(), Error> { self.events.push(Event::StartArray(len)); Ok(()) } fn write_start_dictionary(&mut self, len: Option) -> Result<(), Error> { self.events.push(Event::StartDictionary(len)); Ok(()) } fn write_end_collection(&mut self) -> Result<(), Error> { self.events.push(Event::EndCollection); Ok(()) } fn write_boolean(&mut self, value: bool) -> Result<(), Error> { self.events.push(Event::Boolean(value)); Ok(()) } fn write_data(&mut self, value: Cow<[u8]>) -> Result<(), Error> { self.events .push(Event::Data(Cow::Owned(value.into_owned()))); Ok(()) } fn write_date(&mut self, value: Date) -> Result<(), Error> { self.events.push(Event::Date(value)); Ok(()) } fn write_integer(&mut self, value: Integer) -> Result<(), Error> { self.events.push(Event::Integer(value)); Ok(()) } fn write_real(&mut self, value: f64) -> Result<(), Error> { self.events.push(Event::Real(value)); Ok(()) } fn write_string(&mut self, value: Cow) -> Result<(), Error> { self.events .push(Event::String(Cow::Owned(value.into_owned()))); Ok(()) } fn write_uid(&mut self, value: Uid) -> Result<(), Error> { self.events.push(Event::Uid(value)); Ok(()) } } impl Sealed for VecWriter {} fn new_serializer() -> Serializer { Serializer::new(VecWriter::new()) } fn new_deserializer<'event>( events: Vec>, ) -> Deserializer<'event, Vec, Error>>> { let result_events = events.into_iter().map(Ok).collect(); Deserializer::new(result_events) } fn assert_roundtrip(obj: T, expected_events: &[Event], roundtrip_value: bool) where T: Debug + DeserializeOwned + PartialEq + Serialize, { let mut se = new_serializer(); obj.serialize(&mut se).unwrap(); let events = se.into_inner().into_inner(); let value = if roundtrip_value { to_value(&obj).expect("failed to convert object into value") } else { Value::Boolean(false) }; assert_eq!(&events[..], &expected_events[..]); if roundtrip_value { let expected_value = Value::from_events(expected_events.iter().cloned().map(Ok)) .expect("failed to convert expected events into value"); assert_eq!(value, expected_value); } let mut de = new_deserializer(events); let obj_events_roundtrip = T::deserialize(&mut de).unwrap(); assert_eq!(obj_events_roundtrip, obj); if roundtrip_value { let obj_value_roundtrip: T = from_value(&value).unwrap(); assert_eq!(obj_value_roundtrip, obj); } } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] enum Animal { Cow, Dog(DogOuter), Frog(Result, Option>), Cat { age: Integer, name: String, firmware: Option>, }, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct DogOuter { inner: Vec, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct DogInner { a: (), b: usize, c: Vec, d: Option, e: Data, } #[test] fn cow() { let cow = Animal::Cow; let comparison = &[Event::String("Cow".into())]; assert_roundtrip(cow, comparison, true); } #[test] fn dog() { let dog = Animal::Dog(DogOuter { inner: vec![DogInner { a: (), b: 12, c: vec!["a".to_string(), "b".to_string()], d: Some(Uid::new(42)), e: Data::new(vec![20, 22]), }], }); let comparison = &[ Event::StartDictionary(Some(1)), Event::String("Dog".into()), Event::StartDictionary(None), Event::String("inner".into()), Event::StartArray(Some(1)), Event::StartDictionary(None), Event::String("a".into()), Event::String("".into()), Event::String("b".into()), Event::Integer(12.into()), Event::String("c".into()), Event::StartArray(Some(2)), Event::String("a".into()), Event::String("b".into()), Event::EndCollection, Event::String("d".into()), Event::Uid(Uid::new(42)), Event::String("e".into()), Event::Data(vec![20, 22].into()), Event::EndCollection, Event::EndCollection, Event::EndCollection, Event::EndCollection, ]; assert_roundtrip(dog, comparison, true); } #[test] fn frog() { let frog = Animal::Frog( Ok("hello".to_owned()), Some(vec![1.0, 2.0, std::f64::consts::PI, 0.000000001, 1.27e31]), ); let comparison = &[ Event::StartDictionary(Some(1)), Event::String("Frog".into()), Event::StartArray(Some(2)), Event::StartDictionary(Some(1)), Event::String("Ok".into()), Event::String("hello".into()), Event::EndCollection, Event::StartDictionary(Some(1)), Event::String("Some".into()), Event::StartArray(Some(5)), Event::Real(1.0), Event::Real(2.0), Event::Real(std::f64::consts::PI), Event::Real(0.000000001), Event::Real(1.27e31), Event::EndCollection, Event::EndCollection, Event::EndCollection, Event::EndCollection, ]; assert_roundtrip(frog, comparison, true); } #[test] fn cat_with_firmware() { let cat = Animal::Cat { age: 12.into(), name: "Paws".to_owned(), firmware: Some(vec![0, 1, 2, 3, 4, 5, 6, 7, 8]), }; let comparison = &[ Event::StartDictionary(Some(1)), Event::String("Cat".into()), Event::StartDictionary(None), Event::String("age".into()), Event::Integer(12.into()), Event::String("name".into()), Event::String("Paws".into()), Event::String("firmware".into()), Event::StartArray(Some(9)), Event::Integer(0.into()), Event::Integer(1.into()), Event::Integer(2.into()), Event::Integer(3.into()), Event::Integer(4.into()), Event::Integer(5.into()), Event::Integer(6.into()), Event::Integer(7.into()), Event::Integer(8.into()), Event::EndCollection, Event::EndCollection, Event::EndCollection, ]; assert_roundtrip(cat, comparison, true); } #[test] fn cat_without_firmware() { let cat = Animal::Cat { age: Integer::from(-12), name: "Paws".to_owned(), firmware: None, }; let comparison = &[ Event::StartDictionary(Some(1)), Event::String("Cat".into()), Event::StartDictionary(None), Event::String("age".into()), Event::Integer(Integer::from(-12)), Event::String("name".into()), Event::String("Paws".into()), Event::EndCollection, Event::EndCollection, ]; assert_roundtrip(cat, comparison, true); } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct NewtypeStruct(NewtypeInner); #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct NewtypeInner(u8, u8, u8); #[test] fn newtype_struct() { let newtype = NewtypeStruct(NewtypeInner(34, 32, 13)); let comparison = &[ Event::StartArray(Some(3)), Event::Integer(34.into()), Event::Integer(32.into()), Event::Integer(13.into()), Event::EndCollection, ]; assert_roundtrip(newtype, comparison, true); } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct TypeWithOptions { a: Option, b: Option>, c: Option>, } #[test] fn type_with_options() { let inner = TypeWithOptions { a: None, b: Some(Some(12)), c: None, }; let obj = TypeWithOptions { a: Some("hello".to_owned()), b: Some(None), c: Some(Box::new(inner)), }; let comparison = &[ Event::StartDictionary(None), Event::String("a".into()), Event::String("hello".into()), Event::String("b".into()), Event::StartDictionary(Some(1)), Event::String("None".into()), Event::String("".into()), Event::EndCollection, Event::String("c".into()), Event::StartDictionary(None), Event::String("b".into()), Event::StartDictionary(Some(1)), Event::String("Some".into()), Event::Integer(12.into()), Event::EndCollection, Event::EndCollection, Event::EndCollection, ]; assert_roundtrip(obj, comparison, true); } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct TypeWithDate { a: Option, b: Option, } #[test] fn type_with_date() { let date = Date::from_xml_format("1920-01-01T00:10:00Z").unwrap(); let obj = TypeWithDate { a: Some(28), b: Some(date), }; let comparison = &[ Event::StartDictionary(None), Event::String("a".into()), Event::Integer(28.into()), Event::String("b".into()), Event::Date(date), Event::EndCollection, ]; assert_roundtrip(obj, comparison, true); } #[test] fn option_some() { let obj = Some(12); let comparison = &[Event::Integer(12.into())]; assert_roundtrip(obj, comparison, true); } #[test] fn option_none() { let obj: Option = None; let comparison = &[]; // Written as nothing so can't be represented by a `Value`. assert_roundtrip(obj, comparison, false); } #[test] fn option_some_some() { let obj = Some(Some(12)); let comparison = &[ Event::StartDictionary(Some(1)), Event::String("Some".into()), Event::Integer(12.into()), Event::EndCollection, ]; assert_roundtrip(obj, comparison, true); } #[test] fn option_some_none() { let obj: Option> = Some(None); let comparison = &[ Event::StartDictionary(Some(1)), Event::String("None".into()), Event::String("".into()), Event::EndCollection, ]; assert_roundtrip(obj, comparison, true); } #[test] fn option_dictionary_values() { let mut obj = BTreeMap::new(); obj.insert("a".to_owned(), None); obj.insert("b".to_owned(), Some(None)); obj.insert("c".to_owned(), Some(Some(144))); let comparison = &[ Event::StartDictionary(Some(3)), Event::String("a".into()), Event::StartDictionary(Some(1)), Event::String("None".into()), Event::String("".into()), Event::EndCollection, Event::String("b".into()), Event::StartDictionary(Some(1)), Event::String("Some".into()), Event::StartDictionary(Some(1)), Event::String("None".into()), Event::String("".into()), Event::EndCollection, Event::EndCollection, Event::String("c".into()), Event::StartDictionary(Some(1)), Event::String("Some".into()), Event::StartDictionary(Some(1)), Event::String("Some".into()), Event::Integer(144.into()), Event::EndCollection, Event::EndCollection, Event::EndCollection, ]; assert_roundtrip(obj, comparison, true); } #[test] fn option_dictionary_keys() { let mut obj = BTreeMap::new(); obj.insert(None, 1); obj.insert(Some(None), 2); obj.insert(Some(Some(144)), 3); let comparison = &[ Event::StartDictionary(Some(3)), Event::StartDictionary(Some(1)), Event::String("None".into()), Event::String("".into()), Event::EndCollection, Event::Integer(1.into()), Event::StartDictionary(Some(1)), Event::String("Some".into()), Event::StartDictionary(Some(1)), Event::String("None".into()), Event::String("".into()), Event::EndCollection, Event::EndCollection, Event::Integer(2.into()), Event::StartDictionary(Some(1)), Event::String("Some".into()), Event::StartDictionary(Some(1)), Event::String("Some".into()), Event::Integer(144.into()), Event::EndCollection, Event::EndCollection, Event::Integer(3.into()), Event::EndCollection, ]; // This example uses non-string dictionary keys which can only be represented by binary plists // and not by a `Value`. assert_roundtrip(obj, comparison, false); } #[test] fn option_array() { let obj = vec![None, Some(None), Some(Some(144))]; let comparison = &[ Event::StartArray(Some(3)), Event::StartDictionary(Some(1)), Event::String("None".into()), Event::String("".into()), Event::EndCollection, Event::StartDictionary(Some(1)), Event::String("Some".into()), Event::StartDictionary(Some(1)), Event::String("None".into()), Event::String("".into()), Event::EndCollection, Event::EndCollection, Event::StartDictionary(Some(1)), Event::String("Some".into()), Event::StartDictionary(Some(1)), Event::String("Some".into()), Event::Integer(144.into()), Event::EndCollection, Event::EndCollection, Event::EndCollection, ]; assert_roundtrip(obj, comparison, true); } #[test] fn enum_variant_types() { #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] enum Foo { Unit, Newtype(u32), Tuple(u32, String), Struct { v: u32, s: String }, } let expected = &[Event::String("Unit".into())]; assert_roundtrip(Foo::Unit, expected, true); let expected = &[ Event::StartDictionary(Some(1)), Event::String("Newtype".into()), Event::Integer(42.into()), Event::EndCollection, ]; assert_roundtrip(Foo::Newtype(42), expected, true); let expected = &[ Event::StartDictionary(Some(1)), Event::String("Tuple".into()), Event::StartArray(Some(2)), Event::Integer(42.into()), Event::String("bar".into()), Event::EndCollection, Event::EndCollection, ]; assert_roundtrip(Foo::Tuple(42, "bar".into()), expected, true); let expected = &[ Event::StartDictionary(Some(1)), Event::String("Struct".into()), Event::StartDictionary(None), Event::String("v".into()), Event::Integer(42.into()), Event::String("s".into()), Event::String("bar".into()), Event::EndCollection, Event::EndCollection, ]; assert_roundtrip( Foo::Struct { v: 42, s: "bar".into(), }, expected, true, ); } #[test] fn deserialise_old_enum_unit_variant_encoding() { #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] enum Foo { Bar, Baz, } // `plist` before v1.1 serialised unit enum variants as if they were newtype variants // containing an empty string. let events = &[ Event::StartDictionary(Some(1)), Event::String("Baz".into()), Event::String("".into()), Event::EndCollection, ]; let mut de = new_deserializer(events.to_vec()); let obj = Foo::deserialize(&mut de).unwrap(); assert_eq!(obj, Foo::Baz); } #[test] fn deserialize_dictionary_xml() { let reader = File::open("./tests/data/xml.plist").unwrap(); let dict: Dictionary = crate::from_reader(reader).unwrap(); check_common_plist(&dict); // xml.plist has this member, but binary.plist does not. assert_eq!( dict.get("HexademicalNumber") // sic .unwrap() .as_unsigned_integer() .unwrap(), 0xDEADBEEF ); } #[test] fn deserialize_dictionary_binary() { let reader = File::open("./tests/data/binary.plist").unwrap(); let dict: Dictionary = crate::from_reader(reader).unwrap(); check_common_plist(&dict); } // Shared checks used by the tests deserialize_dictionary_xml() and // deserialize_dictionary_binary(), which load files with different formats // but the same data elements. fn check_common_plist(dict: &Dictionary) { // Array elements let lines = dict.get("Lines").unwrap().as_array().unwrap(); assert_eq!(lines.len(), 2); assert_eq!( lines[0].as_string().unwrap(), "It is a tale told by an idiot, " ); assert_eq!( lines[1].as_string().unwrap(), "Full of sound and fury, signifying nothing." ); // Dictionary // // There is no embedded dictionary in this plist. See // deserialize_dictionary_binary_nskeyedarchiver() below for an example // of that. // Boolean elements assert!(dict.get("IsTrue").unwrap().as_boolean().unwrap()); assert!(!dict.get("IsNotFalse").unwrap().as_boolean().unwrap()); // Data let data = dict.get("Data").unwrap().as_data().unwrap(); assert_eq!(data.len(), 15); assert_eq!( data, &[0, 0, 0, 0xbe, 0, 0, 0, 0x03, 0, 0, 0, 0x1e, 0, 0, 0] ); // Date assert_eq!( dict.get("Birthdate").unwrap().as_date().unwrap(), Date::from_xml_format("1981-05-16T11:32:06Z").unwrap() ); // Real assert_eq!(dict.get("Height").unwrap().as_real().unwrap(), 1.6); // Integer assert_eq!( dict.get("BiggestNumber") .unwrap() .as_unsigned_integer() .unwrap(), 18446744073709551615 ); assert_eq!( dict.get("Death").unwrap().as_unsigned_integer().unwrap(), 1564 ); assert_eq!( dict.get("SmallestNumber") .unwrap() .as_signed_integer() .unwrap(), -9223372036854775808 ); // String assert_eq!( dict.get("Author").unwrap().as_string().unwrap(), "William Shakespeare" ); assert_eq!(dict.get("Blank").unwrap().as_string().unwrap(), ""); // Uid // // No checks for Uid value type in this test. See // deserialize_dictionary_binary_nskeyedarchiver() below for an example // of that. } #[test] fn deserialize_dictionary_binary_nskeyedarchiver() { let reader = File::open("./tests/data/binary_NSKeyedArchiver.plist").unwrap(); let dict: Dictionary = crate::from_reader(reader).unwrap(); assert_eq!( dict.get("$archiver").unwrap().as_string().unwrap(), "NSKeyedArchiver" ); let objects = dict.get("$objects").unwrap().as_array().unwrap(); assert_eq!(objects.len(), 5); assert_eq!(objects[0].as_string().unwrap(), "$null"); let objects_1 = objects[1].as_dictionary().unwrap(); assert_eq!( *objects_1.get("$class").unwrap().as_uid().unwrap(), Uid::new(4) ); assert_eq!( objects_1 .get("NSRangeCount") .unwrap() .as_unsigned_integer() .unwrap(), 42 ); assert_eq!( *objects_1.get("NSRangeData").unwrap().as_uid().unwrap(), Uid::new(2) ); let objects_2 = objects[2].as_dictionary().unwrap(); assert_eq!( *objects_2.get("$class").unwrap().as_uid().unwrap(), Uid::new(3) ); let objects_2_nsdata = objects_2.get("NS.data").unwrap().as_data().unwrap(); assert_eq!(objects_2_nsdata.len(), 103); assert_eq!(objects_2_nsdata[0], 0x03); assert_eq!(objects_2_nsdata[102], 0x01); let objects_3 = objects[3].as_dictionary().unwrap(); let objects_3_classes = objects_3.get("$classes").unwrap().as_array().unwrap(); assert_eq!(objects_3_classes.len(), 3); assert_eq!(objects_3_classes[0].as_string().unwrap(), "NSMutableData"); assert_eq!(objects_3_classes[1].as_string().unwrap(), "NSData"); assert_eq!(objects_3_classes[2].as_string().unwrap(), "NSObject"); assert_eq!( objects_3.get("$classname").unwrap().as_string().unwrap(), "NSMutableData" ); let objects_4 = objects[4].as_dictionary().unwrap(); let objects_4_classes = objects_4.get("$classes").unwrap().as_array().unwrap(); assert_eq!(objects_4_classes.len(), 3); assert_eq!( objects_4_classes[0].as_string().unwrap(), "NSMutableIndexSet" ); assert_eq!(objects_4_classes[1].as_string().unwrap(), "NSIndexSet"); assert_eq!(objects_4_classes[2].as_string().unwrap(), "NSObject"); assert_eq!( objects_4.get("$classname").unwrap().as_string().unwrap(), "NSMutableIndexSet" ); let top = dict.get("$top").unwrap().as_dictionary().unwrap(); assert_eq!( *top.get("foundItems").unwrap().as_uid().unwrap(), Uid::new(1) ); let version = dict.get("$version").unwrap().as_unsigned_integer().unwrap(); assert_eq!(version, 100000); } fn try_parse_xml(bom: bool, whitespace: bool, decl: bool, comment: bool, doctype: bool) -> bool { #[derive(Deserialize)] struct LayerinfoData { color: Option, } let mut data = Vec::new(); if bom { // The UTF-8 byte order mark data.extend(b"\xef\xbb\xbf"); } if whitespace { data.extend(b"\r\n\t "); } if decl { data.extend(br#""#); } if comment { data.extend(br#""#); } if doctype { data.extend(br#""#); } data.extend( br#" color 1,0.75,0,0.7 "#, ); if let Ok(lib_dict) = crate::from_bytes::(&data) { lib_dict.color.unwrap() == "1,0.75,0,0.7" } else { false } } #[test] fn xml_detection() { for bom in [true, false] { for whitespace in [true, false] { for decl in [true, false] { for comment in [true, false] { for doctype in [true, false] { assert!( try_parse_xml(bom, whitespace, decl, comment, doctype), "bom={bom}, whitespace={whitespace}, decl={decl}, comment={comment}, doctype={doctype}" ); } } } } } } #[test] fn dictionary_deserialize_dictionary_in_struct() { // Example from #[derive(Deserialize)] struct LayerinfoData { color: Option, lib: Option, } let lib_dict: LayerinfoData = crate::from_bytes(r#" color 1,0.75,0,0.7 lib com.typemytype.robofont.segmentType curve "#.as_bytes()).unwrap(); assert_eq!(lib_dict.color.unwrap(), "1,0.75,0,0.7"); assert_eq!( lib_dict .lib .unwrap() .get("com.typemytype.robofont.segmentType") .unwrap() .as_string() .unwrap(), "curve" ); } #[test] fn dictionary_serialize_xml() { // Dictionary to be embedded in dict, below. let mut inner_dict = Dictionary::new(); inner_dict.insert( "FirstKey".to_owned(), Value::String("FirstValue".to_owned()), ); inner_dict.insert("SecondKey".to_owned(), Value::Data(vec![10, 20, 30, 40])); inner_dict.insert("ThirdKey".to_owned(), Value::Real(1.234)); inner_dict.insert( "FourthKey".to_owned(), Value::Date(Date::from_xml_format("1981-05-16T11:32:06Z").unwrap()), ); // Top-level dictionary. let mut dict = Dictionary::new(); dict.insert( "AnArray".to_owned(), Value::Array(vec![ Value::String("Hello, world!".to_owned()), Value::Integer(Integer::from(345)), ]), ); dict.insert("ADictionary".to_owned(), Value::Dictionary(inner_dict)); dict.insert("AnInteger".to_owned(), Value::Integer(Integer::from(123))); dict.insert("ATrueBoolean".to_owned(), Value::Boolean(true)); dict.insert("AFalseBoolean".to_owned(), Value::Boolean(false)); // Serialize dictionary as an XML plist. let mut buf = Cursor::new(Vec::new()); crate::to_writer_xml(&mut buf, &dict).unwrap(); let buf = buf.into_inner(); let xml = std::str::from_utf8(&buf).unwrap(); let comparison = " \tAnArray \t \t\tHello, world! \t\t345 \t \tADictionary \t \t\tFirstKey \t\tFirstValue \t\tSecondKey \t\t \t\tChQeKA== \t\t \t\tThirdKey \t\t1.234 \t\tFourthKey \t\t1981-05-16T11:32:06Z \t \tAnInteger \t123 \tATrueBoolean \t \tAFalseBoolean \t "; assert_eq!(xml, comparison); } #[test] fn empty_array_and_dictionary_serialize_to_xml() { #[derive(Serialize, Default)] struct Empty { vec: Vec, map: BTreeMap, } // Serialize dictionary as an XML plist. let mut buf = Cursor::new(Vec::new()); crate::to_writer_xml(&mut buf, &Empty::default()).unwrap(); let buf = buf.into_inner(); let xml = std::str::from_utf8(&buf).unwrap(); let comparison = " \tvec \t \tmap \t "; assert_eq!(xml, comparison); } #[test] fn serde_yaml_to_value() { let value: Value = serde_yaml::from_str("true").unwrap(); assert_eq!(value, Value::Boolean(true)); } #[test] fn serialize_to_from_value() { let dog = Animal::Dog(DogOuter { inner: vec![DogInner { a: (), b: 12, c: vec!["a".to_string(), "b".to_string()], d: Some(Uid::new(42)), e: Data::new(vec![1, 2, 3]), }], }); let dog_value = to_value(&dog).unwrap(); assert_eq!( dog_value .as_dictionary() .unwrap() .get("Dog") .unwrap() .as_dictionary() .unwrap() .get("inner") .unwrap() .as_array() .unwrap()[0] .as_dictionary() .unwrap()["b"] .as_unsigned_integer() .unwrap(), 12 ); let dog_roundtrip: Animal = from_value(&dog_value).unwrap(); assert_eq!(dog_roundtrip, dog); } plist-1.7.0/src/stream/ascii_reader.rs000064400000000000000000000451021046102023000160300ustar 00000000000000/// Ascii property lists are used in legacy settings and only support four /// datatypes: Array, Dictionary, String and Data. /// See [Apple /// Documentation](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html) /// for more info. /// However this reader also support Integers as first class datatype. /// This reader will accept certain ill-formed ascii plist without complaining. /// It does not check the integrity of the plist format. use crate::{ error::{Error, ErrorKind}, stream::{Event, OwnedEvent}, Integer, }; use std::io::Read; pub struct AsciiReader { reader: R, current_pos: u64, /// lookahead char to avoid backtracking. peeked_char: Option, current_char: Option, } impl AsciiReader { pub fn new(reader: R) -> Self { Self { reader, current_pos: 0, peeked_char: None, current_char: None, } } pub fn into_inner(self) -> R { self.reader } fn error(&self, kind: ErrorKind) -> Error { kind.with_byte_offset(self.current_pos) } fn read_one(&mut self) -> Result, Error> { let mut buf: [u8; 1] = [0; 1]; match self.reader.read_exact(&mut buf) { Ok(()) => Ok(Some(buf[0])), Err(err) => { if err.kind() == std::io::ErrorKind::UnexpectedEof { Ok(None) } else { Err(self.error(ErrorKind::Io(err))) } } } } /// Consume the reader and set [`Self::current_char`] and /// [`Self::peeked_char`]. Returns the current character. fn advance(&mut self) -> Result, Error> { self.current_char = self.peeked_char; self.peeked_char = self.read_one()?; // We need to read two chars to boot the process and fill the peeked // char. if self.current_pos == 0 { self.current_char = self.peeked_char; self.peeked_char = self.read_one()?; } if self.current_char.is_some() { self.current_pos += 1; } Ok(self.current_char) } /// From Apple doc: /// /// > The quotation marks can be omitted if the string is composed strictly of alphanumeric /// > characters and contains no white space (numbers are handled as /// > strings in property lists). Though the property list format uses /// > ASCII for strings, note that Cocoa uses Unicode. Since string /// > encodings vary from region to region, this representation makes the /// > format fragile. You may see strings containing unreadable sequences of /// > ASCII characters; these are used to represent Unicode characters /// /// This function will naively try to convert the string to Integer. fn unquoted_string_literal(&mut self, first: u8) -> Result, Error> { let mut acc: Vec = Vec::new(); acc.push(first); while { match self.peeked_char { Some(c) => { c != b' ' && c != b')' && c != b'\r' && c != b'\t' && c != b';' && c != b',' } None => false, } } { // consuming the string itself self.advance()?; match self.current_char { Some(c) => acc.push(c), None => return Err(self.error(ErrorKind::UnclosedString)), }; } let string_literal = String::from_utf8(acc).map_err(|_e| self.error(ErrorKind::InvalidUtf8AsciiStream))?; // Not ideal but does the trick for now match Integer::from_str(&string_literal) { Ok(i) => Ok(Some(Event::Integer(i))), Err(_) => Ok(Some(Event::String(string_literal.into()))), } } /// The process for decoding utf-16 escapes to utf-8 is: /// 1. Convert the 4 hex characters to utf-16 code units (u16s). /// '\u006d' becomes 0x6d. /// 2. Based on the first code unit, determine whether another code unit is /// required to form the complete code point. /// "\uD83D\uDCA9" becomes `[0xd73d, 0xdca9]` /// 3. Convert the 1 or 2 u16 code point to utf-8. /// `[0xd73d, 0xdca9]` becomes '💩'. /// /// The standard library has some useful functions behind unstable feature /// flags, we can simplify and optimize this a bit once they're stable. /// - str_from_utf16_endian /// - is_utf16_surrogate fn utf16_escape(&mut self) -> Result { let mut code_units: &mut [u16] = &mut [0u16; 2]; let Some(code_unit) = self.utf16_code_unit()? else { return Err(self.error(ErrorKind::InvalidUtf16String)); }; code_units[0] = code_unit; // This is the utf-16 surrogate range, indicating another code unit is // necessary to form a complete code point. if !matches!(code_unit, 0xD800..=0xDFFF) { code_units = &mut code_units[0..1]; } else { self.advance_quoted_string()?; if self.current_char != Some(b'\\') || !matches!(self.peeked_char, Some(b'u') | Some(b'U')) { return Err(self.error(ErrorKind::InvalidUtf16String)); } self.advance_quoted_string()?; if let Some(code_unit) = self.utf16_code_unit()? { code_units[1] = code_unit; } } let utf8 = String::from_utf16(code_units) .map_err(|_| self.error(ErrorKind::InvalidUtf16String))?; Ok(utf8) } /// Expects the reader's next read to return the first hex character of the /// utf-16 hex string. fn utf16_code_unit(&mut self) -> Result, Error> { let hex_chars = [ self.advance_quoted_string()?, self.advance_quoted_string()?, self.advance_quoted_string()?, self.advance_quoted_string()?, ]; let hex_str = std::str::from_utf8(&hex_chars) .map_err(|_| self.error(ErrorKind::InvalidUtf16String))?; let code_unit = u16::from_str_radix(hex_str, 16) .map_err(|_| self.error(ErrorKind::InvalidUtf16String))?; Ok(Some(code_unit)) } #[inline] fn advance_quoted_string(&mut self) -> Result { match self.advance()? { Some(c) => Ok(c), None => Err(self.error(ErrorKind::UnclosedString)), } } fn quoted_string_literal(&mut self, quote: u8) -> Result, Error> { let mut acc = String::new(); loop { let c = self.advance_quoted_string()?; if c == quote { return Ok(Some(Event::String(acc.into()))); } let replacement = if c == b'\\' { let c = self.advance_quoted_string()?; match c { b'\\' | b'"' => c as char, b'a' => '\u{7}', b'b' => '\u{8}', b'f' => '\u{c}', b'n' => '\n', b'r' => '\r', b't' => '\t', b'U' => { let utf8 = self.utf16_escape()?; acc.push_str(utf8.as_str()); continue; } b'v' => '\u{b}', b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' => { let value = [ c, self.advance_quoted_string()?, self.advance_quoted_string()?, ]; let value = std::str::from_utf8(&value) .map_err(|_| self.error(ErrorKind::InvalidOctalString))?; let value = u16::from_str_radix(value, 8) .map_err(|_| self.error(ErrorKind::InvalidOctalString))? as u32; let value = char::from_u32(value) .ok_or(self.error(ErrorKind::InvalidOctalString))?; map_next_step_to_unicode(value) } _ => return Err(self.error(ErrorKind::InvalidUtf8AsciiStream)), } } else { c as char }; acc.push(replacement); } } fn line_comment(&mut self) -> Result<(), Error> { // Consumes up to the end of the line. // There's no error in this a line comment can reach the EOF and there's // no forbidden chars in comments. while { match self.peeked_char { Some(c) => c != b'\n', None => false, } } { let _ = self.advance()?; } Ok(()) } fn block_comment(&mut self) -> Result<(), Error> { let mut latest_consume = b' '; while { latest_consume != b'*' || match self.advance()? { Some(c) => c != b'/', None => false, } } { latest_consume = self .advance()? .ok_or(self.error(ErrorKind::IncompleteComment))?; } Ok(()) } /// Returns: /// - Some(string) if '/' was the first character of a string /// - None if '/' was the beginning of a comment. fn potential_comment(&mut self) -> Result, Error> { match self.peeked_char { Some(c) => match c { b'/' => self.line_comment().map(|_| None), b'*' => self.block_comment().map(|_| None), _ => self.unquoted_string_literal(c), }, // EOF None => Err(self.error(ErrorKind::IncompleteComment)), } } /// Consumes the reader until it finds a valid Event /// Possible events for Ascii plists: /// - `StartArray(Option)`, /// - `StartDictionary(Option)`, /// - `EndCollection`, /// - `Data(Vec)`, fn read_next(&mut self) -> Result, Error> { while let Some(c) = self.advance()? { match c { // Single char tokens b'(' => return Ok(Some(Event::StartArray(None))), b')' => return Ok(Some(Event::EndCollection)), b'{' => return Ok(Some(Event::StartDictionary(None))), b'}' => return Ok(Some(Event::EndCollection)), b'\'' | b'"' => return self.quoted_string_literal(c), b'/' => { match self.potential_comment() { Ok(Some(event)) => return Ok(Some(event)), Ok(None) => { /* Comment has been consumed */ } Err(e) => return Err(e), } } b',' | b';' | b'=' => { /* consume these without emitting anything */ } b' ' | b'\r' | b'\t' | b'\n' => { /* whitespace is not significant */ } _ => return self.unquoted_string_literal(c), } } Ok(None) } } impl Iterator for AsciiReader { type Item = Result; fn next(&mut self) -> Option> { self.read_next().transpose() } } /// Maps NextStep encoding to Unicode, see: /// - /// - fn map_next_step_to_unicode(c: char) -> char { const NEXT_UNICODE_MAPPING: &[char] = &[ '\u{A0}', '\u{C0}', '\u{C1}', '\u{C2}', '\u{C3}', '\u{C4}', '\u{C5}', '\u{C7}', '\u{C8}', '\u{C9}', '\u{CA}', '\u{CB}', '\u{CC}', '\u{CD}', '\u{CE}', '\u{CF}', '\u{D0}', '\u{D1}', '\u{D2}', '\u{D3}', '\u{D4}', '\u{D5}', '\u{D6}', '\u{D9}', '\u{DA}', '\u{DB}', '\u{DC}', '\u{DD}', '\u{DE}', '\u{B5}', '\u{D7}', '\u{F7}', '\u{A9}', '\u{A1}', '\u{A2}', '\u{A3}', '\u{2044}', '\u{A5}', '\u{192}', '\u{A7}', '\u{A4}', '\u{2019}', '\u{201C}', '\u{AB}', '\u{2039}', '\u{203A}', '\u{FB01}', '\u{FB02}', '\u{AE}', '\u{2013}', '\u{2020}', '\u{2021}', '\u{B7}', '\u{A6}', '\u{B6}', '\u{2022}', '\u{201A}', '\u{201E}', '\u{201D}', '\u{BB}', '\u{2026}', '\u{2030}', '\u{AC}', '\u{BF}', '\u{B9}', '\u{2CB}', '\u{B4}', '\u{2C6}', '\u{2DC}', '\u{AF}', '\u{2D8}', '\u{2D9}', '\u{A8}', '\u{B2}', '\u{2DA}', '\u{B8}', '\u{B3}', '\u{2DD}', '\u{2DB}', '\u{2C7}', '\u{2014}', '\u{B1}', '\u{BC}', '\u{BD}', '\u{BE}', '\u{E0}', '\u{E1}', '\u{E2}', '\u{E3}', '\u{E4}', '\u{E5}', '\u{E7}', '\u{E8}', '\u{E9}', '\u{EA}', '\u{EB}', '\u{EC}', '\u{C6}', '\u{ED}', '\u{AA}', '\u{EE}', '\u{EF}', '\u{F0}', '\u{F1}', '\u{141}', '\u{D8}', '\u{152}', '\u{BA}', '\u{F2}', '\u{F3}', '\u{F4}', '\u{F5}', '\u{F6}', '\u{E6}', '\u{F9}', '\u{FA}', '\u{FB}', '\u{131}', '\u{FC}', '\u{FD}', '\u{142}', '\u{F8}', '\u{153}', '\u{DF}', '\u{FE}', '\u{FF}', '\u{FFFD}', '\u{FFFD}', ]; let index = c as usize; if index < 128 || index > 0xff { return c; } NEXT_UNICODE_MAPPING[index - 128] } #[cfg(test)] mod tests { use std::{fs::File, io::Cursor}; use super::*; use crate::stream::Event::*; #[test] fn empty_test() { let plist = "".to_owned(); let cursor = Cursor::new(plist.as_bytes()); let streaming_parser = AsciiReader::new(cursor); let events: Vec = streaming_parser.map(|e| e.unwrap()).collect(); assert_eq!(events, &[]); } #[test] fn streaming_sample() { let reader = File::open("./tests/data/ascii-sample.plist").unwrap(); let streaming_parser = AsciiReader::new(reader); let events: Vec = streaming_parser.map(|e| e.unwrap()).collect(); let comparison = &[ StartDictionary(None), String("KeyName1".into()), String("Value1".into()), String("AnotherKeyName".into()), String("Value2".into()), String("Something".into()), StartArray(None), String("ArrayItem1".into()), String("ArrayItem2".into()), String("ArrayItem3".into()), EndCollection, String("Key4".into()), String("0.10".into()), String("KeyFive".into()), StartDictionary(None), String("Dictionary2Key1".into()), String("Something".into()), String("AnotherKey".into()), String("Somethingelse".into()), EndCollection, EndCollection, ]; assert_eq!(events, comparison); } #[test] fn utf8_strings() { let plist = "{ names = (Léa, François, Żaklina, 王芳); }".to_owned(); let cursor = Cursor::new(plist.as_bytes()); let streaming_parser = AsciiReader::new(cursor); let events: Vec = streaming_parser.map(|e| e.unwrap()).collect(); let comparison = &[ StartDictionary(None), String("names".into()), StartArray(None), String("Léa".into()), String("François".into()), String("Żaklina".into()), String("王芳".into()), EndCollection, EndCollection, ]; assert_eq!(events, comparison); } #[test] fn invalid_utf16_escapes() { let plist = br#"{ key1 = "\U123"; key2 = "\UD83D"; key3 = "\u0080"; }"#; let cursor = Cursor::new(plist); let streaming_parser = AsciiReader::new(cursor); let events: Vec> = streaming_parser.collect(); // key1's value assert!(events[2].is_err()); // key2's value assert!(events[4].is_err()); // key3's value assert!(events[6].is_err()); } #[test] fn invalid_octal_escapes() { let plist = br#"{ key1 = "\1"; key2 = "\12"; }"#; let cursor = Cursor::new(plist); let streaming_parser = AsciiReader::new(cursor); let events: Vec> = streaming_parser.collect(); // key1's value assert!(events[2].is_err()); // key2's value assert!(events[4].is_err()); } #[test] fn escaped_sequences_in_strings() { let plist = br#"{ key1 = "va\"lue"; key2 = 'va"lue'; key3 = "va\a\b\f\n\r\t\v\"\nlue"; key4 = "a\012b"; key5 = "\\UD83D\\UDCA9"; key6 = "\UD83D\UDCA9"; key7 = "\U0080"; key8 = "\200\377"; }"#; let cursor = Cursor::new(plist); let streaming_parser = AsciiReader::new(cursor); let events: Vec = streaming_parser.map(|e| e.unwrap()).collect(); let comparison = &[ StartDictionary(None), String("key1".into()), String(r#"va"lue"#.into()), String("key2".into()), String(r#"va"lue"#.into()), String("key3".into()), String("va\u{7}\u{8}\u{c}\n\r\t\u{b}\"\nlue".into()), String("key4".into()), String("a\nb".into()), String("key5".into()), String("\\UD83D\\UDCA9".into()), String("key6".into()), String("💩".into()), String("key7".into()), String("\u{80}".into()), String("key8".into()), String("\u{a0}\u{fffd}".into()), EndCollection, ]; assert_eq!(events, comparison); } #[test] fn integers_and_strings() { let plist = "{ name = James, age = 42 }".to_owned(); let cursor = Cursor::new(plist.as_bytes()); let streaming_parser = AsciiReader::new(cursor); let events: Vec = streaming_parser.map(|e| e.unwrap()).collect(); let comparison = &[ StartDictionary(None), String("name".into()), String("James".into()), String("age".into()), Integer(42.into()), EndCollection, ]; assert_eq!(events, comparison); } #[test] fn netnewswire_pbxproj() { let reader = File::open("./tests/data/netnewswire.pbxproj").unwrap(); let streaming_parser = AsciiReader::new(reader); // Ensure that we don't fail when reading the file let events: Vec = streaming_parser.map(|e| e.unwrap()).collect(); assert!(!events.is_empty()); } } plist-1.7.0/src/stream/binary_reader.rs000064400000000000000000000421601046102023000162250ustar 00000000000000use std::{ io::{self, Read, Seek, SeekFrom}, mem::size_of, }; use crate::{ date::{Date, InfiniteOrNanDate}, error::{Error, ErrorKind}, stream::{Event, OwnedEvent}, u64_to_usize, Uid, }; struct StackItem { object_ref: u64, child_object_refs: Vec, ty: StackType, } enum StackType { Array, Dict, } // https://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c // https://hg.python.org/cpython/file/3.4/Lib/plistlib.py pub struct BinaryReader { stack: Vec, object_offsets: Vec, object_on_stack: Vec, reader: PosReader, ref_size: u8, root_object: u64, trailer_start_offset: u64, } struct PosReader { reader: R, pos: u64, } impl PosReader { fn read_all(&mut self, buf: &mut [u8]) -> Result<(), Error> { self.read_exact(buf) .map_err(|err| ErrorKind::Io(err).with_byte_offset(self.pos))?; Ok(()) } fn seek(&mut self, pos: SeekFrom) -> Result { self.pos = self .reader .seek(pos) .map_err(|err| ErrorKind::Io(err).with_byte_offset(self.pos))?; Ok(self.pos) } } impl Read for PosReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { let count = self.reader.read(buf)?; self.pos .checked_add(count as u64) .expect("file cannot be larger than `u64::max_value()` bytes"); Ok(count) } } impl BinaryReader { pub fn new(reader: R) -> BinaryReader { BinaryReader { stack: Vec::new(), object_offsets: Vec::new(), object_on_stack: Vec::new(), reader: PosReader { reader, pos: 0 }, ref_size: 0, root_object: 0, trailer_start_offset: 0, } } fn allocate_vec(&self, len: u64, size: usize) -> Result, Error> { // Check we are not reading past the start of the plist trailer let inner = |len: u64, size: usize| { let byte_len = len.checked_mul(size as u64)?; let end_offset = self.reader.pos.checked_add(byte_len)?; if end_offset <= self.trailer_start_offset { Some(()) } else { None } }; inner(len, size).ok_or_else(|| self.with_pos(ErrorKind::ObjectOffsetTooLarge))?; Ok(Vec::with_capacity(len as usize)) } fn read_trailer(&mut self) -> Result<(), Error> { self.reader.seek(SeekFrom::Start(0))?; let mut magic = [0; 8]; self.reader.read_all(&mut magic)?; if &magic != b"bplist00" { return Err(self.with_pos(ErrorKind::InvalidMagic)); } self.trailer_start_offset = self.reader.seek(SeekFrom::End(-32))?; // Trailer starts with 6 bytes of padding let mut zeros = [0; 6]; self.reader.read_all(&mut zeros)?; let offset_size = self.read_u8()?; match offset_size { 1 | 2 | 4 | 8 => (), _ => return Err(self.with_pos(ErrorKind::InvalidTrailerObjectOffsetSize)), } self.ref_size = self.read_u8()?; match self.ref_size { 1 | 2 | 4 | 8 => (), _ => return Err(self.with_pos(ErrorKind::InvalidTrailerObjectReferenceSize)), } let num_objects = self.read_be_u64()?; self.root_object = self.read_be_u64()?; let offset_table_offset = self.read_be_u64()?; // Read offset table self.reader.seek(SeekFrom::Start(offset_table_offset))?; self.object_offsets = self.read_ints(num_objects, offset_size)?; self.object_on_stack = vec![false; self.object_offsets.len()]; Ok(()) } /// Reads a list of `len` big-endian integers of `size` bytes from the reader. fn read_ints(&mut self, len: u64, size: u8) -> Result, Error> { let mut ints = self.allocate_vec(len, size as usize)?; for _ in 0..len { match size { 1 => ints.push(self.read_u8()?.into()), 2 => ints.push(self.read_be_u16()?.into()), 4 => ints.push(self.read_be_u32()?.into()), 8 => ints.push(self.read_be_u64()?), _ => unreachable!("size is either self.ref_size or offset_size both of which are already validated") } } Ok(ints) } /// Reads a list of `len` offsets into the object table from the reader. fn read_refs(&mut self, len: u64) -> Result, Error> { let ref_size = self.ref_size; self.read_ints(len, ref_size) } /// Reads a compressed value length from the reader. `len` must contain the low 4 bits of the /// object token. fn read_object_len(&mut self, len: u8) -> Result { if (len & 0x0f) == 0x0f { let len_power_of_two = self.read_u8()? & 0x03; Ok(match len_power_of_two { 0 => self.read_u8()?.into(), 1 => self.read_be_u16()?.into(), 2 => self.read_be_u32()?.into(), 3 => self.read_be_u64()?, _ => return Err(self.with_pos(ErrorKind::InvalidObjectLength)), }) } else { Ok(len.into()) } } /// Reads `len` bytes from the reader. fn read_data(&mut self, len: u64) -> Result, Error> { let mut data = self.allocate_vec(len, size_of::())?; data.resize(len as usize, 0); self.reader.read_all(&mut data)?; Ok(data) } fn seek_to_object(&mut self, object_ref: u64) -> Result { let object_ref = u64_to_usize(object_ref) .ok_or_else(|| self.with_pos(ErrorKind::ObjectReferenceTooLarge))?; let offset = *self .object_offsets .get(object_ref) .ok_or_else(|| self.with_pos(ErrorKind::ObjectReferenceTooLarge))?; if offset >= self.trailer_start_offset { return Err(self.with_pos(ErrorKind::ObjectOffsetTooLarge)); } self.reader.seek(SeekFrom::Start(offset)) } fn push_stack_item_and_check_for_recursion(&mut self, item: StackItem) -> Result<(), Error> { let object_ref = u64_to_usize(item.object_ref).expect("internal consistency error"); let is_on_stack = &mut self.object_on_stack[object_ref]; if *is_on_stack { return Err(self.with_pos(ErrorKind::RecursiveObject)); } *is_on_stack = true; self.stack.push(item); Ok(()) } fn pop_stack_item(&mut self) -> StackItem { let item = self.stack.pop().expect("internal consistency error"); let object_ref = u64_to_usize(item.object_ref).expect("internal consistency error"); self.object_on_stack[object_ref] = false; item } fn read_next(&mut self) -> Result, Error> { let object_ref = if self.ref_size == 0 { // Initialise here rather than in new self.read_trailer()?; self.root_object } else { let maybe_object_ref = if let Some(stack_item) = self.stack.last_mut() { stack_item.child_object_refs.pop() } else { // Finished reading the plist return Ok(None); }; if let Some(object_ref) = maybe_object_ref { object_ref } else { // We're at the end of an array or dict. Pop the top stack item and return. let stack_item = self.pop_stack_item(); match stack_item.ty { StackType::Array | StackType::Dict => return Ok(Some(Event::EndCollection)), } } }; self.seek_to_object(object_ref)?; let token = self.read_u8()?; let ty = (token & 0xf0) >> 4; let size = token & 0x0f; let result = match (ty, size) { (0x0, 0x00) => return Err(self.with_pos(ErrorKind::NullObjectUnimplemented)), (0x0, 0x08) => Some(Event::Boolean(false)), (0x0, 0x09) => Some(Event::Boolean(true)), (0x0, 0x0f) => return Err(self.with_pos(ErrorKind::FillObjectUnimplemented)), (0x1, 0) => Some(Event::Integer(self.read_u8()?.into())), (0x1, 1) => Some(Event::Integer(self.read_be_u16()?.into())), (0x1, 2) => Some(Event::Integer(self.read_be_u32()?.into())), (0x1, 3) => Some(Event::Integer(self.read_be_i64()?.into())), (0x1, 4) => { let value = self.read_be_i128()?; if value < 0 || value > u64::max_value().into() { return Err(self.with_pos(ErrorKind::IntegerOutOfRange)); } Some(Event::Integer((value as u64).into())) } (0x1, _) => return Err(self.with_pos(ErrorKind::UnknownObjectType(token))), // variable length int (0x2, 2) => Some(Event::Real(f32::from_bits(self.read_be_u32()?).into())), (0x2, 3) => Some(Event::Real(f64::from_bits(self.read_be_u64()?))), (0x2, _) => return Err(self.with_pos(ErrorKind::UnknownObjectType(token))), // odd length float (0x3, 3) => { // Date. Seconds since 1/1/2001 00:00:00. let secs = f64::from_bits(self.read_be_u64()?); let date = Date::from_seconds_since_plist_epoch(secs) .map_err(|InfiniteOrNanDate| self.with_pos(ErrorKind::InfiniteOrNanDate))?; Some(Event::Date(date)) } (0x4, n) => { // Data let len = self.read_object_len(n)?; Some(Event::Data(self.read_data(len)?.into())) } (0x5, n) => { // ASCII string let len = self.read_object_len(n)?; let raw = self.read_data(len)?; let string = String::from_utf8(raw) .map_err(|_| self.with_pos(ErrorKind::InvalidUtf8String))?; Some(Event::String(string.into())) } (0x6, n) => { // UTF-16 string let len_utf16_codepoints = self.read_object_len(n)?; let mut raw_utf16 = self.allocate_vec(len_utf16_codepoints, size_of::())?; for _ in 0..len_utf16_codepoints { raw_utf16.push(self.read_be_u16()?); } let string = String::from_utf16(&raw_utf16) .map_err(|_| self.with_pos(ErrorKind::InvalidUtf16String))?; Some(Event::String(string.into())) } (0x8, n) if n < 8 => { // Uid let mut buf = [0; 8]; // `len_bytes` is at most 8. let len_bytes = n as usize + 1; // Values are stored in big-endian so we must put the least significant bytes at // the end of the buffer. self.reader.read_all(&mut buf[8 - len_bytes..])?; let value = u64::from_be_bytes(buf); Some(Event::Uid(Uid::new(value))) } (0xa, n) => { // Array let len = self.read_object_len(n)?; let mut child_object_refs = self.read_refs(len)?; // Reverse so we can pop off the end of the stack in order child_object_refs.reverse(); self.push_stack_item_and_check_for_recursion(StackItem { object_ref, ty: StackType::Array, child_object_refs, })?; Some(Event::StartArray(Some(len))) } (0xd, n) => { // Dict let len = self.read_object_len(n)?; let key_refs = self.read_refs(len)?; let value_refs = self.read_refs(len)?; let keys_and_values_len = len .checked_mul(2) .ok_or_else(|| self.with_pos(ErrorKind::ObjectTooLarge))?; let mut child_object_refs = self.allocate_vec(keys_and_values_len, self.ref_size as usize)?; let len = key_refs.len(); for i in 1..=len { // Reverse so we can pop off the end of the stack in order child_object_refs.push(value_refs[len - i]); child_object_refs.push(key_refs[len - i]); } self.push_stack_item_and_check_for_recursion(StackItem { object_ref, ty: StackType::Dict, child_object_refs, })?; Some(Event::StartDictionary(Some(len as u64))) } (_, _) => return Err(self.with_pos(ErrorKind::UnknownObjectType(token))), }; Ok(result) } fn read_u8(&mut self) -> Result { let mut buf = [0; 1]; self.reader.read_all(&mut buf)?; Ok(buf[0]) } fn read_be_u16(&mut self) -> Result { let mut buf = [0; 2]; self.reader.read_all(&mut buf)?; Ok(u16::from_be_bytes(buf)) } fn read_be_u32(&mut self) -> Result { let mut buf = [0; 4]; self.reader.read_all(&mut buf)?; Ok(u32::from_be_bytes(buf)) } fn read_be_u64(&mut self) -> Result { let mut buf = [0; 8]; self.reader.read_all(&mut buf)?; Ok(u64::from_be_bytes(buf)) } fn read_be_i64(&mut self) -> Result { let mut buf = [0; 8]; self.reader.read_all(&mut buf)?; Ok(i64::from_be_bytes(buf)) } fn read_be_i128(&mut self) -> Result { let mut buf = [0; 16]; self.reader.read_all(&mut buf)?; Ok(i128::from_be_bytes(buf)) } fn with_pos(&self, kind: ErrorKind) -> Error { kind.with_byte_offset(self.reader.pos) } } impl Iterator for BinaryReader { type Item = Result; fn next(&mut self) -> Option> { match self.read_next() { Ok(Some(event)) => Some(Ok(event)), Err(err) => { // Mark the plist as finished self.stack.clear(); Some(Err(err)) } Ok(None) => None, } } } #[cfg(test)] mod tests { use std::fs::File; use super::*; #[test] fn streaming_parser() { use crate::stream::Event::*; let reader = File::open("./tests/data/binary.plist").unwrap(); let streaming_parser = BinaryReader::new(reader); let events: Vec = streaming_parser.map(|e| e.unwrap()).collect(); let comparison = &[ StartDictionary(Some(13)), String("Author".into()), String("William Shakespeare".into()), String("Birthdate".into()), Date(super::Date::from_xml_format("1981-05-16T11:32:06Z").unwrap()), String("EmptyArray".into()), StartArray(Some(0)), EndCollection, String("IsNotFalse".into()), Boolean(false), String("SmallestNumber".into()), Integer((-9223372036854775808i64).into()), String("EmptyDictionary".into()), StartDictionary(Some(0)), EndCollection, String("Height".into()), Real(1.6), String("Lines".into()), StartArray(Some(2)), String("It is a tale told by an idiot, ".into()), String("Full of sound and fury, signifying nothing.".into()), EndCollection, String("Death".into()), Integer(1564.into()), String("Blank".into()), String("".into()), String("BiggestNumber".into()), Integer(18446744073709551615u64.into()), String("IsTrue".into()), Boolean(true), String("Data".into()), Data(vec![0, 0, 0, 190, 0, 0, 0, 3, 0, 0, 0, 30, 0, 0, 0].into()), EndCollection, ]; assert_eq!(events, &comparison[..]); } #[test] fn utf16_plist() { let reader = File::open("./tests/data/utf16_bplist.plist").unwrap(); let streaming_parser = BinaryReader::new(reader); let mut events: Vec = streaming_parser.map(|e| e.unwrap()).collect(); assert_eq!(events[2], Event::String("\u{2605} or better".into())); let poem = if let Event::String(ref mut poem) = events[4] { poem } else { panic!("not a string") }; assert_eq!(poem.len(), 643); assert_eq!(poem.to_mut().pop().unwrap(), '\u{2605}'); } #[test] fn nskeyedarchiver_plist() { let reader = File::open("./tests/data/binary_NSKeyedArchiver.plist").unwrap(); let streaming_parser = BinaryReader::new(reader); let events: Vec = streaming_parser.map(|e| e.unwrap()).collect(); assert_eq!(events[10], Event::Uid(Uid::new(4))); assert_eq!(events[12], Event::Uid(Uid::new(2))); assert_eq!(events[18], Event::Uid(Uid::new(3))); assert_eq!(events[46], Event::Uid(Uid::new(1))); } } plist-1.7.0/src/stream/binary_writer.rs000064400000000000000000000651741046102023000163110ustar 00000000000000// TODO: Revisit the design of `Event` once the `HashMap` raw interface is stabilised. // Ideally `Value`s would be stored inline in `Event`. use indexmap::IndexMap; use std::{ borrow::Cow, io::{self, Write}, num::NonZeroUsize, }; use crate::{ error::{self, Error, ErrorKind, EventKind}, stream::Writer, Date, Integer, Uid, }; pub struct BinaryWriter { writer: PosWriter, events: Vec, dictionary_key_events: Vec, values: IndexMap, ValueState>, /// Pointers into `events` for each of the currently unclosed `Collection` events. collection_stack: Vec, /// The number of `Collection` and unique `Value` events in `events`. num_objects: usize, } struct PosWriter { writer: W, pos: usize, } #[derive(Clone)] struct ObjectRef(NonZeroUsize); /// An array of `len` elements is stored as a `Collection` event followed by `skip_len` events /// containing the contents of the array. e.g. /// /// Collection(ty: Array, len: 2, skip_len: 2) /// Value /// Value /// /// If the array contains another array or dictionary `len` and `skip_len` will differ. e.g. /// /// Collection(ty: Array, len: 2, skip_len: 3) /// Value /// Collection(ty: Array, len: 1, skip_len: 1) /// Value /// /// A dictionary of `len` (key, value) pairs is stored as a `Collection` event followed by /// `skip_len` events containing the contents of the dictionary. The dictionary values are stored /// first. These are followed by a `DictionaryKeys` event and then the keys themselves. e.g. /// /// Collection(ty: Dictionary, len: 2, skip_len: 6) /// Value /// Collection(ty: Array, len: 1, skip_len: 1) /// Value /// DictionaryKeys(2) /// Value (Key) /// Value (Key) /// /// This arrangement simplifies writing dictionaries as they must be written in the order /// (key, key, value, value) instead of (key, value, key, value) as they are passed to the writer. /// Unclosed dictionaries have their keys stored in `dictionary_key_events` and these are only /// moved to the end of the `BinaryWriter::events` array once the dictionary is closed in /// `write_end_collection`. enum Event { Collection(Collection), /// Index of the value in the `values` map. Value(usize), /// The number of dictionary keys following this event. DictionaryKeys(usize), } struct Collection { ty: CollectionType, /// The number of elements in an array or (key, value) pairs in a dictionary. /// Unclosed dictionaries have a `len` equal to the number of keys plus the number of values /// written so far. This is fixed up in `write_end_collection`. len: usize, /// The number of events to skip to get to the next element after the collection. skip: usize, object_ref: Option, } #[derive(Eq, PartialEq)] enum CollectionType { Array, Dictionary, } #[derive(Eq, Hash, PartialEq)] enum Value<'a> { Boolean(bool), Data(Cow<'a, [u8]>), Date(Date), Integer(Integer), /// Floats are deduplicated based on their bitwise value. Real(u64), String(Cow<'a, str>), Uid(Uid), } enum ValueState { /// The value has not been assigned an object reference. Unassigned, /// The value has been assigned an object reference but has not yet been written. Unwritten(ObjectRef), /// The value has been written with the given object reference. Written(ObjectRef), } impl BinaryWriter { pub fn new(writer: W) -> BinaryWriter { BinaryWriter { writer: PosWriter { writer, pos: 0 }, events: Vec::new(), dictionary_key_events: Vec::new(), values: IndexMap::new(), collection_stack: Vec::new(), num_objects: 0, } } fn write_start_collection(&mut self, ty: CollectionType) -> Result<(), Error> { if self.expecting_dictionary_key() { let ty_event_kind = match ty { CollectionType::Array => EventKind::StartArray, CollectionType::Dictionary => EventKind::StartDictionary, }; return Err(ErrorKind::UnexpectedEventType { expected: EventKind::DictionaryKeyOrEndCollection, found: ty_event_kind, } .without_position()); } self.increment_current_collection_len(); self.collection_stack.push(self.events.len()); self.events.push(Event::Collection(Collection { ty, len: 0, skip: 0, object_ref: None, })); self.num_objects += 1; Ok(()) } fn write_end_collection(&mut self) -> Result<(), Error> { let collection_event_index = self.collection_stack.pop().ok_or_else(|| { ErrorKind::UnexpectedEventType { expected: EventKind::ValueOrStartCollection, found: EventKind::EndCollection, } .without_position() })?; let current_event_index = self.events.len() - 1; let c = if let Event::Collection(c) = &mut self.events[collection_event_index] { c } else { unreachable!("items in `collection_stack` always point to a collection event"); }; c.skip = current_event_index - collection_event_index; if let CollectionType::Dictionary = c.ty { // Ensure that every dictionary key is paired with a value. if !is_even(c.len) { return Err(ErrorKind::UnexpectedEventType { expected: EventKind::DictionaryKeyOrEndCollection, found: EventKind::EndCollection, } .without_position()); } // Fix up the dictionary length. It should contain the number of key-value pairs, // not the number of keys and values. c.len /= 2; // To skip past a dictionary we also need to skip the `DictionaryKeys` event and the // keys that follow it. c.skip += 1 + c.len; let len = c.len; self.events.push(Event::DictionaryKeys(len)); // Move the cached dictionary keys to the end of the events array. let keys_start_index = self.dictionary_key_events.len() - len; self.events.extend( self.dictionary_key_events .drain(keys_start_index..) .map(Event::Value), ); } if self.collection_stack.is_empty() { self.write_plist()?; } Ok(()) } fn write_value(&mut self, value: Value) -> Result<(), Error> { let expecting_dictionary_key = self.expecting_dictionary_key(); // Ensure that all dictionary keys are strings. match (&value, expecting_dictionary_key) { (Value::String(_), true) | (_, false) => (), (_, true) => { return Err(ErrorKind::UnexpectedEventType { expected: EventKind::DictionaryKeyOrEndCollection, found: value.event_kind(), } .without_position()) } } // Deduplicate `value`. There is one entry in `values` for each unqiue `Value` in the // plist. let value_index = if let Some((value_index, _, _)) = self.values.get_full(&value) { value_index } else { self.num_objects += 1; let value = value.into_owned(); let (value_index, _) = self.values.insert_full(value, ValueState::Unassigned); value_index }; // Dictionary keys are buffered in `dictionary_key_events` until the dictionary is closed // in `write_end_collection` when they are moved to the end of the `events` array. if expecting_dictionary_key { self.dictionary_key_events.push(value_index); } else { self.events.push(Event::Value(value_index)); } self.increment_current_collection_len(); if self.collection_stack.is_empty() { self.write_plist()?; } Ok(()) } fn expecting_dictionary_key(&self) -> bool { if let Some(&event_index) = self.collection_stack.last() { if let Event::Collection(c) = &self.events[event_index] { c.ty == CollectionType::Dictionary && is_even(c.len) } else { unreachable!("items in `collection_stack` always point to a collection event"); } } else { false } } fn increment_current_collection_len(&mut self) { if let Some(&event_index) = self.collection_stack.last() { if let Event::Collection(c) = &mut self.events[event_index] { c.len += 1; } else { unreachable!("items in `collection_stack` always point to a collection event"); } } } fn write_plist(&mut self) -> Result<(), Error> { assert!(self.collection_stack.is_empty()); // Write header self.writer.write_exact(b"bplist00")?; // Write objects let mut events_vec = std::mem::take(&mut self.events); let mut events = &mut events_vec[..]; let ref_size = plist_ref_size(self.num_objects - 1); let mut offset_table = vec![0; self.num_objects]; // Assign the first (root) event an object reference of zero. let mut next_object_ref = ObjectRef::zero(); match &mut events[0] { Event::Value(value_index) => { let (_, value_state) = value_mut(&mut self.values, *value_index); *value_state = ValueState::Unwritten(next_object_ref.clone_and_increment_self()); } Event::Collection(c) => { c.object_ref = Some(next_object_ref.clone_and_increment_self()); } Event::DictionaryKeys(_) => { unreachable!("`events` starts with a value or collection event") } } while let Some((event, rest)) = events.split_first_mut() { events = rest; match event { Event::Collection(c) => { let collection_events = &mut events[..c.skip]; self.write_plist_collection( c, collection_events, ref_size, &mut next_object_ref, &mut offset_table, )?; } Event::Value(value_index) => { self.write_plist_value(*value_index, &mut offset_table)?; } // Dictionary keys will have already been written in `write_plist_collection` so we // skip over them here. Event::DictionaryKeys(len) => { events = &mut events[*len..]; } } } // Write object offset table let offset_table_offset = self.writer.pos; let offset_size = plist_ref_size(offset_table_offset); for &offset in &offset_table { write_plist_ref(&mut self.writer, offset_size, offset)?; } // Write trailer // 6 zero bytes padding // 1 byte offset size // 1 byte object ref size // 8 bytes number of objects // 8 bytes root object ref (always zero) // 8 bytes file offset of the object offset table let mut trailer = [0; 32]; trailer[6] = offset_size; trailer[7] = ref_size; trailer[8..16].copy_from_slice(&(self.num_objects as u64).to_be_bytes()); trailer[24..32].copy_from_slice(&(offset_table_offset as u64).to_be_bytes()); self.writer.write_exact(&trailer)?; self.writer .flush() .map_err(error::from_io_without_position)?; // Reset plist writer self.writer.pos = 0; events_vec.clear(); self.events = events_vec; self.values.clear(); self.num_objects = 0; Ok(()) } fn write_plist_collection( &mut self, collection: &Collection, events: &mut [Event], ref_size: u8, next_object_ref: &mut ObjectRef, offset_table: &mut [usize], ) -> Result<(), Error> { if let Some(object_ref) = &collection.object_ref { offset_table[object_ref.value()] = self.writer.pos; } else { unreachable!("collection object refs are assigned before this function is called"); } // Split the events in the current collection into keys and values (arrays contain only // values). This is required as dictionary keys appear after values in the `events array // but all keys must be written before any values. let (keys, values, ty) = match collection.ty { CollectionType::Array => (&mut [][..], events, 0xa0), CollectionType::Dictionary => { let keys_start_offset = events.len() - collection.len - 1; let (values, keys) = events.split_at_mut(keys_start_offset); (&mut keys[1..], values, 0xd0) } }; let mut collection_events = keys.iter_mut().chain(values); // Collections are written as a length prefixed array of object references. For an array // the length is the number of elements. For a dictionary it is the number of (key, value) // pairs. write_plist_value_ty_and_size(&mut self.writer, ty, collection.len)?; while let Some(event) = collection_events.next() { let object_ref = match event { Event::Collection(c) => { // We only want to write references to top level elements in the collection so // we skip over the contents of any sub-collections. if c.skip > 0 { let _ = collection_events.nth(c.skip - 1); } // Collections are not deduplicated so they must be assigned an object // reference here. assert!(c.object_ref.is_none()); let object_ref = next_object_ref.clone_and_increment_self(); c.object_ref = Some(object_ref.clone()); object_ref } Event::Value(value_index) => { // Values are deduplicated so we only assign an object reference if we have not // already done so previously. let (_, value_state) = value_mut(&mut self.values, *value_index); match value_state { ValueState::Unassigned => { let object_ref = next_object_ref.clone_and_increment_self(); *value_state = ValueState::Unwritten(object_ref.clone()); object_ref } ValueState::Unwritten(object_ref) | ValueState::Written(object_ref) => { object_ref.clone() } } } Event::DictionaryKeys(_) => unreachable!( "`DictionaryKeys` events are specifically excluded from the iterator" ), }; write_plist_ref(&mut self.writer, ref_size, object_ref.value())?; } // We write dictionary keys here as they appear after values in the `events` array but // should come before values in the plist stream to reduce seeking on read. for key in keys { if let Event::Value(value_index) = key { self.write_plist_value(*value_index, offset_table)?; } else { unreachable!("dictionary keys are assigned as values in `write_end_collection`"); } } Ok(()) } fn write_plist_value( &mut self, value_index: usize, offset_table: &mut [usize], ) -> Result<(), Error> { let (value, value_state) = value_mut(&mut self.values, value_index); let object_ref = match value_state { ValueState::Unassigned => { unreachable!("value object refs are assigned before this function is called"); } ValueState::Unwritten(object_ref) => object_ref.clone(), ValueState::Written(_) => return Ok(()), }; offset_table[object_ref.value()] = self.writer.pos; *value_state = ValueState::Written(object_ref); match value { Value::Boolean(true) => { self.writer.write_exact(&[0x09])?; } Value::Boolean(false) => { self.writer.write_exact(&[0x08])?; } Value::Data(v) => { write_plist_value_ty_and_size(&mut self.writer, 0x40, v.len())?; self.writer.write_exact(&v[..])?; } Value::Date(v) => { let secs = v.as_seconds_since_plist_epoch(); let mut buf: [_; 9] = [0x33, 0, 0, 0, 0, 0, 0, 0, 0]; buf[1..].copy_from_slice(&secs.to_bits().to_be_bytes()); self.writer.write_exact(&buf)?; } Value::Integer(v) => { if let Some(v) = v.as_signed() { if v >= 0 && v <= i64::from(u8::max_value()) { self.writer.write_exact(&[0x10, v as u8])?; } else if v >= 0 && v <= i64::from(u16::max_value()) { let mut buf: [_; 3] = [0x11, 0, 0]; buf[1..].copy_from_slice(&(v as u16).to_be_bytes()); self.writer.write_exact(&buf)?; } else if v >= 0 && v <= i64::from(u32::max_value()) { let mut buf: [_; 5] = [0x12, 0, 0, 0, 0]; buf[1..].copy_from_slice(&(v as u32).to_be_bytes()); self.writer.write_exact(&buf)?; } else { let mut buf: [_; 9] = [0x13, 0, 0, 0, 0, 0, 0, 0, 0]; buf[1..].copy_from_slice(&v.to_be_bytes()); self.writer.write_exact(&buf)?; } } else if let Some(v) = v.as_unsigned() { // `u64`s larger than `i64::max_value()` are stored as signed 128 bit // integers. let mut buf: [_; 17] = [0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; buf[1..].copy_from_slice(&i128::from(v).to_be_bytes()); self.writer.write_exact(&buf)?; } else { unreachable!("an integer can be represented as either an i64 or u64"); } } Value::Real(v) => { let mut buf: [_; 9] = [0x23, 0, 0, 0, 0, 0, 0, 0, 0]; buf[1..].copy_from_slice(&v.to_be_bytes()); self.writer.write_exact(&buf)?; } Value::String(v) if v.is_ascii() => { let ascii = v.as_bytes(); write_plist_value_ty_and_size(&mut self.writer, 0x50, ascii.len())?; self.writer.write_exact(ascii)?; } Value::String(v) => { let utf16_len = v.encode_utf16().count(); write_plist_value_ty_and_size(&mut self.writer, 0x60, utf16_len)?; for c in v.encode_utf16() { self.writer.write_exact(&c.to_be_bytes())?; } } Value::Uid(v) => { let v = v.get(); if v <= u64::from(u8::max_value()) { self.writer.write_exact(&[0x80, v as u8])?; } else if v <= u64::from(u16::max_value()) { let mut buf: [_; 3] = [0x81, 0, 0]; buf[1..].copy_from_slice(&(v as u16).to_be_bytes()); self.writer.write_exact(&buf)?; } else if v <= u64::from(u32::max_value()) { let mut buf: [_; 5] = [0x83, 0, 0, 0, 0]; buf[1..].copy_from_slice(&(v as u32).to_be_bytes()); self.writer.write_exact(&buf)?; } else { let mut buf: [_; 9] = [0x87, 0, 0, 0, 0, 0, 0, 0, 0]; // we want to be explicit about the type here #[allow(clippy::unnecessary_cast)] buf[1..].copy_from_slice(&(v as u64).to_be_bytes()); self.writer.write_exact(&buf)?; } } } Ok(()) } } impl Writer for BinaryWriter { fn write_start_array(&mut self, _len: Option) -> Result<(), Error> { self.write_start_collection(CollectionType::Array) } fn write_start_dictionary(&mut self, _len: Option) -> Result<(), Error> { self.write_start_collection(CollectionType::Dictionary) } fn write_end_collection(&mut self) -> Result<(), Error> { self.write_end_collection() } fn write_boolean(&mut self, value: bool) -> Result<(), Error> { self.write_value(Value::Boolean(value)) } fn write_data(&mut self, value: Cow<[u8]>) -> Result<(), Error> { self.write_value(Value::Data(value)) } fn write_date(&mut self, value: Date) -> Result<(), Error> { self.write_value(Value::Date(value)) } fn write_integer(&mut self, value: Integer) -> Result<(), Error> { self.write_value(Value::Integer(value)) } fn write_real(&mut self, value: f64) -> Result<(), Error> { self.write_value(Value::Real(value.to_bits())) } fn write_string(&mut self, value: Cow) -> Result<(), Error> { self.write_value(Value::String(value)) } fn write_uid(&mut self, value: Uid) -> Result<(), Error> { self.write_value(Value::Uid(value)) } } fn is_even(value: usize) -> bool { value & 1 == 0 } fn value_mut<'a>( values: &'a mut IndexMap, ValueState>, value_index: usize, ) -> (&'a Value<'static>, &'a mut ValueState) { values .get_index_mut(value_index) .expect("internal consistency error") } fn write_plist_value_ty_and_size( writer: &mut PosWriter, token: u8, size: usize, ) -> Result<(), Error> { if size < 0x0f { writer.write_exact(&[token | (size as u8)])?; } else if size <= u8::max_value() as usize { writer.write_exact(&[token | 0x0f, 0x10, size as u8])?; } else if size <= u16::max_value() as usize { let mut buf: [_; 4] = [token | 0x0f, 0x11, 0, 0]; buf[2..].copy_from_slice(&(size as u16).to_be_bytes()); writer.write_exact(&buf)?; } else if size <= u32::max_value() as usize { let mut buf: [_; 6] = [token | 0x0f, 0x12, 0, 0, 0, 0]; buf[2..].copy_from_slice(&(size as u32).to_be_bytes()); writer.write_exact(&buf)?; } else { let mut buf: [_; 10] = [token | 0x0f, 0x13, 0, 0, 0, 0, 0, 0, 0, 0]; buf[2..].copy_from_slice(&(size as u64).to_be_bytes()); writer.write_exact(&buf)?; } Ok(()) } fn plist_ref_size(max_value: usize) -> u8 { let significant_bits = 64 - (max_value as u64).leading_zeros() as u8; // Convert to number of bytes let significant_bytes = (significant_bits + 7) / 8; // Round up to the next integer byte size which must be power of two. significant_bytes.next_power_of_two() } fn write_plist_ref( writer: &mut PosWriter, ref_size: u8, value: usize, ) -> Result<(), Error> { match ref_size { 1 => writer.write_exact(&[value as u8]), 2 => writer.write_exact(&(value as u16).to_be_bytes()), 4 => writer.write_exact(&(value as u32).to_be_bytes()), 8 => writer.write_exact(&(value as u64).to_be_bytes()), _ => unreachable!("`ref_size` is a power of two less than or equal to 8"), } } impl PosWriter { fn write_exact(&mut self, buf: &[u8]) -> Result<(), Error> { self.write_all(buf) .map_err(error::from_io_without_position)?; Ok(()) } } impl Write for PosWriter { fn write(&mut self, buf: &[u8]) -> io::Result { let count = self.writer.write(buf)?; self.pos = self .pos .checked_add(count) .expect("binary plist cannot be larger than `usize::max_value()` bytes"); Ok(count) } fn flush(&mut self) -> io::Result<()> { self.writer.flush() } } impl ObjectRef { fn zero() -> ObjectRef { ObjectRef(NonZeroUsize::new(1).unwrap()) } fn clone_and_increment_self(&mut self) -> ObjectRef { let current = self.0; self.0 = NonZeroUsize::new(current.get() + 1).unwrap(); ObjectRef(current) } fn value(&self) -> usize { self.0.get() - 1 } } impl<'a> Value<'a> { fn into_owned(self) -> Value<'static> { match self { Value::Boolean(v) => Value::Boolean(v), Value::Data(v) => Value::Data(Cow::Owned(v.into_owned())), Value::Date(v) => Value::Date(v), Value::Integer(v) => Value::Integer(v), Value::Real(v) => Value::Real(v), Value::String(v) => Value::String(Cow::Owned(v.into_owned())), Value::Uid(v) => Value::Uid(v), } } fn event_kind(&self) -> EventKind { match self { Value::Boolean(_) => EventKind::Boolean, Value::Data(_) => EventKind::Data, Value::Date(_) => EventKind::Date, Value::Integer(_) => EventKind::Integer, Value::Real(_) => EventKind::Real, Value::String(_) => EventKind::String, Value::Uid(_) => EventKind::Uid, } } } #[cfg(test)] mod tests { use std::{fs::File, io::Cursor, path::Path}; use crate::{stream::BinaryReader, Value}; fn test_roundtrip>(path: P) { let reader = File::open(path).unwrap(); let streaming_parser = BinaryReader::new(reader); let value_to_encode = Value::from_events(streaming_parser).unwrap(); let mut buf = Cursor::new(Vec::new()); value_to_encode.to_writer_binary(&mut buf).unwrap(); let buf_inner = buf.into_inner(); let streaming_parser = BinaryReader::new(Cursor::new(buf_inner)); let events: Vec> = streaming_parser.collect(); let value_decoded_from_encode = Value::from_events(events.into_iter()).unwrap(); assert_eq!(value_to_encode, value_decoded_from_encode); } #[test] fn bplist_roundtrip() { test_roundtrip("./tests/data/binary.plist") } #[test] fn utf16_roundtrip() { test_roundtrip("./tests/data/utf16_bplist.plist") } #[test] fn nskeyedarchiver_roundtrip() { test_roundtrip("./tests/data/binary_NSKeyedArchiver.plist") } } plist-1.7.0/src/stream/mod.rs000064400000000000000000000354341046102023000142040ustar 00000000000000//! An abstraction of a plist file as a stream of events. Used to support multiple encodings. mod binary_reader; pub use self::binary_reader::BinaryReader; mod binary_writer; pub use self::binary_writer::BinaryWriter; mod xml_reader; pub use self::xml_reader::XmlReader; mod xml_writer; pub use self::xml_writer::XmlWriter; #[cfg(feature = "serde")] pub(crate) use xml_writer::encode_data_base64 as xml_encode_data_base64; mod ascii_reader; pub use self::ascii_reader::AsciiReader; use std::{ borrow::Cow, io::{self, BufReader, Read, Seek}, vec, }; use crate::{ dictionary, error::{Error, ErrorKind}, Date, Integer, Uid, Value, }; /// An encoding of a plist as a flat structure. /// /// Output by the event readers. /// /// Dictionary keys and values are represented as pairs of values e.g.: /// /// ```ignore rust /// StartDictionary /// String("Height") // Key /// Real(181.2) // Value /// String("Age") // Key /// Integer(28) // Value /// EndDictionary /// ``` /// /// ## Lifetimes /// /// This type has a lifetime parameter; during serialization, data is borrowed /// from a [`Value`], and the lifetime of the event is the lifetime of the /// [`Value`] being serialized. /// /// During deserialization, data is always copied anyway, and this lifetime /// is always `'static`. #[derive(Clone, Debug, PartialEq)] #[non_exhaustive] pub enum Event<'a> { // While the length of an array or dict cannot be feasably greater than max(usize) this better // conveys the concept of an effectively unbounded event stream. StartArray(Option), StartDictionary(Option), EndCollection, Boolean(bool), Data(Cow<'a, [u8]>), Date(Date), Integer(Integer), Real(f64), String(Cow<'a, str>), Uid(Uid), } /// An owned [`Event`]. /// /// During deserialization, events are always owned; this type alias helps /// keep that code a bit clearer. pub type OwnedEvent = Event<'static>; /// An `Event` stream returned by `Value::into_events`. pub struct Events<'a> { stack: Vec>, } enum StackItem<'a> { Root(&'a Value), Array(std::slice::Iter<'a, Value>), Dict(dictionary::Iter<'a>), DictValue(&'a Value), } /// Options for customizing serialization of XML plists. #[derive(Clone, Debug)] pub struct XmlWriteOptions { root_element: bool, indent_char: u8, indent_count: usize, } impl XmlWriteOptions { /// Specify the sequence of characters used for indentation. /// /// This may be either an `&'static str` or an owned `String`. /// /// The default is `\t`. /// /// Since replacing `xml-rs` with `quick-xml`, the indent string has to consist of a single /// repeating ascii character. This is a backwards compatibility function, prefer using /// [`XmlWriteOptions::indent`]. #[deprecated(since = "1.4.0", note = "please use `indent` instead")] pub fn indent_string(self, indent_str: impl Into>) -> Self { let indent_str = indent_str.into(); let indent_str = indent_str.as_ref(); if indent_str.is_empty() { return self.indent(0, 0); } assert!( indent_str.chars().all(|chr| chr.is_ascii()), "indent str must be ascii" ); let indent_str = indent_str.as_bytes(); assert!( indent_str.iter().all(|chr| chr == &indent_str[0]), "indent str must consist of a single repeating character" ); self.indent(indent_str[0], indent_str.len()) } /// Specifies the character and amount used for indentation. /// /// `indent_char` must be a valid UTF8 character. /// /// The default is indenting with a single tab. pub fn indent(mut self, indent_char: u8, indent_count: usize) -> Self { self.indent_char = indent_char; self.indent_count = indent_count; self } /// Selects whether to write the XML prologue, plist document type and root element. /// /// In other words the following: /// ```xml /// /// /// /// ... /// /// ``` /// /// The default is `true`. pub fn root_element(mut self, write_root: bool) -> Self { self.root_element = write_root; self } } impl Default for XmlWriteOptions { fn default() -> Self { XmlWriteOptions { indent_char: b'\t', indent_count: 1, root_element: true, } } } impl<'a> Events<'a> { pub(crate) fn new(value: &'a Value) -> Events<'a> { Events { stack: vec![StackItem::Root(value)], } } } impl<'a> Iterator for Events<'a> { type Item = Event<'a>; fn next(&mut self) -> Option> { fn handle_value<'c, 'b: 'c>( value: &'b Value, stack: &'c mut Vec>, ) -> Event<'b> { match value { Value::Array(array) => { let len = array.len(); let iter = array.iter(); stack.push(StackItem::Array(iter)); Event::StartArray(Some(len as u64)) } Value::Dictionary(dict) => { let len = dict.len(); let iter = dict.into_iter(); stack.push(StackItem::Dict(iter)); Event::StartDictionary(Some(len as u64)) } Value::Boolean(value) => Event::Boolean(*value), Value::Data(value) => Event::Data(Cow::Borrowed(value)), Value::Date(value) => Event::Date(*value), Value::Real(value) => Event::Real(*value), Value::Integer(value) => Event::Integer(*value), Value::String(value) => Event::String(Cow::Borrowed(value.as_str())), Value::Uid(value) => Event::Uid(*value), } } Some(match self.stack.pop()? { StackItem::Root(value) => handle_value(value, &mut self.stack), StackItem::Array(mut array) => { if let Some(value) = array.next() { // There might still be more items in the array so return it to the stack. self.stack.push(StackItem::Array(array)); handle_value(value, &mut self.stack) } else { Event::EndCollection } } StackItem::Dict(mut dict) => { if let Some((key, value)) = dict.next() { // There might still be more items in the dictionary so return it to the stack. self.stack.push(StackItem::Dict(dict)); // The next event to be returned must be the dictionary value. self.stack.push(StackItem::DictValue(value)); // Return the key event now. Event::String(Cow::Borrowed(key)) } else { Event::EndCollection } } StackItem::DictValue(value) => handle_value(value, &mut self.stack), }) } } pub struct Reader(ReaderInner); enum ReaderInner { Uninitialized(Option), Binary(BinaryReader), Xml(XmlReader>), Ascii(AsciiReader>), } impl Reader { pub fn new(reader: R) -> Reader { Reader(ReaderInner::Uninitialized(Some(reader))) } fn init(&mut self, mut reader: R) -> Result, Error> { // Rewind reader back to the start. if let Err(err) = reader.rewind().map_err(from_io_offset_0) { self.0 = ReaderInner::Uninitialized(Some(reader)); return Err(err); } // A plist is binary if it starts with magic bytes. match Reader::is_binary(&mut reader) { Ok(true) => { self.0 = ReaderInner::Binary(BinaryReader::new(reader)); return self.next().transpose(); } Ok(false) => (), Err(err) => { self.0 = ReaderInner::Uninitialized(Some(reader)); return Err(err); } }; // If a plist is not binary, try to parse as XML. // Use a `BufReader` for XML and ASCII plists as it is required by `quick-xml` and will // definitely speed up ASCII parsing as well. let mut xml_reader = XmlReader::new(BufReader::new(reader)); let mut reader = match xml_reader.next() { res @ Some(Ok(_)) | res @ None => { self.0 = ReaderInner::Xml(xml_reader); return res.transpose(); } Some(Err(err)) if xml_reader.xml_doc_started() => { self.0 = ReaderInner::Uninitialized(Some(xml_reader.into_inner().into_inner())); return Err(err); } Some(Err(_)) => xml_reader.into_inner(), }; // Rewind reader back to the start. if let Err(err) = reader.rewind().map_err(from_io_offset_0) { self.0 = ReaderInner::Uninitialized(Some(reader.into_inner())); return Err(err); } // If no valid XML markup is found, try to parse as ASCII. let mut ascii_reader = AsciiReader::new(reader); match ascii_reader.next() { res @ Some(Ok(_)) | res @ None => { self.0 = ReaderInner::Ascii(ascii_reader); res.transpose() } Some(Err(err)) => { self.0 = ReaderInner::Uninitialized(Some(ascii_reader.into_inner().into_inner())); Err(err) } } } fn is_binary(reader: &mut R) -> Result { let mut magic = [0; 8]; reader.read_exact(&mut magic).map_err(from_io_offset_0)?; reader.rewind().map_err(from_io_offset_0)?; Ok(&magic == b"bplist00") } } impl Iterator for Reader { type Item = Result; fn next(&mut self) -> Option> { match self.0 { ReaderInner::Xml(ref mut parser) => parser.next(), ReaderInner::Binary(ref mut parser) => parser.next(), ReaderInner::Ascii(ref mut parser) => parser.next(), ReaderInner::Uninitialized(ref mut reader) => { let reader = reader.take().unwrap(); self.init(reader).transpose() } } } } fn from_io_offset_0(err: io::Error) -> Error { ErrorKind::Io(err).with_byte_offset(0) } /// Supports writing event streams in different plist encodings. pub trait Writer: private::Sealed { fn write(&mut self, event: Event) -> Result<(), Error> { match event { Event::StartArray(len) => self.write_start_array(len), Event::StartDictionary(len) => self.write_start_dictionary(len), Event::EndCollection => self.write_end_collection(), Event::Boolean(value) => self.write_boolean(value), Event::Data(value) => self.write_data(value), Event::Date(value) => self.write_date(value), Event::Integer(value) => self.write_integer(value), Event::Real(value) => self.write_real(value), Event::String(value) => self.write_string(value), Event::Uid(value) => self.write_uid(value), } } fn write_start_array(&mut self, len: Option) -> Result<(), Error>; fn write_start_dictionary(&mut self, len: Option) -> Result<(), Error>; fn write_end_collection(&mut self) -> Result<(), Error>; fn write_boolean(&mut self, value: bool) -> Result<(), Error>; fn write_data(&mut self, value: Cow<[u8]>) -> Result<(), Error>; fn write_date(&mut self, value: Date) -> Result<(), Error>; fn write_integer(&mut self, value: Integer) -> Result<(), Error>; fn write_real(&mut self, value: f64) -> Result<(), Error>; fn write_string(&mut self, value: Cow) -> Result<(), Error>; fn write_uid(&mut self, value: Uid) -> Result<(), Error>; } pub(crate) mod private { use std::io::Write; pub trait Sealed {} impl Sealed for super::BinaryWriter {} impl Sealed for super::XmlWriter {} } #[cfg(test)] mod tests { use std::fs::File; use super::{Event::*, *}; const ANIMALS_PLIST_EVENTS: &[Event] = &[ StartDictionary(None), String(Cow::Borrowed("AnimalColors")), StartDictionary(None), String(Cow::Borrowed("lamb")), // key String(Cow::Borrowed("black")), String(Cow::Borrowed("pig")), // key String(Cow::Borrowed("pink")), String(Cow::Borrowed("worm")), // key String(Cow::Borrowed("pink")), EndCollection, String(Cow::Borrowed("AnimalSmells")), StartDictionary(None), String(Cow::Borrowed("lamb")), // key String(Cow::Borrowed("lambish")), String(Cow::Borrowed("pig")), // key String(Cow::Borrowed("piggish")), String(Cow::Borrowed("worm")), // key String(Cow::Borrowed("wormy")), EndCollection, String(Cow::Borrowed("AnimalSounds")), StartDictionary(None), String(Cow::Borrowed("Lisa")), // key String(Cow::Borrowed("Why is the worm talking like a lamb?")), String(Cow::Borrowed("lamb")), // key String(Cow::Borrowed("baa")), String(Cow::Borrowed("pig")), // key String(Cow::Borrowed("oink")), String(Cow::Borrowed("worm")), // key String(Cow::Borrowed("baa")), EndCollection, EndCollection, ]; #[test] fn autodetect_binary() { let reader = File::open("./tests/data/binary.plist").unwrap(); let mut streaming_parser = Reader::new(reader); let events: Result, _> = streaming_parser.by_ref().collect(); assert!(matches!(streaming_parser.0, ReaderInner::Binary(_))); // The contents of this plist are tested for elsewhere. assert!(events.is_ok()); } #[test] fn autodetect_xml() { let reader = File::open("./tests/data/xml-animals.plist").unwrap(); let mut streaming_parser = Reader::new(reader); let events: Result, _> = streaming_parser.by_ref().collect(); assert!(matches!(streaming_parser.0, ReaderInner::Xml(_))); assert_eq!(events.unwrap(), ANIMALS_PLIST_EVENTS); } #[test] fn autodetect_ascii() { let reader = File::open("./tests/data/ascii-animals.plist").unwrap(); let mut streaming_parser = Reader::new(reader); let events: Result, _> = streaming_parser.by_ref().collect(); assert!(matches!(streaming_parser.0, ReaderInner::Ascii(_))); assert_eq!(events.unwrap(), ANIMALS_PLIST_EVENTS); } } plist-1.7.0/src/stream/xml_reader.rs000064400000000000000000000251051046102023000155410ustar 00000000000000use base64::{engine::general_purpose::STANDARD as base64_standard, Engine}; use quick_xml::{events::Event as XmlEvent, Error as XmlReaderError, Reader as EventReader}; use std::io::{self, BufRead}; use crate::{ error::{Error, ErrorKind, FilePosition}, stream::{Event, OwnedEvent}, Date, Integer, }; #[derive(Clone, PartialEq, Eq)] struct ElmName(Box<[u8]>); impl From<&[u8]> for ElmName { fn from(bytes: &[u8]) -> Self { ElmName(Box::from(bytes)) } } impl AsRef<[u8]> for ElmName { fn as_ref(&self) -> &[u8] { &self.0 } } pub struct XmlReader { buffer: Vec, started: bool, finished: bool, state: ReaderState, } struct ReaderState(EventReader); enum ReadResult { XmlDecl, Event(OwnedEvent), Eof, } impl XmlReader { pub fn new(reader: R) -> XmlReader { let mut xml_reader = EventReader::from_reader(reader); let config = xml_reader.config_mut(); config.trim_text(false); config.check_end_names = true; config.expand_empty_elements = true; XmlReader { buffer: Vec::new(), started: false, finished: false, state: ReaderState(xml_reader), } } pub fn into_inner(self) -> R { self.state.0.into_inner() } pub(crate) fn xml_doc_started(&self) -> bool { self.started } } impl From for ErrorKind { fn from(err: XmlReaderError) -> Self { match err { XmlReaderError::Io(err) if err.kind() == io::ErrorKind::UnexpectedEof => { ErrorKind::UnexpectedEof } XmlReaderError::Io(err) => match std::sync::Arc::try_unwrap(err) { Ok(err) => ErrorKind::Io(err), Err(err) => ErrorKind::Io(std::io::Error::from(err.kind())), }, XmlReaderError::Syntax(_) => ErrorKind::UnexpectedEof, XmlReaderError::IllFormed(_) => ErrorKind::InvalidXmlSyntax, XmlReaderError::NonDecodable(_) => ErrorKind::InvalidXmlUtf8, _ => ErrorKind::InvalidXmlSyntax, } } } impl Iterator for XmlReader { type Item = Result; fn next(&mut self) -> Option> { if self.finished { return None; } loop { match self.state.read_next(&mut self.buffer) { Ok(ReadResult::XmlDecl) => { self.started = true; } Ok(ReadResult::Event(event)) => { self.started = true; return Some(Ok(event)); } Ok(ReadResult::Eof) => { self.started = true; self.finished = true; return None; } Err(err) => { self.finished = true; return Some(Err(err)); } } } } } impl ReaderState { fn xml_reader_pos(&self) -> FilePosition { let pos = self.0.buffer_position(); FilePosition(pos as u64) } fn with_pos(&self, kind: ErrorKind) -> Error { kind.with_position(self.xml_reader_pos()) } fn read_xml_event<'buf>(&mut self, buffer: &'buf mut Vec) -> Result, Error> { let event = self.0.read_event_into(buffer); let pos = self.xml_reader_pos(); event.map_err(|err| ErrorKind::from(err).with_position(pos)) } fn read_content(&mut self, buffer: &mut Vec) -> Result { loop { match self.read_xml_event(buffer)? { XmlEvent::Text(text) => { let unescaped = text .unescape() .map_err(|err| self.with_pos(ErrorKind::from(err)))?; return String::from_utf8(unescaped.as_ref().into()) .map_err(|_| self.with_pos(ErrorKind::InvalidUtf8String)); } XmlEvent::End(_) => { return Ok("".to_owned()); } XmlEvent::Eof => return Err(self.with_pos(ErrorKind::UnclosedXmlElement)), XmlEvent::Start(_) => return Err(self.with_pos(ErrorKind::UnexpectedXmlOpeningTag)), XmlEvent::PI(_) | XmlEvent::Empty(_) | XmlEvent::Comment(_) | XmlEvent::CData(_) | XmlEvent::Decl(_) | XmlEvent::DocType(_) => { // skip } } } } fn read_next(&mut self, buffer: &mut Vec) -> Result { loop { match self.read_xml_event(buffer)? { XmlEvent::Decl(_) | XmlEvent::DocType(_) => return Ok(ReadResult::XmlDecl), XmlEvent::Start(name) => { match name.local_name().as_ref() { b"plist" => {} b"array" => return Ok(ReadResult::Event(Event::StartArray(None))), b"dict" => return Ok(ReadResult::Event(Event::StartDictionary(None))), b"key" => { return Ok(ReadResult::Event(Event::String( self.read_content(buffer)?.into(), ))) } b"data" => { let mut encoded = self.read_content(buffer)?; // Strip whitespace and line endings from input string encoded.retain(|c| !c.is_ascii_whitespace()); let data = base64_standard .decode(&encoded) .map_err(|_| self.with_pos(ErrorKind::InvalidDataString))?; return Ok(ReadResult::Event(Event::Data(data.into()))); } b"date" => { let s = self.read_content(buffer)?; let date = Date::from_xml_format(&s) .map_err(|_| self.with_pos(ErrorKind::InvalidDateString))?; return Ok(ReadResult::Event(Event::Date(date))); } b"integer" => { let s = self.read_content(buffer)?; match Integer::from_str(&s) { Ok(i) => return Ok(ReadResult::Event(Event::Integer(i))), Err(_) => { return Err(self.with_pos(ErrorKind::InvalidIntegerString)) } } } b"real" => { let s = self.read_content(buffer)?; match s.parse() { Ok(f) => return Ok(ReadResult::Event(Event::Real(f))), Err(_) => return Err(self.with_pos(ErrorKind::InvalidRealString)), } } b"string" => { return Ok(ReadResult::Event(Event::String( self.read_content(buffer)?.into(), ))) } b"true" => return Ok(ReadResult::Event(Event::Boolean(true))), b"false" => return Ok(ReadResult::Event(Event::Boolean(false))), _ => return Err(self.with_pos(ErrorKind::UnknownXmlElement)), } } XmlEvent::End(name) => match name.local_name().as_ref() { b"array" | b"dict" => return Ok(ReadResult::Event(Event::EndCollection)), _ => (), }, XmlEvent::Eof => return Ok(ReadResult::Eof), XmlEvent::Text(text) => { let unescaped = text .unescape() .map_err(|err| self.with_pos(ErrorKind::from(err)))?; if !unescaped.chars().all(char::is_whitespace) { return Err( self.with_pos(ErrorKind::UnexpectedXmlCharactersExpectedElement) ); } } XmlEvent::PI(_) | XmlEvent::CData(_) | XmlEvent::Comment(_) | XmlEvent::Empty(_) => { // skip } } } } } #[cfg(test)] mod tests { use std::{fs::File, io::BufReader}; use super::*; use crate::stream::Event::*; #[test] fn streaming_parser() { let reader = File::open("./tests/data/xml.plist").unwrap(); let streaming_parser = XmlReader::new(BufReader::new(reader)); let events: Result, _> = streaming_parser.collect(); let comparison = &[ StartDictionary(None), String("Author".into()), String("William Shakespeare".into()), String("Lines".into()), StartArray(None), String("It is a tale told by an idiot, ".into()), String("Full of sound and fury, signifying nothing.".into()), EndCollection, String("Death".into()), Integer(1564.into()), String("Height".into()), Real(1.60), String("Data".into()), Data(vec![0, 0, 0, 190, 0, 0, 0, 3, 0, 0, 0, 30, 0, 0, 0].into()), String("Birthdate".into()), Date(super::Date::from_xml_format("1981-05-16T11:32:06Z").unwrap()), String("Blank".into()), String("".into()), String("BiggestNumber".into()), Integer(18446744073709551615u64.into()), String("SmallestNumber".into()), Integer((-9223372036854775808i64).into()), String("HexademicalNumber".into()), Integer(0xdead_beef_u64.into()), String("IsTrue".into()), Boolean(true), String("IsNotFalse".into()), Boolean(false), EndCollection, ]; assert_eq!(events.unwrap(), comparison); } #[test] fn bad_data() { let reader = File::open("./tests/data/xml_error.plist").unwrap(); let streaming_parser = XmlReader::new(BufReader::new(reader)); let events: Vec<_> = streaming_parser.collect(); assert!(events.last().unwrap().is_err()); } } plist-1.7.0/src/stream/xml_writer.rs000064400000000000000000000372601046102023000156200ustar 00000000000000use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine}; use quick_xml::{ events::{BytesEnd, BytesStart, BytesText, Event as XmlEvent}, Error as XmlWriterError, Writer as EventWriter, }; use std::{ borrow::Cow, io::{self, Write}, }; use crate::{ error::{self, from_io_without_position, Error, ErrorKind, EventKind}, stream::{Writer, XmlWriteOptions}, Date, Integer, Uid, }; const DATA_MAX_LINE_CHARS: usize = 68; const DATA_MAX_LINE_BYTES: usize = 51; static XML_PROLOGUE: &[u8] = br#" "#; #[derive(PartialEq)] enum Element { Dictionary, Array, } pub struct XmlWriter { xml_writer: EventWriter, write_root_element: bool, indent_char: u8, indent_count: usize, started_plist: bool, stack: Vec, expecting_key: bool, pending_collection: Option, } enum PendingCollection { Array, Dictionary, } impl XmlWriter { #[cfg(feature = "enable_unstable_features_that_may_break_with_minor_version_bumps")] pub fn new(writer: W) -> XmlWriter { let opts = XmlWriteOptions::default(); XmlWriter::new_with_options(writer, &opts) } pub fn new_with_options(writer: W, opts: &XmlWriteOptions) -> XmlWriter { let xml_writer = if opts.indent_count == 0 { EventWriter::new(writer) } else { EventWriter::new_with_indent(writer, opts.indent_char, opts.indent_count) }; XmlWriter { xml_writer, write_root_element: opts.root_element, indent_char: opts.indent_char, indent_count: opts.indent_count, started_plist: false, stack: Vec::new(), expecting_key: false, pending_collection: None, } } #[cfg(feature = "enable_unstable_features_that_may_break_with_minor_version_bumps")] pub fn into_inner(self) -> W { self.xml_writer.into_inner() } fn write_element_and_value(&mut self, name: &str, value: &str) -> Result<(), Error> { self.xml_writer .create_element(name) .write_text_content(BytesText::new(value))?; Ok(()) } fn start_element(&mut self, name: &str) -> Result<(), Error> { self.xml_writer .write_event(XmlEvent::Start(BytesStart::new(name)))?; Ok(()) } fn end_element(&mut self, name: &str) -> Result<(), Error> { self.xml_writer .write_event(XmlEvent::End(BytesEnd::new(name)))?; Ok(()) } fn write_event Result<(), Error>>( &mut self, f: F, ) -> Result<(), Error> { if !self.started_plist { if self.write_root_element { self.xml_writer .get_mut() .write_all(XML_PROLOGUE) .map_err(error::from_io_without_position)?; } self.started_plist = true; } f(self)?; // If there are no more open tags then write the element if self.stack.is_empty() { if self.write_root_element { // We didn't tell the xml_writer about the tag so we'll skip telling it // about the tag as well. self.xml_writer .get_mut() .write_all(b"\n") .map_err(error::from_io_without_position)?; } self.xml_writer .get_mut() .flush() .map_err(error::from_io_without_position)?; } Ok(()) } fn write_value_event Result<(), Error>>( &mut self, event_kind: EventKind, f: F, ) -> Result<(), Error> { self.handle_pending_collection()?; self.write_event(|this| { if this.expecting_key { return Err(ErrorKind::UnexpectedEventType { expected: EventKind::DictionaryKeyOrEndCollection, found: event_kind, } .without_position()); } f(this)?; this.expecting_key = this.stack.last() == Some(&Element::Dictionary); Ok(()) }) } fn handle_pending_collection(&mut self) -> Result<(), Error> { if let Some(PendingCollection::Array) = self.pending_collection { self.pending_collection = None; self.write_value_event(EventKind::StartArray, |this| { this.start_element("array")?; this.stack.push(Element::Array); Ok(()) }) } else if let Some(PendingCollection::Dictionary) = self.pending_collection { self.pending_collection = None; self.write_value_event(EventKind::StartDictionary, |this| { this.start_element("dict")?; this.stack.push(Element::Dictionary); this.expecting_key = true; Ok(()) }) } else { Ok(()) } } } impl Writer for XmlWriter { fn write_start_array(&mut self, _len: Option) -> Result<(), Error> { self.handle_pending_collection()?; self.pending_collection = Some(PendingCollection::Array); Ok(()) } fn write_start_dictionary(&mut self, _len: Option) -> Result<(), Error> { self.handle_pending_collection()?; self.pending_collection = Some(PendingCollection::Dictionary); Ok(()) } fn write_end_collection(&mut self) -> Result<(), Error> { self.write_event(|this| { match this.pending_collection.take() { Some(PendingCollection::Array) => { this.xml_writer .write_event(XmlEvent::Empty(BytesStart::new("array")))?; this.expecting_key = this.stack.last() == Some(&Element::Dictionary); return Ok(()); } Some(PendingCollection::Dictionary) => { this.xml_writer .write_event(XmlEvent::Empty(BytesStart::new("dict")))?; this.expecting_key = this.stack.last() == Some(&Element::Dictionary); return Ok(()); } _ => {} }; match (this.stack.pop(), this.expecting_key) { (Some(Element::Dictionary), true) => { this.end_element("dict")?; } (Some(Element::Array), _) => { this.end_element("array")?; } (Some(Element::Dictionary), false) | (None, _) => { return Err(ErrorKind::UnexpectedEventType { expected: EventKind::ValueOrStartCollection, found: EventKind::EndCollection, } .without_position()); } } this.expecting_key = this.stack.last() == Some(&Element::Dictionary); Ok(()) }) } fn write_boolean(&mut self, value: bool) -> Result<(), Error> { self.write_value_event(EventKind::Boolean, |this| { let value = if value { "true" } else { "false" }; Ok(this .xml_writer .write_event(XmlEvent::Empty(BytesStart::new(value)))?) }) } fn write_data(&mut self, value: Cow<[u8]>) -> Result<(), Error> { self.write_value_event(EventKind::Data, |this| { this.xml_writer .create_element("data") .write_inner_content(|xml_writer| { write_data_base64( &value, true, this.indent_char, this.stack.len() * this.indent_count, xml_writer.get_mut(), ) .map_err(from_io_without_position) }) .map(|_| ()) }) } fn write_date(&mut self, value: Date) -> Result<(), Error> { self.write_value_event(EventKind::Date, |this| { this.write_element_and_value("date", &value.to_xml_format()) }) } fn write_integer(&mut self, value: Integer) -> Result<(), Error> { self.write_value_event(EventKind::Integer, |this| { this.write_element_and_value("integer", &value.to_string()) }) } fn write_real(&mut self, value: f64) -> Result<(), Error> { self.write_value_event(EventKind::Real, |this| { this.write_element_and_value("real", &value.to_string()) }) } fn write_string(&mut self, value: Cow) -> Result<(), Error> { self.handle_pending_collection()?; self.write_event(|this| { if this.expecting_key { this.write_element_and_value("key", &value)?; this.expecting_key = false; } else { this.write_element_and_value("string", &value)?; this.expecting_key = this.stack.last() == Some(&Element::Dictionary); } Ok(()) }) } fn write_uid(&mut self, _value: Uid) -> Result<(), Error> { Err(ErrorKind::UidNotSupportedInXmlPlist.without_position()) } } impl From for Error { fn from(err: XmlWriterError) -> Self { match err { XmlWriterError::Io(err) => match std::sync::Arc::try_unwrap(err) { Ok(err) => ErrorKind::Io(err), Err(err) => ErrorKind::Io(std::io::Error::from(err.kind())), } .without_position(), _ => unreachable!(), } } } #[cfg(feature = "serde")] pub(crate) fn encode_data_base64(data: &[u8]) -> String { // Pre-allocate space for the base64 encoded data. let num_lines = (data.len() + DATA_MAX_LINE_BYTES - 1) / DATA_MAX_LINE_BYTES; let max_len = num_lines * (DATA_MAX_LINE_CHARS + 1); let mut base64 = Vec::with_capacity(max_len); write_data_base64(data, false, b'\t', 0, &mut base64).expect("writing to a vec cannot fail"); String::from_utf8(base64).expect("encoded base64 is ascii") } fn write_data_base64( data: &[u8], write_initial_newline: bool, indent_char: u8, indent_repeat: usize, mut writer: impl Write, ) -> io::Result<()> { // XML plist data elements are always formatted by apple tools as // // AAAA..AA (68 characters per line) // let mut encoded = [0; DATA_MAX_LINE_CHARS]; for (i, line) in data.chunks(DATA_MAX_LINE_BYTES).enumerate() { // Write newline if write_initial_newline || i > 0 { writer.write_all(&[b'\n'])?; } // Write indent for _ in 0..indent_repeat { writer.write_all(&[indent_char])?; } // Write bytes let encoded_len = BASE64_STANDARD .encode_slice(line, &mut encoded) .expect("encoded base64 max line length is known"); writer.write_all(&encoded[..encoded_len])?; } Ok(()) } #[cfg(test)] mod tests { use std::io::Cursor; use super::*; use crate::stream::Event; #[test] fn streaming_parser() { let plist = [ Event::StartDictionary(None), Event::String("Author".into()), Event::String("William Shakespeare".into()), Event::String("Lines".into()), Event::StartArray(None), Event::String("It is a tale told by an idiot,".into()), Event::String("Full of sound and fury, signifying nothing.".into()), Event::Data((0..128).collect::>().into()), Event::EndCollection, Event::String("Death".into()), Event::Integer(1564.into()), Event::String("Height".into()), Event::Real(1.60), Event::String("Data".into()), Event::Data(vec![0, 0, 0, 190, 0, 0, 0, 3, 0, 0, 0, 30, 0, 0, 0].into()), Event::String("Birthdate".into()), Event::Date(super::Date::from_xml_format("1981-05-16T11:32:06Z").unwrap()), Event::String("Comment".into()), Event::String("2 < 3".into()), // make sure characters are escaped Event::String("BiggestNumber".into()), Event::Integer(18446744073709551615u64.into()), Event::String("SmallestNumber".into()), Event::Integer((-9223372036854775808i64).into()), Event::String("IsTrue".into()), Event::Boolean(true), Event::String("IsNotFalse".into()), Event::Boolean(false), Event::EndCollection, ]; let expected = " \tAuthor \tWilliam Shakespeare \tLines \t \t\tIt is a tale told by an idiot, \t\tFull of sound and fury, signifying nothing. \t\t \t\tAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEy \t\tMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2Rl \t\tZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8= \t\t \t \tDeath \t1564 \tHeight \t1.6 \tData \t \tAAAAvgAAAAMAAAAeAAAA \t \tBirthdate \t1981-05-16T11:32:06Z \tComment \t2 < 3 \tBiggestNumber \t18446744073709551615 \tSmallestNumber \t-9223372036854775808 \tIsTrue \t \tIsNotFalse \t "; let actual = events_to_xml(plist, XmlWriteOptions::default()); assert_eq!(actual, expected); } #[test] fn custom_indent_string() { let plist = [ Event::StartArray(None), Event::String("It is a tale told by an idiot,".into()), Event::String("Full of sound and fury, signifying nothing.".into()), Event::EndCollection, ]; let expected = " ...It is a tale told by an idiot, ...Full of sound and fury, signifying nothing. "; let actual = events_to_xml(plist, XmlWriteOptions::default().indent(b'.', 3)); assert_eq!(actual, expected); } #[test] fn no_root() { let plist = [ Event::StartArray(None), Event::String("It is a tale told by an idiot,".into()), Event::String("Full of sound and fury, signifying nothing.".into()), Event::EndCollection, ]; let expected = " \tIt is a tale told by an idiot, \tFull of sound and fury, signifying nothing. "; let actual = events_to_xml(plist, XmlWriteOptions::default().root_element(false)); assert_eq!(actual, expected); } fn events_to_xml<'event>( events: impl IntoIterator>, options: XmlWriteOptions, ) -> String { let mut cursor = Cursor::new(Vec::new()); let mut writer = XmlWriter::new_with_options(&mut cursor, &options); for event in events { writer.write(event).unwrap(); } String::from_utf8(cursor.into_inner()).unwrap() } } plist-1.7.0/src/uid.rs000064400000000000000000000045011046102023000127020ustar 00000000000000use std::fmt; /// A plist `uid` value. These are found exclusively in plists created by `NSKeyedArchiver`. #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub struct Uid { value: u64, } impl Uid { /// Creates a new `Uid` containing the given value. pub fn new(value: u64) -> Uid { Uid { value } } /// Returns the value as a `u64`. pub fn get(self) -> u64 { self.value } } impl fmt::Debug for Uid { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { self.value.fmt(f) } } #[cfg(feature = "serde")] pub mod serde_impls { use serde::{ de::{Deserialize, Deserializer, Error, Visitor}, ser::{Serialize, Serializer}, }; use std::fmt; use crate::Uid; pub const UID_NEWTYPE_STRUCT_NAME: &str = "PLIST-UID"; impl Serialize for Uid { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_newtype_struct(UID_NEWTYPE_STRUCT_NAME, &self.get()) } } struct UidNewtypeVisitor; impl<'de> Visitor<'de> for UidNewtypeVisitor { type Value = Uid; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a plist uid") } fn visit_u64(self, v: u64) -> Result where E: Error, { UidU64Visitor.visit_u64(v) } fn visit_newtype_struct(self, deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_u64(UidU64Visitor) } } struct UidU64Visitor; impl<'de> Visitor<'de> for UidU64Visitor { type Value = Uid; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a plist uid") } fn visit_u64(self, v: u64) -> Result where E: Error, { Ok(Uid::new(v)) } } impl<'de> Deserialize<'de> for Uid { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_newtype_struct(UID_NEWTYPE_STRUCT_NAME, UidNewtypeVisitor) } } } plist-1.7.0/src/value.rs000064400000000000000000000632531046102023000132460ustar 00000000000000use std::{ borrow::Cow, fs::File, io::{BufReader, BufWriter, Read, Seek, Write}, path::Path, }; use crate::{ error::{self, Error, ErrorKind, EventKind}, stream::{ private, AsciiReader, BinaryWriter, Event, Events, Reader, Writer, XmlReader, XmlWriteOptions, XmlWriter, }, u64_to_usize, Date, Dictionary, Integer, Uid, }; /// Represents any plist value. #[derive(Clone, Debug, PartialEq)] #[non_exhaustive] pub enum Value { Array(Vec), Dictionary(Dictionary), Boolean(bool), Data(Vec), Date(Date), Real(f64), Integer(Integer), String(String), Uid(Uid), } impl Value { /// Reads a `Value` from a plist file of any encoding. pub fn from_file>(path: P) -> Result { let file = File::open(path).map_err(error::from_io_without_position)?; Value::from_reader(BufReader::new(file)) } /// Reads a `Value` from a seekable byte stream containing a plist of any encoding. pub fn from_reader(reader: R) -> Result { let reader = Reader::new(reader); Value::from_events(reader) } /// Reads a `Value` from a byte stream containing an ASCII encoded plist. pub fn from_reader_ascii(reader: R) -> Result { let reader = AsciiReader::new(reader); Value::from_events(reader) } /// Reads a `Value` from a byte stream containing an XML encoded plist. pub fn from_reader_xml(reader: R) -> Result { let reader = XmlReader::new(BufReader::new(reader)); Value::from_events(reader) } /// Serializes a `Value` to a file as a binary encoded plist. pub fn to_file_binary>(&self, path: P) -> Result<(), Error> { let mut file = File::create(path).map_err(error::from_io_without_position)?; self.to_writer_binary(BufWriter::new(&mut file))?; file.sync_all().map_err(error::from_io_without_position)?; Ok(()) } /// Serializes a `Value` to a file as an XML encoded plist. pub fn to_file_xml>(&self, path: P) -> Result<(), Error> { let mut file = File::create(path).map_err(error::from_io_without_position)?; self.to_writer_xml(BufWriter::new(&mut file))?; file.sync_all().map_err(error::from_io_without_position)?; Ok(()) } /// Serializes a `Value` to a byte stream as a binary encoded plist. pub fn to_writer_binary(&self, writer: W) -> Result<(), Error> { let mut writer = BinaryWriter::new(writer); self.to_writer_inner(&mut writer) } /// Serializes a `Value` to a byte stream as an XML encoded plist. pub fn to_writer_xml(&self, writer: W) -> Result<(), Error> { self.to_writer_xml_with_options(writer, &XmlWriteOptions::default()) } /// Serializes a `Value` to a stream, using custom [`XmlWriteOptions`]. /// /// If you need to serialize to a file, you must acquire an appropriate /// `Write` handle yourself. /// /// # Examples /// /// ```no_run /// use std::io::{BufWriter, Write}; /// use std::fs::File; /// use plist::{Dictionary, Value, XmlWriteOptions}; /// /// let value: Value = Dictionary::new().into(); /// // .. add some keys & values /// let mut file = File::create("com.example.myPlist.plist").unwrap(); /// let options = XmlWriteOptions::default().indent_string(" "); /// value.to_writer_xml_with_options(BufWriter::new(&mut file), &options).unwrap(); /// file.sync_all().unwrap(); /// ``` pub fn to_writer_xml_with_options( &self, writer: W, options: &XmlWriteOptions, ) -> Result<(), Error> { let mut writer = XmlWriter::new_with_options(writer, options); self.to_writer_inner(&mut writer) } fn to_writer_inner(&self, writer: &mut dyn Writer) -> Result<(), Error> { let events = self.events(); for event in events { writer.write(event)?; } Ok(()) } /// Builds a single `Value` from an `Event` iterator. /// On success any excess `Event`s will remain in the iterator. #[cfg(feature = "enable_unstable_features_that_may_break_with_minor_version_bumps")] pub fn from_events<'event, T>(events: T) -> Result where T: IntoIterator, Error>>, { Builder::build(events.into_iter()) } /// Builds a single `Value` from an `Event` iterator. /// On success any excess `Event`s will remain in the iterator. #[cfg(not(feature = "enable_unstable_features_that_may_break_with_minor_version_bumps"))] pub(crate) fn from_events<'event, T>(events: T) -> Result where T: IntoIterator, Error>>, { Builder::build(events.into_iter()) } /// Converts a `Value` into an `Event` iterator. #[cfg(feature = "enable_unstable_features_that_may_break_with_minor_version_bumps")] #[doc(hidden)] #[deprecated(since = "1.2.0", note = "use Value::events instead")] pub fn into_events(&self) -> Events { self.events() } /// Creates an `Event` iterator for this `Value`. #[cfg(not(feature = "enable_unstable_features_that_may_break_with_minor_version_bumps"))] pub(crate) fn events(&self) -> Events { Events::new(self) } /// Creates an `Event` iterator for this `Value`. #[cfg(feature = "enable_unstable_features_that_may_break_with_minor_version_bumps")] pub fn events(&self) -> Events { Events::new(self) } /// If the `Value` is a Array, returns the underlying `Vec`. /// /// Returns `None` otherwise. /// /// This method consumes the `Value`. To get a reference instead, use /// `as_array`. pub fn into_array(self) -> Option> { match self { Value::Array(dict) => Some(dict), _ => None, } } /// If the `Value` is an Array, returns the associated `Vec`. /// /// Returns `None` otherwise. pub fn as_array(&self) -> Option<&Vec> { match *self { Value::Array(ref array) => Some(array), _ => None, } } /// If the `Value` is an Array, returns the associated mutable `Vec`. /// /// Returns `None` otherwise. pub fn as_array_mut(&mut self) -> Option<&mut Vec> { match *self { Value::Array(ref mut array) => Some(array), _ => None, } } /// If the `Value` is a Dictionary, returns the associated `BTreeMap`. /// /// Returns `None` otherwise. /// /// This method consumes the `Value`. To get a reference instead, use /// `as_dictionary`. pub fn into_dictionary(self) -> Option { match self { Value::Dictionary(dict) => Some(dict), _ => None, } } /// If the `Value` is a Dictionary, returns the associated `BTreeMap`. /// /// Returns `None` otherwise. pub fn as_dictionary(&self) -> Option<&Dictionary> { match *self { Value::Dictionary(ref dict) => Some(dict), _ => None, } } /// If the `Value` is a Dictionary, returns the associated mutable `BTreeMap`. /// /// Returns `None` otherwise. pub fn as_dictionary_mut(&mut self) -> Option<&mut Dictionary> { match *self { Value::Dictionary(ref mut dict) => Some(dict), _ => None, } } /// If the `Value` is a Boolean, returns the associated `bool`. /// /// Returns `None` otherwise. pub fn as_boolean(&self) -> Option { match *self { Value::Boolean(v) => Some(v), _ => None, } } /// If the `Value` is a Data, returns the underlying `Vec`. /// /// Returns `None` otherwise. /// /// This method consumes the `Value`. If this is not desired, please use /// `as_data` method. pub fn into_data(self) -> Option> { match self { Value::Data(data) => Some(data), _ => None, } } /// If the `Value` is a Data, returns the associated `Vec`. /// /// Returns `None` otherwise. pub fn as_data(&self) -> Option<&[u8]> { match *self { Value::Data(ref data) => Some(data), _ => None, } } /// If the `Value` is a Date, returns the associated `Date`. /// /// Returns `None` otherwise. pub fn as_date(&self) -> Option { match *self { Value::Date(date) => Some(date), _ => None, } } /// If the `Value` is a Real, returns the associated `f64`. /// /// Returns `None` otherwise. pub fn as_real(&self) -> Option { match *self { Value::Real(v) => Some(v), _ => None, } } /// If the `Value` is a signed Integer, returns the associated `i64`. /// /// Returns `None` otherwise. pub fn as_signed_integer(&self) -> Option { match *self { Value::Integer(v) => v.as_signed(), _ => None, } } /// If the `Value` is an unsigned Integer, returns the associated `u64`. /// /// Returns `None` otherwise. pub fn as_unsigned_integer(&self) -> Option { match *self { Value::Integer(v) => v.as_unsigned(), _ => None, } } /// If the `Value` is a String, returns the underlying `String`. /// /// Returns `None` otherwise. /// /// This method consumes the `Value`. If this is not desired, please use /// `as_string` method. pub fn into_string(self) -> Option { match self { Value::String(v) => Some(v), _ => None, } } /// If the `Value` is a String, returns the associated `str`. /// /// Returns `None` otherwise. pub fn as_string(&self) -> Option<&str> { match *self { Value::String(ref v) => Some(v), _ => None, } } /// If the `Value` is a Uid, returns the underlying `Uid`. /// /// Returns `None` otherwise. /// /// This method consumes the `Value`. If this is not desired, please use /// `as_uid` method. pub fn into_uid(self) -> Option { match self { Value::Uid(u) => Some(u), _ => None, } } /// If the `Value` is a Uid, returns the associated `Uid`. /// /// Returns `None` otherwise. pub fn as_uid(&self) -> Option<&Uid> { match *self { Value::Uid(ref u) => Some(u), _ => None, } } } #[cfg(feature = "serde")] pub mod serde_impls { use serde::{ de, de::{EnumAccess, MapAccess, SeqAccess, VariantAccess, Visitor}, ser, }; use crate::{ date::serde_impls::DATE_NEWTYPE_STRUCT_NAME, uid::serde_impls::UID_NEWTYPE_STRUCT_NAME, Dictionary, Value, }; pub const VALUE_NEWTYPE_STRUCT_NAME: &str = "PLIST-VALUE"; impl ser::Serialize for Value { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { match *self { Value::Array(ref v) => v.serialize(serializer), Value::Dictionary(ref m) => m.serialize(serializer), Value::Boolean(b) => serializer.serialize_bool(b), Value::Data(ref v) => serializer.serialize_bytes(v), Value::Date(d) => d.serialize(serializer), Value::Real(n) => serializer.serialize_f64(n), Value::Integer(n) => n.serialize(serializer), Value::String(ref s) => serializer.serialize_str(s), Value::Uid(ref u) => u.serialize(serializer), } } } impl<'de> de::Deserialize<'de> for Value { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { struct ValueVisitor; impl<'de> Visitor<'de> for ValueVisitor { type Value = Value; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("any supported plist value") } fn visit_bool(self, value: bool) -> Result { Ok(Value::Boolean(value)) } fn visit_byte_buf(self, v: Vec) -> Result { Ok(Value::Data(v)) } fn visit_bytes(self, v: &[u8]) -> Result { Ok(Value::Data(v.to_vec())) } fn visit_i64(self, value: i64) -> Result { Ok(Value::Integer(value.into())) } fn visit_u64(self, value: u64) -> Result { Ok(Value::Integer(value.into())) } fn visit_f64(self, value: f64) -> Result { Ok(Value::Real(value)) } fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { let mut values = Dictionary::new(); while let Some((k, v)) = map.next_entry()? { values.insert(k, v); } Ok(Value::Dictionary(values)) } fn visit_str(self, value: &str) -> Result { Ok(Value::String(value.to_owned())) } fn visit_string(self, value: String) -> Result { Ok(Value::String(value)) } fn visit_newtype_struct(self, deserializer: T) -> Result where T: de::Deserializer<'de>, { deserializer.deserialize_any(self) } fn visit_seq(self, mut seq: A) -> Result where A: SeqAccess<'de>, { let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or(0)); while let Some(elem) = seq.next_element()? { vec.push(elem); } Ok(Value::Array(vec)) } fn visit_enum(self, data: A) -> Result where A: EnumAccess<'de>, { let (name, variant) = data.variant::()?; match &*name { DATE_NEWTYPE_STRUCT_NAME => Ok(Value::Date(variant.newtype_variant()?)), UID_NEWTYPE_STRUCT_NAME => Ok(Value::Uid(variant.newtype_variant()?)), _ => Err(de::Error::unknown_variant( &name, &[DATE_NEWTYPE_STRUCT_NAME, UID_NEWTYPE_STRUCT_NAME], )), } } } // Serde serialisers are encouraged to treat newtype structs as insignificant // wrappers around the data they contain. That means not parsing anything other // than the contained value. Therefore, this should not prevent using `Value` // with other `Serializer`s. deserializer.deserialize_newtype_struct(VALUE_NEWTYPE_STRUCT_NAME, ValueVisitor) } } } impl From> for Value { fn from(from: Vec) -> Value { Value::Array(from) } } impl From for Value { fn from(from: Dictionary) -> Value { Value::Dictionary(from) } } impl From for Value { fn from(from: bool) -> Value { Value::Boolean(from) } } impl<'a> From<&'a bool> for Value { fn from(from: &'a bool) -> Value { Value::Boolean(*from) } } impl From for Value { fn from(from: Date) -> Value { Value::Date(from) } } impl<'a> From<&'a Date> for Value { fn from(from: &'a Date) -> Value { Value::Date(*from) } } impl From for Value { fn from(from: f64) -> Value { Value::Real(from) } } impl From for Value { fn from(from: f32) -> Value { Value::Real(from.into()) } } impl From for Value { fn from(from: i64) -> Value { Value::Integer(Integer::from(from)) } } impl From for Value { fn from(from: i32) -> Value { Value::Integer(Integer::from(from)) } } impl From for Value { fn from(from: i16) -> Value { Value::Integer(Integer::from(from)) } } impl From for Value { fn from(from: i8) -> Value { Value::Integer(Integer::from(from)) } } impl From for Value { fn from(from: u64) -> Value { Value::Integer(Integer::from(from)) } } impl From for Value { fn from(from: u32) -> Value { Value::Integer(Integer::from(from)) } } impl From for Value { fn from(from: u16) -> Value { Value::Integer(Integer::from(from)) } } impl From for Value { fn from(from: u8) -> Value { Value::Integer(Integer::from(from)) } } impl<'a> From<&'a f64> for Value { fn from(from: &'a f64) -> Value { Value::Real(*from) } } impl<'a> From<&'a f32> for Value { fn from(from: &'a f32) -> Value { Value::Real((*from).into()) } } impl<'a> From<&'a i64> for Value { fn from(from: &'a i64) -> Value { Value::Integer(Integer::from(*from)) } } impl<'a> From<&'a i32> for Value { fn from(from: &'a i32) -> Value { Value::Integer(Integer::from(*from)) } } impl<'a> From<&'a i16> for Value { fn from(from: &'a i16) -> Value { Value::Integer(Integer::from(*from)) } } impl<'a> From<&'a i8> for Value { fn from(from: &'a i8) -> Value { Value::Integer(Integer::from(*from)) } } impl<'a> From<&'a u64> for Value { fn from(from: &'a u64) -> Value { Value::Integer(Integer::from(*from)) } } impl<'a> From<&'a u32> for Value { fn from(from: &'a u32) -> Value { Value::Integer(Integer::from(*from)) } } impl<'a> From<&'a u16> for Value { fn from(from: &'a u16) -> Value { Value::Integer((*from).into()) } } impl<'a> From<&'a u8> for Value { fn from(from: &'a u8) -> Value { Value::Integer((*from).into()) } } impl From for Value { fn from(from: String) -> Value { Value::String(from) } } impl<'a> From<&'a str> for Value { fn from(from: &'a str) -> Value { Value::String(from.into()) } } enum StackItem { Root(Value), Array(Vec), Dict(Dictionary), DictAndKey(Dictionary, String), } #[derive(Default)] pub struct Builder { stack: Vec, } impl Builder { fn build<'event, T>(stream: T) -> Result where T: Iterator, Error>>, { let mut builder = Self::default(); for event in stream { builder.write(event?)?; } builder.finish() } fn write_value(&mut self, value: Value) -> Result<(), Error> { match (self.stack.pop(), value) { (None, value) => self.stack.push(StackItem::Root(value)), (Some(StackItem::Root(_)), value) => { return Err(ErrorKind::ExpectedEndOfEventStream { found: EventKind::of_value(&value), } .without_position()) } (Some(StackItem::Array(mut array)), value) => { array.push(value); self.stack.push(StackItem::Array(array)); } (Some(StackItem::Dict(dict)), Value::String(key)) => { self.stack.push(StackItem::DictAndKey(dict, key)) } (Some(StackItem::Dict(_)), value) => { return Err(ErrorKind::UnexpectedEventType { expected: EventKind::DictionaryKeyOrEndCollection, found: EventKind::of_value(&value), } .without_position()) } (Some(StackItem::DictAndKey(mut dict, key)), value) => { dict.insert(key, value); self.stack.push(StackItem::Dict(dict)); } } Ok(()) } pub fn finish(&mut self) -> Result { match self.stack.pop() { Some(StackItem::Root(value)) => Ok(value), _ => Err(ErrorKind::UnexpectedEndOfEventStream.without_position()), } } } impl Writer for Builder { fn write_start_array(&mut self, len: Option) -> Result<(), Error> { let len = len.and_then(u64_to_usize).unwrap_or(0); self.stack.push(StackItem::Array(Vec::with_capacity(len))); Ok(()) } fn write_start_dictionary(&mut self, _: Option) -> Result<(), Error> { self.stack.push(StackItem::Dict(Dictionary::new())); Ok(()) } fn write_end_collection(&mut self) -> Result<(), Error> { let value = match self.stack.pop() { Some(StackItem::Root(_)) => { return Err(ErrorKind::ExpectedEndOfEventStream { found: EventKind::EndCollection, } .without_position()) } Some(StackItem::Array(array)) => Value::Array(array), Some(StackItem::Dict(dict)) => Value::Dictionary(dict), Some(StackItem::DictAndKey(_, _)) | None => { return Err(ErrorKind::UnexpectedEventType { expected: EventKind::ValueOrStartCollection, found: EventKind::EndCollection, } .without_position()) } }; self.write_value(value) } fn write_boolean(&mut self, value: bool) -> Result<(), Error> { self.write_value(Value::Boolean(value)) } fn write_data(&mut self, value: Cow<[u8]>) -> Result<(), Error> { self.write_value(Value::Data(value.into_owned())) } fn write_date(&mut self, value: Date) -> Result<(), Error> { self.write_value(Value::Date(value)) } fn write_integer(&mut self, value: Integer) -> Result<(), Error> { self.write_value(Value::Integer(value)) } fn write_real(&mut self, value: f64) -> Result<(), Error> { self.write_value(Value::Real(value)) } fn write_string(&mut self, value: Cow) -> Result<(), Error> { self.write_value(Value::String(value.into_owned())) } fn write_uid(&mut self, value: Uid) -> Result<(), Error> { self.write_value(Value::Uid(value)) } } impl private::Sealed for Builder {} #[cfg(test)] mod tests { use std::time::SystemTime; use super::*; use crate::{stream::Event::*, Date}; #[test] fn value_accessors() { let vec = vec![Value::Real(0.0)]; let mut array = Value::Array(vec.clone()); assert_eq!(array.as_array(), Some(&vec.clone())); assert_eq!(array.as_array_mut(), Some(&mut vec.clone())); let mut map = Dictionary::new(); map.insert("key1".to_owned(), Value::String("value1".to_owned())); let mut dict = Value::Dictionary(map.clone()); assert_eq!(dict.as_dictionary(), Some(&map.clone())); assert_eq!(dict.as_dictionary_mut(), Some(&mut map.clone())); assert_eq!(Value::Boolean(true).as_boolean(), Some(true)); let slice: &[u8] = &[1, 2, 3]; assert_eq!(Value::Data(slice.to_vec()).as_data(), Some(slice)); assert_eq!( Value::Data(slice.to_vec()).into_data(), Some(slice.to_vec()) ); let date: Date = SystemTime::now().into(); assert_eq!(Value::Date(date.clone()).as_date(), Some(date)); assert_eq!(Value::Real(0.0).as_real(), Some(0.0)); assert_eq!(Value::Integer(1.into()).as_signed_integer(), Some(1)); assert_eq!(Value::Integer(1.into()).as_unsigned_integer(), Some(1)); assert_eq!(Value::Integer((-1).into()).as_unsigned_integer(), None); assert_eq!( Value::Integer((i64::max_value() as u64 + 1).into()).as_signed_integer(), None ); assert_eq!(Value::String("2".to_owned()).as_string(), Some("2")); assert_eq!( Value::String("t".to_owned()).into_string(), Some("t".to_owned()) ); } #[test] fn builder() { // Input let events = vec![ StartDictionary(None), String("Author".into()), String("William Shakespeare".into()), String("Lines".into()), StartArray(None), String("It is a tale told by an idiot,".into()), String("Full of sound and fury, signifying nothing.".into()), EndCollection, String("Birthdate".into()), Integer(1564.into()), String("Height".into()), Real(1.60), EndCollection, ]; let value = Builder::build(events.into_iter().map(|e| Ok(e))); // Expected output let lines = vec![ Value::String("It is a tale told by an idiot,".to_owned()), Value::String("Full of sound and fury, signifying nothing.".to_owned()), ]; let mut dict = Dictionary::new(); dict.insert( "Author".to_owned(), Value::String("William Shakespeare".to_owned()), ); dict.insert("Lines".to_owned(), Value::Array(lines)); dict.insert("Birthdate".to_owned(), Value::Integer(1564.into())); dict.insert("Height".to_owned(), Value::Real(1.60)); assert_eq!(value.unwrap(), Value::Dictionary(dict)); } } plist-1.7.0/tests/data/ascii-animals.plist000064400000000000000000000004071046102023000166270ustar 00000000000000{ AnimalColors = { lamb = black; pig = pink; worm = pink; }; AnimalSmells = { lamb = lambish; pig = piggish; worm = wormy; }; AnimalSounds = { Lisa = "Why is the worm talking like a lamb?"; lamb = baa; pig = oink; worm = baa; }; } plist-1.7.0/tests/data/ascii-sample.plist000064400000000000000000000006041046102023000164630ustar 00000000000000/* Sample from GNUStep: http://wiki.gnustep.org/index.php?title=Property_Lists */ { KeyName1 = /* embedded comment */ Value1; AnotherKeyName = "Value2"; // This should be ignored. Ignored = "Not in here" Something = ( "ArrayItem1", "ArrayItem2", "ArrayItem3" ); Key4 = 0.10; // Line Comment KeyFive = { Dictionary2Key1 = "Something"; AnotherKey = "Somethingelse"; }; } plist-1.7.0/tests/data/binary.plist000064400000000000000000000006531046102023000154040ustar 00000000000000bplist00 VAuthorYBirthdateZEmptyArrayZIsNotFalse^SmallestNumber_EmptyDictionaryVHeightULinesUDeathUBlank]BiggestNumberVIsTrueTData_William Shakespeare3ve#?_#It is a tale told by an idiot, _+Full of sound and fury, signifying nothing.P O#*4?JYkrx~),->?Qplist-1.7.0/tests/data/binary_NSKeyedArchiver.plist000064400000000000000000000007501046102023000204500ustar 00000000000000bplist00 X$versionX$objectsY$archiverT$topU$null \NSRangeCountV$class[NSRangeData* WNS.dataOg ,035<@GLSV[dox|@Z$classnameX$classes]NSMutableDataVNSDataXNSObject_NSMutableIndexSet_NSMutableIndexSetZNSIndexSet_NSKeyedArchiver!"ZfoundItems#-27=CJW^jlnpu}$)=AU`ru#plist-1.7.0/tests/data/binary_circular_array.plist000064400000000000000000000007031046102023000204620ustar 00000000000000bplist00 LinesUDeathVHeightYBirthdateVAuthorTData _It is a tale Tolda ateVAuthorTData _>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>It is a tale Tolda y bn idiot,_+Full of sound and fury, signif sound and fury, signifying nthng.#?6v!eareOm ShakespeareO!(28>Abplist-1.7.0/tests/data/binary_zero_offset_size.plist000064400000000000000000000004241046102023000210370ustar 00000000000000bplist00 ULinesUDeathVHeightYBirthdate signifying nthing.#?#?3ve_William ShakespeareO!(29>Abte signifying nthing.#?#?3ve_William ShakespeareO!(plist-1.7.0/tests/data/book.plist000064400000000000000000000012211046102023000150420ustar 00000000000000 Title Great Expectations Author Charles Dickens Excerpt Whether I should have made out this object so soon, if there had been no fine lady sitting at it, I cannot say. In an armchair, with an elbow resting on the table and her head leaning on that hand, sat the strangest lady I have ever seen, or shall ever see. CopiesSold 123456789 plist-1.7.0/tests/data/netnewswire.pbxproj000064400000000000000000012132661046102023000170320ustar 00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 49F40DF82335B71000552BF4 /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; }; 49F40DF92335B71000552BF4 /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; }; 51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */; }; 51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; 5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */; }; 511D43CF231FA62200FB1562 /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; }; 511D43D0231FA62500FB1562 /* TimelineKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */; }; 511D43D1231FA62800FB1562 /* SidebarKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B681FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist */; }; 511D43D2231FA62C00FB1562 /* GlobalKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B641FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist */; }; 511D43EF231FBDE900FB1562 /* LaunchScreenPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 511D43ED231FBDE800FB1562 /* LaunchScreenPad.storyboard */; }; 511D4419231FC02D00FB1562 /* KeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511D4410231FC02D00FB1562 /* KeyboardManager.swift */; }; 51236339236915B100951F16 /* RoundedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512363372369155100951F16 /* RoundedProgressView.swift */; }; 5123DB9F233EC6FD00282CC9 /* FeedInspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5123DB9E233EC6FD00282CC9 /* FeedInspectorView.swift */; }; 5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5126EE96226CB48A00C22AFC /* SceneCoordinator.swift */; }; 5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */; }; 5127B23A222B4849006D641D /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; }; 512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; 512E08E72268801200BDCFDD /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */; }; 512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512E08F722688F7C00BDCFDD /* MasterFeedTableViewSectionHeader.swift */; }; 512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512E092B2268B25500BDCFDD /* UISplitViewController-Extensions.swift */; }; 512E094D2268B8AB00BDCFDD /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; }; 5131463E235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 51314637235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 51314668235A7E4600387FDC /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51314666235A7E4600387FDC /* IntentHandler.swift */; }; 513146B2235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */; }; 513146B3235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */; }; 513146B4235A8FD000387FDC /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8520DD8CF200CA8CF5 /* RSCore.framework */; }; 513146B6235A8FD000387FDC /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC420DD8E0C00CA8CF5 /* RSDatabase.framework */; }; 513146B8235A8FD000387FDC /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; }; 513146BA235A8FD000387FDC /* RSTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; }; 513146BC235A8FD000387FDC /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FA320DD8D0500CA8CF5 /* RSWeb.framework */; }; 513146BF235A8FDB00387FDC /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407166A2262A60D00344432 /* Account.framework */; }; 513146C1235A8FDB00387FDC /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; }; 513146C3235A8FDB00387FDC /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; 513146C5235A8FDB00387FDC /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; }; 51314704235C41FC00387FDC /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 51314707235C41FC00387FDC /* Intents.intentdefinition */; }; 51314705235C41FC00387FDC /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 51314707235C41FC00387FDC /* Intents.intentdefinition */; }; 513228FB233037630033D4ED /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513228F2233037620033D4ED /* Reachability.swift */; }; 513228FC233037630033D4ED /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513228F2233037620033D4ED /* Reachability.swift */; }; 513C5CE9232571C2003D4054 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513C5CE8232571C2003D4054 /* ShareViewController.swift */; }; 513C5CEC232571C2003D4054 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 513C5CEA232571C2003D4054 /* MainInterface.storyboard */; }; 513C5CF0232571C2003D4054 /* NetNewsWire iOS Share Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 513C5CE6232571C2003D4054 /* NetNewsWire iOS Share Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 513C5CFD2325749A003D4054 /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407166A2262A60D00344432 /* Account.framework */; }; 513C5D00232574AF003D4054 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; }; 513C5D02232574B4003D4054 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; 513C5D04232574B9003D4054 /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8520DD8CF200CA8CF5 /* RSCore.framework */; }; 513C5D06232574C0003D4054 /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC420DD8E0C00CA8CF5 /* RSDatabase.framework */; }; 513C5D08232574C6003D4054 /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; }; 513C5D0A232574D2003D4054 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FA320DD8D0500CA8CF5 /* RSWeb.framework */; }; 513C5D0C232574DA003D4054 /* RSTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; }; 513C5D0E232574E4003D4054 /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; }; 5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5142192923522B5500E07E2C /* ImageViewController.swift */; }; 514219372352510100E07E2C /* ImageScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514219362352510100E07E2C /* ImageScrollView.swift */; }; 5142194B2353C1CF00E07E2C /* main_mac.js in Resources */ = {isa = PBXBuildFile; fileRef = 5142194A2353C1CF00E07E2C /* main_mac.js */; }; 514219582353C28900E07E2C /* main_ios.js in Resources */ = {isa = PBXBuildFile; fileRef = 514219572353C28900E07E2C /* main_ios.js */; }; 5144EA2F2279FAB600D19003 /* AccountsDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA2E2279FAB600D19003 /* AccountsDetailViewController.swift */; }; 5144EA362279FC3D00D19003 /* AccountsAddLocal.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA352279FC3D00D19003 /* AccountsAddLocal.xib */; }; 5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */; }; 5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */; }; 5144EA40227A37EC00D19003 /* ImportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */; }; 5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */; }; 5144EA52227B8E4500D19003 /* AccountsFeedbin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */; }; 5148F44B2336DB4700F8CD8B /* MasterTimelineTitleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */; }; 5148F4552336DB7000F8CD8B /* MasterTimelineTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */; }; 514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */; }; 514B7D1F23219F3C00BAC947 /* AddControllerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */; }; 5154368B229404D1005E1CDF /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; }; 51554C24228B71910055115A /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; }; 51554C25228B71910055115A /* SyncDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51554C30228B71A10055115A /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; }; 51554C31228B71A10055115A /* SyncDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 515D4FC123257A3200EE1167 /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; 515D4FCA23257CB500EE1167 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; }; 515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; }; 516A093723609A3600EAE89B /* SettingsAccountTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 516A091D23609A3600EAE89B /* SettingsAccountTableViewCell.xib */; }; 516A09392360A2AE00EAE89B /* SettingsAccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516A09382360A2AE00EAE89B /* SettingsAccountTableViewCell.swift */; }; 516A093B2360A4A000EAE89B /* SettingsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 516A093A2360A4A000EAE89B /* SettingsTableViewCell.xib */; }; 516A09402361240900EAE89B /* Account.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 516A093F2361240900EAE89B /* Account.storyboard */; }; 516A09422361248000EAE89B /* Inspector.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 516A09412361248000EAE89B /* Inspector.storyboard */; }; 51707439232AA97100A461A3 /* ShareFolderPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */; }; 5170743A232AABFC00A461A3 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */; }; 517630042336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; 517630052336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; 517630232336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */; }; 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */; }; 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */; }; 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 5183CCE6226F4E110010922C /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 5183CCE8226F68D90010922C /* AccountRefreshTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */; }; 5183CCE9226F68D90010922C /* AccountRefreshTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */; }; 518651B223555EB20078E021 /* NNW3Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651AB23555EB20078E021 /* NNW3Document.swift */; }; 518651DA235621840078E021 /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651D9235621840078E021 /* ImageTransition.swift */; }; 5186A635235EF3A800C97195 /* VibrantLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5186A634235EF3A800C97195 /* VibrantLabel.swift */; }; 518B2EE82351B45600400001 /* NetNewsWire_iOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D61952029031D009BC708 /* NetNewsWire_iOSTests.swift */; }; 51934CCB230F599B006127BE /* ThemedNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CC1230F5963006127BE /* ThemedNavigationController.swift */; }; 51934CCE2310792F006127BE /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; }; 51938DF2231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; 519D740623243CC0008BB345 /* RefreshInterval-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; 51A16997235E10D700EB091F /* RefreshIntervalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */; }; 51A16999235E10D700EB091F /* LocalAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */; }; 51A1699A235E10D700EB091F /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51A16990235E10D600EB091F /* Settings.storyboard */; }; 51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16991235E10D600EB091F /* AccountInspectorViewController.swift */; }; 51A1699C235E10D700EB091F /* AddAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16992235E10D600EB091F /* AddAccountViewController.swift */; }; 51A1699D235E10D700EB091F /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16993235E10D600EB091F /* SettingsViewController.swift */; }; 51A1699F235E10D700EB091F /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16995235E10D600EB091F /* AboutViewController.swift */; }; 51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */; }; 51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51AF460D232488C6001742EF /* Account-Extensions.swift */; }; 51B62E68233186730085F949 /* AvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* AvatarView.swift */; }; 51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; }; 51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; }; 51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; 51C451AA226377C200C03939 /* ArticlesDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51C451B9226377C900C03939 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; }; 51C451BA226377C900C03939 /* Articles.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51C451BD226377D000C03939 /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407166A2262A60D00344432 /* Account.framework */; }; 51C451BE226377D000C03939 /* Account.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407166A2262A60D00344432 /* Account.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51C451D22264C7F200C03939 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FA320DD8D0500CA8CF5 /* RSWeb.framework */; }; 51C451D32264C7F200C03939 /* RSWeb.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FA320DD8D0500CA8CF5 /* RSWeb.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51C451E02264C7F900C03939 /* RSTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; }; 51C451E12264C7F900C03939 /* RSTree.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51C451E42264C80600C03939 /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; }; 51C451E52264C80600C03939 /* RSParser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51C451E82264C81000C03939 /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC420DD8E0C00CA8CF5 /* RSDatabase.framework */; }; 51C451E92264C81000C03939 /* RSDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC420DD8E0C00CA8CF5 /* RSDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51C451EC2264C81B00C03939 /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8520DD8CF200CA8CF5 /* RSCore.framework */; }; 51C451ED2264C81B00C03939 /* RSCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8520DD8CF200CA8CF5 /* RSCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51C451F02264C83100C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; 51C451F12264C83100C03939 /* ArticlesDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51C451F42264C83900C03939 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; }; 51C451F52264C83900C03939 /* Articles.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51C451F82264C83E00C03939 /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407166A2262A60D00344432 /* Account.framework */; }; 51C451F92264C83E00C03939 /* Account.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407166A2262A60D00344432 /* Account.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51C45258226508CF00C03939 /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45254226507D200C03939 /* AppAssets.swift */; }; 51C45259226508D300C03939 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45255226507D200C03939 /* AppDefaults.swift */; }; 51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */; }; 51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45250226506F400C03939 /* String-Extensions.swift */; }; 51C45268226508F600C03939 /* MasterFeedUnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45261226508F600C03939 /* MasterFeedUnreadCountView.swift */; }; 51C45269226508F600C03939 /* MasterFeedTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45262226508F600C03939 /* MasterFeedTableViewCell.swift */; }; 51C4526A226508F600C03939 /* MasterFeedTableViewCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45263226508F600C03939 /* MasterFeedTableViewCellLayout.swift */; }; 51C4526B226508F600C03939 /* MasterFeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45264226508F600C03939 /* MasterFeedViewController.swift */; }; 51C452762265091600C03939 /* MasterTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4526E2265091600C03939 /* MasterTimelineViewController.swift */; }; 51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452702265091600C03939 /* MultilineUILabelSizer.swift */; }; 51C452782265091600C03939 /* MasterTimelineCellData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452712265091600C03939 /* MasterTimelineCellData.swift */; }; 51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452722265091600C03939 /* MasterTimelineTableViewCell.swift */; }; 51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452742265091600C03939 /* MasterUnreadIndicatorView.swift */; }; 51C4527C2265091600C03939 /* MasterTimelineDefaultCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452752265091600C03939 /* MasterTimelineDefaultCellLayout.swift */; }; 51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4527E2265092C00C03939 /* ArticleViewController.swift */; }; 51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */; }; 51C452862265093600C03939 /* Add.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51C452822265093600C03939 /* Add.storyboard */; }; 51C452882265093600C03939 /* AddFeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452842265093600C03939 /* AddFeedViewController.swift */; }; 51C4528D2265095F00C03939 /* AddFolderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4528B2265095F00C03939 /* AddFolderViewController.swift */; }; 51C4528E2265099C00C03939 /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; }; 51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */; }; 51C45290226509C100C03939 /* PseudoFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */; }; 51C45291226509C800C03939 /* SmartFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7C01FC2488C00854A1F /* SmartFeed.swift */; }; 51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */; }; 51C45293226509C800C03939 /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; }; 51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; }; 51C45296226509D300C03939 /* OPMLExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8444C8F11FED81840051386C /* OPMLExporter.swift */; }; 51C45297226509E300C03939 /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; }; 51C4529922650A0000C03939 /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; }; 51C4529A22650A0400C03939 /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; }; 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; 51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; }; 51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; 51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; }; 51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; }; 51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; }; 51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; 51C452A322650A1E00C03939 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; }; 51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */; }; 51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */; }; 51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; }; 51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; 51C452A922650DC600C03939 /* ArticleRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */; }; 51C452AB22650DC600C03939 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; 51C452AC22650FD200C03939 /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; }; 51C452AE2265104D00C03939 /* ArticleStringFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */; }; 51C452AF2265108300C03939 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; 51C452B42265141B00C03939 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51C452B32265141B00C03939 /* WebKit.framework */; }; 51C452B82265178500C03939 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 51C452B72265178500C03939 /* styleSheet.css */; }; 51CC9B3E231720B2000E842F /* MasterFeedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CC9B3D231720B2000E842F /* MasterFeedDataSource.swift */; }; 51CE1C0923621EDA005548FC /* RefreshProgressView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51CE1C0823621EDA005548FC /* RefreshProgressView.xib */; }; 51CE1C0B23622007005548FC /* RefreshProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CE1C0A23622006005548FC /* RefreshProgressView.swift */; }; 51CE1C712367721A005548FC /* testURLsOfCurrentArticle.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EADB213660A100CF2DE4 /* testURLsOfCurrentArticle.applescript */; }; 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; 51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */; }; 51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; }; 51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB32229AB02C00645299 /* ErrorHandler.swift */; }; 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB3C229AB08300645299 /* ErrorHandler.swift */; }; 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; 51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; 51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EAED95231363EF00A9EEE3 /* NonIntrinsicButton.swift */; }; 51EC114C2149FE3300B296E3 /* FolderTreeMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */; }; 51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; }; 51EF0F79227716380050506E /* ColorHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F78227716380050506E /* ColorHash.swift */; }; 51EF0F7A22771B890050506E /* ColorHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F78227716380050506E /* ColorHash.swift */; }; 51EF0F7E2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F7D2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift */; }; 51EF0F802277A8330050506E /* MasterTimelineCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F7F2277A8330050506E /* MasterTimelineCellLayout.swift */; }; 51EF0F8E2279C9260050506E /* AccountsAdd.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51EF0F8D2279C9260050506E /* AccountsAdd.xib */; }; 51EF0F902279C9500050506E /* AccountsAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F8F2279C9500050506E /* AccountsAddViewController.swift */; }; 51EF0F922279CA620050506E /* AccountsAddTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F912279CA620050506E /* AccountsAddTableCellView.swift */; }; 51F85BEB22724CB600C787DC /* About.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 51F85BEA22724CB600C787DC /* About.rtf */; }; 51F85BED227251DF00C787DC /* Acknowledgments.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 51F85BEC227251DF00C787DC /* Acknowledgments.rtf */; }; 51F85BEF2272520B00C787DC /* Thanks.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 51F85BEE2272520B00C787DC /* Thanks.rtf */; }; 51F85BF12272524100C787DC /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 51F85BF02272524100C787DC /* Credits.rtf */; }; 51F85BF32272531500C787DC /* Dedication.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 51F85BF22272531500C787DC /* Dedication.rtf */; }; 51F85BF52273625800C787DC /* Bundle-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BF42273625800C787DC /* Bundle-Extensions.swift */; }; 51F85BF722749FA100C787DC /* UIFont-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BF622749FA100C787DC /* UIFont-Extensions.swift */; }; 51F85BF92274AA7B00C787DC /* UIBarButtonItem-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */; }; 51F85BFB2275D85000C787DC /* Array-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BFA2275D85000C787DC /* Array-Extensions.swift */; }; 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BFC2275DCA800C787DC /* SingleLineUILabelSizer.swift */; }; 51FA73A42332BE110090D516 /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; }; 51FA73A52332BE110090D516 /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; }; 51FA73A72332BE880090D516 /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; }; 51FA73A82332BE880090D516 /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; }; 51FA73AA2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */; }; 51FA73AB2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */; }; 51FA73B72332D5F70090D516 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */; }; 51FD40C72341555A00880194 /* UIImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FD40BD2341555600880194 /* UIImage-Extensions.swift */; }; 51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */; }; 51FE10032345529D0056195D /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; }; 51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; }; 51FE10092346739D0056195D /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; }; 51FE100A234673A00056195D /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; }; 51FFF0C4235EE8E5002762AA /* VibrantButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FFF0C3235EE8E5002762AA /* VibrantButton.swift */; }; 55E15BCB229D65A900D6602A /* AccountsReaderAPI.xib in Resources */ = {isa = PBXBuildFile; fileRef = 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */; }; 55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */; }; 5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F323808231DF9F000706F6B /* VibrantTableViewCell.swift */; }; 6581C73820CED60100F4AD34 /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */; }; 6581C73A20CED60100F4AD34 /* SafariExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */; }; 6581C73D20CED60100F4AD34 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */; }; 6581C74020CED60100F4AD34 /* netnewswire-subscribe-to-feed.js in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73F20CED60100F4AD34 /* netnewswire-subscribe-to-feed.js */; }; 6581C74220CED60100F4AD34 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */; }; 65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; 65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; }; 65ED3FB9235DEF6C0081F399 /* TimelineAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* TimelineAvatarView.swift */; }; 65ED3FBA235DEF6C0081F399 /* ArticleExtractorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */; }; 65ED3FBB235DEF6C0081F399 /* InspectorWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BBB12C20142A4700F054F5 /* InspectorWindowController.swift */; }; 65ED3FBC235DEF6C0081F399 /* ColorHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F78227716380050506E /* ColorHash.swift */; }; 65ED3FBD235DEF6C0081F399 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */; }; 65ED3FBE235DEF6C0081F399 /* Account+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5907D962004B7EB005947E5 /* Account+Scriptability.swift */; }; 65ED3FBF235DEF6C0081F399 /* NothingInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */; }; 65ED3FC0235DEF6C0081F399 /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; }; 65ED3FC1235DEF6C0081F399 /* TimelineKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B5A1FEA00FB00C7C76A /* TimelineKeyboardDelegate.swift */; }; 65ED3FC2235DEF6C0081F399 /* Browser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45DC1ED8C54B000A8B52 /* Browser.swift */; }; 65ED3FC3235DEF6C0081F399 /* DetailWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */; }; 65ED3FC4235DEF6C0081F399 /* OPMLExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8444C8F11FED81840051386C /* OPMLExporter.swift */; }; 65ED3FC5235DEF6C0081F399 /* MainWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A975D1ED9EB72007D329B /* MainWindowController.swift */; }; 65ED3FC6235DEF6C0081F399 /* UnreadFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */; }; 65ED3FC7235DEF6C0081F399 /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513228F2233037620033D4ED /* Reachability.swift */; }; 65ED3FC8235DEF6C0081F399 /* SidebarCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29211FC9251E007B49E3 /* SidebarCellLayout.swift */; }; 65ED3FC9235DEF6C0081F399 /* SmartFeedPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */; }; 65ED3FCA235DEF6C0081F399 /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; }; 65ED3FCB235DEF6C0081F399 /* SidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97621ED9EB96007D329B /* SidebarViewController.swift */; }; 65ED3FCC235DEF6C0081F399 /* AccountsFeedlyWebWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */; }; 65ED3FCD235DEF6C0081F399 /* SidebarOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97601ED9EB96007D329B /* SidebarOutlineView.swift */; }; 65ED3FCE235DEF6C0081F399 /* DetailKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */; }; 65ED3FCF235DEF6C0081F399 /* TimelineContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD9822153B6B008CE1BF /* TimelineContainerView.swift */; }; 65ED3FD0235DEF6C0081F399 /* Author+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2678B20130ECF00A8D3C0 /* Author+Scriptability.swift */; }; 65ED3FD1235DEF6C0081F399 /* PseudoFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */; }; 65ED3FD2235DEF6C0081F399 /* AccountsAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F8F2279C9500050506E /* AccountsAddViewController.swift */; }; 65ED3FD3235DEF6C0081F399 /* NSScriptCommand+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */; }; 65ED3FD4235DEF6C0081F399 /* Article+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553737C20186C1F006D8857 /* Article+Scriptability.swift */; }; 65ED3FD5235DEF6C0081F399 /* SmartFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7C01FC2488C00854A1F /* SmartFeed.swift */; }; 65ED3FD6235DEF6C0081F399 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; 65ED3FD7235DEF6C0081F399 /* NSApplication+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5907D7E2004AC00005947E5 /* NSApplication+Scriptability.swift */; }; 65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */; }; 65ED3FD9235DEF6C0081F399 /* SidebarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A979E1ED9F130007D329B /* SidebarCell.swift */; }; 65ED3FDA235DEF6C0081F399 /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; 65ED3FDB235DEF6C0081F399 /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */; }; 65ED3FDC235DEF6C0081F399 /* UnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97631ED9EB96007D329B /* UnreadCountView.swift */; }; 65ED3FDD235DEF6C0081F399 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; }; 65ED3FDE235DEF6C0081F399 /* CrashReportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840BEE4021D70E64009BBAFA /* CrashReportWindowController.swift */; }; 65ED3FDF235DEF6C0081F399 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; }; 65ED3FE0235DEF6C0081F399 /* AccountsControlsBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC7122629E1200D921D6 /* AccountsControlsBackgroundView.swift */; }; 65ED3FE1235DEF6C0081F399 /* MarkCommandValidationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */; }; 65ED3FE2235DEF6C0081F399 /* ArticlePasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */; }; 65ED3FE3235DEF6C0081F399 /* ArticleUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */; }; 65ED3FE4235DEF6C0081F399 /* NNW3OpenPanelAccessoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849ADEE7235981A0000E1B81 /* NNW3OpenPanelAccessoryViewController.swift */; }; 65ED3FE5235DEF6C0081F399 /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; }; 65ED3FE6235DEF6C0081F399 /* RenameWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A37CB4201ECD610087C5AF /* RenameWindowController.swift */; }; 65ED3FE7235DEF6C0081F399 /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */; }; 65ED3FE8235DEF6C0081F399 /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; }; 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */; }; 65ED3FEB235DEF6C0081F399 /* (null) in Sources */ = {isa = PBXBuildFile; }; 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; }; 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; }; 65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5907DB12004BB37005947E5 /* ScriptingObjectContainer.swift */; }; 65ED3FF0235DEF6C0081F399 /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; }; 65ED3FF1235DEF6C0081F399 /* DetailContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD892213E0E3008CE1BF /* DetailContainerView.swift */; }; 65ED3FF2235DEF6C0081F399 /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; 65ED3FF3235DEF6C0081F399 /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; }; 65ED3FF4235DEF6C0081F399 /* TimelineViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */; }; 65ED3FF5235DEF6C0081F399 /* ArticleStringFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */; }; 65ED3FF6235DEF6C0081F399 /* MultilineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */; }; 65ED3FF7235DEF6C0081F399 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; }; 65ED3FF8235DEF6C0081F399 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB32229AB02C00645299 /* ErrorHandler.swift */; }; 65ED3FF9235DEF6C0081F399 /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; }; 65ED3FFA235DEF6C0081F399 /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; }; 65ED3FFB235DEF6C0081F399 /* AccountsReaderAPIWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */; }; 65ED3FFC235DEF6C0081F399 /* AccountsAddLocalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */; }; 65ED3FFD235DEF6C0081F399 /* PasteboardFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EA92031617300BC20B7 /* PasteboardFolder.swift */; }; 65ED3FFE235DEF6C0081F399 /* AccountsFeedbinWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */; }; 65ED3FFF235DEF6C0081F399 /* SidebarOutlineDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */; }; 65ED4000235DEF6C0081F399 /* SidebarCellAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29231FC9255E007B49E3 /* SidebarCellAppearance.swift */; }; 65ED4001235DEF6C0081F399 /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; }; 65ED4002235DEF6C0081F399 /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; 65ED4003235DEF6C0081F399 /* AdvancedPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC6B22629E1200D921D6 /* AdvancedPreferencesViewController.swift */; }; 65ED4004235DEF6C0081F399 /* SharingServicePickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */; }; 65ED4005235DEF6C0081F399 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; }; 65ED4006235DEF6C0081F399 /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE70E203919360082A1EA /* AppAssets.swift */; }; 65ED4007235DEF6C0081F399 /* AddFeedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97511ED9EAC0007D329B /* AddFeedController.swift */; }; 65ED4008235DEF6C0081F399 /* AccountRefreshTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */; }; 65ED4009235DEF6C0081F399 /* SidebarStatusBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */; }; 65ED400A235DEF6C0081F399 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; 65ED400B235DEF6C0081F399 /* TodayFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */; }; 65ED400C235DEF6C0081F399 /* FolderInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */; }; 65ED400D235DEF6C0081F399 /* SmartFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEE56422C32CA4005FC42C /* SmartFeedDelegate.swift */; }; 65ED400E235DEF6C0081F399 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; }; 65ED400F235DEF6C0081F399 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */; }; 65ED4010235DEF6C0081F399 /* AccountsAddTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F912279CA620050506E /* AccountsAddTableCellView.swift */; }; 65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; }; 65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DDA422168C62008CE1BF /* TimelineContainerViewController.swift */; }; 65ED4013235DEF6C0081F399 /* MainWIndowKeyboardHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B661FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift */; }; 65ED4014235DEF6C0081F399 /* PasteboardFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardFeed.swift */; }; 65ED4015235DEF6C0081F399 /* AccountsDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA2E2279FAB600D19003 /* AccountsDetailViewController.swift */; }; 65ED4016235DEF6C0081F399 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977E1ED9EC42007D329B /* DetailViewController.swift */; }; 65ED4017235DEF6C0081F399 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC6622629B3900D921D6 /* AppDelegate.swift */; }; 65ED4018235DEF6C0081F399 /* AccountsTableViewBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC7022629E1200D921D6 /* AccountsTableViewBackgroundView.swift */; }; 65ED4019235DEF6C0081F399 /* FetchRequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */; }; 65ED401A235DEF6C0081F399 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; }; 65ED401B235DEF6C0081F399 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A976B1ED9EBC8007D329B /* TimelineViewController.swift */; }; 65ED401C235DEF6C0081F399 /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; }; 65ED401D235DEF6C0081F399 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 65ED401E235DEF6C0081F399 /* TimelineCellData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97711ED9EC04007D329B /* TimelineCellData.swift */; }; 65ED401F235DEF6C0081F399 /* BuiltinSmartFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */; }; 65ED4020235DEF6C0081F399 /* AppDelegate+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E4CC53202C1361009B4FFC /* AppDelegate+Scriptability.swift */; }; 65ED4021235DEF6C0081F399 /* NNW3Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651AB23555EB20078E021 /* NNW3Document.swift */; }; 65ED4022235DEF6C0081F399 /* ScriptingObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB4200744A700B9E363 /* ScriptingObject.swift */; }; 65ED4023235DEF6C0081F399 /* Folder+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */; }; 65ED4024235DEF6C0081F399 /* TimelineCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97721ED9EC04007D329B /* TimelineCellLayout.swift */; }; 65ED4025235DEF6C0081F399 /* DetailWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0EA202F693600562D8F /* DetailWebView.swift */; }; 65ED4026235DEF6C0081F399 /* TimelineTableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */; }; 65ED4027235DEF6C0081F399 /* UnreadIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */; }; 65ED4028235DEF6C0081F399 /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; }; 65ED4029235DEF6C0081F399 /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; }; 65ED402A235DEF6C0081F399 /* AddFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */; }; 65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */; }; 65ED402C235DEF6C0081F399 /* TimelineTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A976A1ED9EBC8007D329B /* TimelineTableView.swift */; }; 65ED402D235DEF6C0081F399 /* DetailStatusBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */; }; 65ED402E235DEF6C0081F399 /* MainWindowController+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E4CC63202C1AC1009B4FFC /* MainWindowController+Scriptability.swift */; }; 65ED402F235DEF6C0081F399 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC6E22629E1200D921D6 /* PreferencesWindowController.swift */; }; 65ED4030235DEF6C0081F399 /* SmallIconProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */; }; 65ED4031235DEF6C0081F399 /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; }; 65ED4032235DEF6C0081F399 /* FetchRequestQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCA322BC8C08007694F0 /* FetchRequestQueue.swift */; }; 65ED4033235DEF6C0081F399 /* SidebarKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B581FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift */; }; 65ED4034235DEF6C0081F399 /* AccountsPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC7222629E1200D921D6 /* AccountsPreferencesViewController.swift */; }; 65ED4035235DEF6C0081F399 /* FolderTreeMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */; }; 65ED4036235DEF6C0081F399 /* NNW3ImportController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849ADEE02359817D000E1B81 /* NNW3ImportController.swift */; }; 65ED4037235DEF6C0081F399 /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; 65ED4038235DEF6C0081F399 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; 65ED4039235DEF6C0081F399 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; }; 65ED403A235DEF6C0081F399 /* Feed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */; }; 65ED403B235DEF6C0081F399 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; }; 65ED403C235DEF6C0081F399 /* SingleLineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */; }; 65ED403D235DEF6C0081F399 /* TimelineTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97741ED9EC04007D329B /* TimelineTableCellView.swift */; }; 65ED403E235DEF6C0081F399 /* TimelineCellAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97701ED9EC04007D329B /* TimelineCellAppearance.swift */; }; 65ED403F235DEF6C0081F399 /* ArticleRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */; }; 65ED4040235DEF6C0081F399 /* GeneralPrefencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC6D22629E1200D921D6 /* GeneralPrefencesViewController.swift */; }; 65ED4043235DEF6C0081F399 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9F20DD8D0500CA8CF5 /* RSWeb.framework */; }; 65ED4044235DEF6C0081F399 /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC020DD8E0C00CA8CF5 /* RSDatabase.framework */; }; 65ED4045235DEF6C0081F399 /* RSTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; }; 65ED4046235DEF6C0081F399 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; 65ED4047235DEF6C0081F399 /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; }; 65ED4048235DEF6C0081F399 /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407166A2262A60D00344432 /* Account.framework */; }; 65ED4049235DEF6C0081F399 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; }; 65ED404A235DEF6C0081F399 /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8120DD8CF200CA8CF5 /* RSCore.framework */; }; 65ED404B235DEF6C0081F399 /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; }; 65ED404E235DEF6C0081F399 /* NNW3OpenPanelAccessoryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 849ADEE523598189000E1B81 /* NNW3OpenPanelAccessoryView.xib */; }; 65ED404F235DEF6C0081F399 /* GlobalKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B641FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist */; }; 65ED4050235DEF6C0081F399 /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; }; 65ED4051235DEF6C0081F399 /* TimelineKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */; }; 65ED4052235DEF6C0081F399 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; 65ED4053235DEF6C0081F399 /* AccountsFeedlyWeb.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */; }; 65ED4054235DEF6C0081F399 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 848363062262A3DD00DA1D35 /* Main.storyboard */; }; 65ED4055235DEF6C0081F399 /* AccountsAdd.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51EF0F8D2279C9260050506E /* AccountsAdd.xib */; }; 65ED4056235DEF6C0081F399 /* NetNewsWire.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8A22629E8F00D921D6 /* NetNewsWire.sdef */; }; 65ED4057235DEF6C0081F399 /* AccountsDetail.xib in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC7422629E1200D921D6 /* AccountsDetail.xib */; }; 65ED4058235DEF6C0081F399 /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; 65ED4059235DEF6C0081F399 /* AccountsAddLocal.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA352279FC3D00D19003 /* AccountsAddLocal.xib */; }; 65ED405A235DEF6C0081F399 /* main_mac.js in Resources */ = {isa = PBXBuildFile; fileRef = 5142194A2353C1CF00E07E2C /* main_mac.js */; }; 65ED405B235DEF6C0081F399 /* KeyboardShortcuts.html in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8722629E8F00D921D6 /* KeyboardShortcuts.html */; }; 65ED405C235DEF6C0081F399 /* ImportOPMLSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */; }; 65ED405D235DEF6C0081F399 /* SidebarKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B681FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist */; }; 65ED405E235DEF6C0081F399 /* DefaultFeeds.opml in Resources */ = {isa = PBXBuildFile; fileRef = 84A3EE52223B667F00557320 /* DefaultFeeds.opml */; }; 65ED405F235DEF6C0081F399 /* Preferences.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8022629E4800D921D6 /* Preferences.storyboard */; }; 65ED4060235DEF6C0081F399 /* (null) in Resources */ = {isa = PBXBuildFile; }; 65ED4061235DEF6C0081F399 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 849C64671ED37A5D003D8FC0 /* Assets.xcassets */; }; 65ED4062235DEF6C0081F399 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 848362FC2262A30800DA1D35 /* styleSheet.css */; }; 65ED4063235DEF6C0081F399 /* RenameSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363092262A3F000DA1D35 /* RenameSheet.xib */; }; 65ED4064235DEF6C0081F399 /* AddFolderSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363032262A3CC00DA1D35 /* AddFolderSheet.xib */; }; 65ED4065235DEF6C0081F399 /* AccountsFeedbin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */; }; 65ED4066235DEF6C0081F399 /* TimelineTableView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8405DDA122168920008CE1BF /* TimelineTableView.xib */; }; 65ED4067235DEF6C0081F399 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = B528F81D23333C7E00E735DD /* page.html */; }; 65ED4068235DEF6C0081F399 /* MainWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8483630C2262A3FE00DA1D35 /* MainWindow.storyboard */; }; 65ED4069235DEF6C0081F399 /* AccountsReaderAPI.xib in Resources */ = {isa = PBXBuildFile; fileRef = 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */; }; 65ED406A235DEF6C0081F399 /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; }; 65ED406B235DEF6C0081F399 /* CrashReporterWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 84BAE64821CEDAF20046DB56 /* CrashReporterWindow.xib */; }; 65ED406C235DEF6C0081F399 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8922629E8F00D921D6 /* Credits.rtf */; }; 65ED406D235DEF6C0081F399 /* Inspector.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84BBB12B20142A4700F054F5 /* Inspector.storyboard */; }; 65ED406E235DEF6C0081F399 /* AddFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363002262A3BC00DA1D35 /* AddFeedSheet.xib */; }; 65ED4071235DEF6C0081F399 /* RSWeb.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9F20DD8D0500CA8CF5 /* RSWeb.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 65ED4072235DEF6C0081F399 /* RSDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC020DD8E0C00CA8CF5 /* RSDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 65ED4073235DEF6C0081F399 /* RSTree.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 65ED4074235DEF6C0081F399 /* ArticlesDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 65ED4076235DEF6C0081F399 /* Account.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407166A2262A60D00344432 /* Account.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 65ED4077235DEF6C0081F399 /* Articles.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 65ED4078235DEF6C0081F399 /* RSParser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 65ED4079235DEF6C0081F399 /* SyncDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 65ED407A235DEF6C0081F399 /* RSCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8120DD8CF200CA8CF5 /* RSCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 65ED407C235DEF6C0081F399 /* Subscribe to Feed.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 65ED4092235DEF770081F399 /* SafariExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */; }; 65ED4093235DEF770081F399 /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */; }; 65ED4096235DEF770081F399 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */; }; 65ED4097235DEF770081F399 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */; }; 65ED4098235DEF770081F399 /* netnewswire-subscribe-to-feed.js in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73F20CED60100F4AD34 /* netnewswire-subscribe-to-feed.js */; }; 65ED40A0235DEFF00081F399 /* container-migration.plist in Resources */ = {isa = PBXBuildFile; fileRef = 65ED409F235DEFF00081F399 /* container-migration.plist */; }; 65ED40A1235DEFF00081F399 /* container-migration.plist in Resources */ = {isa = PBXBuildFile; fileRef = 65ED409F235DEFF00081F399 /* container-migration.plist */; }; 65ED42D9235E740D0081F399 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65ED42B0235E71B40081F399 /* Sparkle.framework */; }; 65ED42DA235E74230081F399 /* org.sparkle-project.Downloader.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 65ED42BC235E71B40081F399 /* org.sparkle-project.Downloader.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 65ED42DB235E74230081F399 /* org.sparkle-project.InstallerConnection.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 65ED42B8235E71B40081F399 /* org.sparkle-project.InstallerConnection.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 65ED42DC235E74230081F399 /* org.sparkle-project.InstallerLauncher.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 65ED42B6235E71B40081F399 /* org.sparkle-project.InstallerLauncher.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 65ED42DD235E74230081F399 /* org.sparkle-project.InstallerStatus.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 65ED42BA235E71B40081F399 /* org.sparkle-project.InstallerStatus.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 65ED42DE235E74230081F399 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65ED42B0235E71B40081F399 /* Sparkle.framework */; }; 65ED42DF235E74230081F399 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 65ED42B0235E71B40081F399 /* Sparkle.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 8405DD8A2213E0E3008CE1BF /* DetailContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD892213E0E3008CE1BF /* DetailContainerView.swift */; }; 8405DD9922153B6B008CE1BF /* TimelineContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD9822153B6B008CE1BF /* TimelineContainerView.swift */; }; 8405DD9C22153BD7008CE1BF /* NSView-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */; }; 8405DDA222168920008CE1BF /* TimelineTableView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8405DDA122168920008CE1BF /* TimelineTableView.xib */; }; 8405DDA522168C62008CE1BF /* TimelineContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DDA422168C62008CE1BF /* TimelineContainerViewController.swift */; }; 840958632201629A002C1579 /* Subscribe to Feed.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840BEE4021D70E64009BBAFA /* CrashReportWindowController.swift */; }; 840D617F2029031C009BC708 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D617E2029031C009BC708 /* AppDelegate.swift */; }; 84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */; }; 841ABA4E20145E7300980E11 /* NothingInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */; }; 841ABA5E20145E9200980E11 /* FolderInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */; }; 841ABA6020145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */; }; 84216D0322128B9D0049B9B9 /* DetailWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */; }; 8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; }; 8426119E1FCB6ED40086A189 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; }; 842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; 842E45CE1ED8C308000A8B52 /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; }; 842E45DD1ED8C54B000A8B52 /* Browser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45DC1ED8C54B000A8B52 /* Browser.swift */; }; 84411E711FE5FBFA004B527F /* SmallIconProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */; }; 8444C8F21FED81840051386C /* OPMLExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8444C8F11FED81840051386C /* OPMLExporter.swift */; }; 844B5B591FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B581FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift */; }; 844B5B5B1FEA00FB00C7C76A /* TimelineKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B5A1FEA00FB00C7C76A /* TimelineKeyboardDelegate.swift */; }; 844B5B651FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B641FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist */; }; 844B5B671FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B661FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift */; }; 844B5B691FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 844B5B681FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist */; }; 845213231FCA5B11003B6E93 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; }; 845479881FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */; }; 845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; }; 845A29221FC9251E007B49E3 /* SidebarCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29211FC9251E007B49E3 /* SidebarCellLayout.swift */; }; 845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29231FC9255E007B49E3 /* SidebarCellAppearance.swift */; }; 845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; }; 845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7C01FC2488C00854A1F /* SmartFeed.swift */; }; 84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; 8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; }; 8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; }; 847CD6CA232F4CBF00FAC46D /* TimelineAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* TimelineAvatarView.swift */; }; 847E64A02262783000E00365 /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */; }; 848362FD2262A30800DA1D35 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 848362FC2262A30800DA1D35 /* styleSheet.css */; }; 848362FF2262A30E00DA1D35 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; 848363022262A3BD00DA1D35 /* AddFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363002262A3BC00DA1D35 /* AddFeedSheet.xib */; }; 848363052262A3CC00DA1D35 /* AddFolderSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363032262A3CC00DA1D35 /* AddFolderSheet.xib */; }; 848363082262A3DD00DA1D35 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 848363062262A3DD00DA1D35 /* Main.storyboard */; }; 8483630B2262A3F000DA1D35 /* RenameSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363092262A3F000DA1D35 /* RenameSheet.xib */; }; 8483630E2262A3FE00DA1D35 /* MainWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8483630C2262A3FE00DA1D35 /* MainWindow.storyboard */; }; 848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; }; 848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardFeed.swift */; }; 848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; 849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; }; 849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97511ED9EAC0007D329B /* AddFeedController.swift */; }; 849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */; }; 849A975B1ED9EB0D007D329B /* ArticleUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */; }; 849A975C1ED9EB0D007D329B /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; }; 849A975E1ED9EB72007D329B /* MainWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A975D1ED9EB72007D329B /* MainWindowController.swift */; }; 849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97601ED9EB96007D329B /* SidebarOutlineView.swift */; }; 849A97651ED9EB96007D329B /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */; }; 849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97621ED9EB96007D329B /* SidebarViewController.swift */; }; 849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97631ED9EB96007D329B /* UnreadCountView.swift */; }; 849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */; }; 849A976D1ED9EBC8007D329B /* TimelineTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A976A1ED9EBC8007D329B /* TimelineTableView.swift */; }; 849A976E1ED9EBC8007D329B /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A976B1ED9EBC8007D329B /* TimelineViewController.swift */; }; 849A97761ED9EC04007D329B /* TimelineCellAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97701ED9EC04007D329B /* TimelineCellAppearance.swift */; }; 849A97771ED9EC04007D329B /* TimelineCellData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97711ED9EC04007D329B /* TimelineCellData.swift */; }; 849A97781ED9EC04007D329B /* TimelineCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97721ED9EC04007D329B /* TimelineCellLayout.swift */; }; 849A97791ED9EC04007D329B /* ArticleStringFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */; }; 849A977A1ED9EC04007D329B /* TimelineTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97741ED9EC04007D329B /* TimelineTableCellView.swift */; }; 849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */; }; 849A977F1ED9EC42007D329B /* ArticleRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */; }; 849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977E1ED9EC42007D329B /* DetailViewController.swift */; }; 849A97831ED9EC63007D329B /* SidebarStatusBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */; }; 849A97891ED9ECEF007D329B /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; }; 849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; }; 849A97981ED9EFAA007D329B /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; }; 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A979E1ED9F130007D329B /* SidebarCell.swift */; }; 849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; 849ADEE42359817E000E1B81 /* NNW3ImportController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849ADEE02359817D000E1B81 /* NNW3ImportController.swift */; }; 849ADEE623598189000E1B81 /* NNW3OpenPanelAccessoryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 849ADEE523598189000E1B81 /* NNW3OpenPanelAccessoryView.xib */; }; 849ADEE8235981A0000E1B81 /* NNW3OpenPanelAccessoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849ADEE7235981A0000E1B81 /* NNW3OpenPanelAccessoryViewController.swift */; }; 849C64681ED37A5D003D8FC0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 849C64671ED37A5D003D8FC0 /* Assets.xcassets */; }; 849C78902362AAFC009A71E4 /* ExportOPMLSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 849C78872362AAFB009A71E4 /* ExportOPMLSheet.xib */; }; 849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849C78912362AB04009A71E4 /* ExportOPMLWindowController.swift */; }; 849EE70F203919360082A1EA /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE70E203919360082A1EA /* AppAssets.swift */; }; 849EE72120391F560082A1EA /* SharingServicePickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */; }; 84A14FF320048CA70046AD9A /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */; }; 84A1500520048DDF0046AD9A /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; }; 84A37CB5201ECD610087C5AF /* RenameWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A37CB4201ECD610087C5AF /* RenameWindowController.swift */; }; 84A3EE5F223B667F00557320 /* DefaultFeeds.opml in Resources */ = {isa = PBXBuildFile; fileRef = 84A3EE52223B667F00557320 /* DefaultFeeds.opml */; }; 84A3EE61223B667F00557320 /* DefaultFeeds.opml in Resources */ = {isa = PBXBuildFile; fileRef = 84A3EE52223B667F00557320 /* DefaultFeeds.opml */; }; 84AD1EAA2031617300BC20B7 /* PasteboardFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EA92031617300BC20B7 /* PasteboardFolder.swift */; }; 84AD1EBA2031649C00BC20B7 /* SmartFeedPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */; }; 84AD1EBC2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */; }; 84B7178C201E66580091657D /* SidebarViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */; }; 84B99C9D1FAE83C600ECDEDB /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; }; 84BAE64921CEDAF20046DB56 /* CrashReporterWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 84BAE64821CEDAF20046DB56 /* CrashReporterWindow.xib */; }; 84BBB12D20142A4700F054F5 /* Inspector.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84BBB12B20142A4700F054F5 /* Inspector.storyboard */; }; 84BBB12E20142A4700F054F5 /* InspectorWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BBB12C20142A4700F054F5 /* InspectorWindowController.swift */; }; 84C37FA520DD8D8400CA8CF5 /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8120DD8CF200CA8CF5 /* RSCore.framework */; }; 84C37FA620DD8D8400CA8CF5 /* RSCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8120DD8CF200CA8CF5 /* RSCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84C37FA920DD8D9000CA8CF5 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9F20DD8D0500CA8CF5 /* RSWeb.framework */; }; 84C37FAA20DD8D9000CA8CF5 /* RSWeb.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9F20DD8D0500CA8CF5 /* RSWeb.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84C37FAD20DD8D9900CA8CF5 /* RSTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; }; 84C37FAE20DD8D9900CA8CF5 /* RSTree.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84C37FB520DD8DBB00CA8CF5 /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; }; 84C37FB620DD8DBB00CA8CF5 /* RSParser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84C37FC520DD8E1D00CA8CF5 /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC020DD8E0C00CA8CF5 /* RSDatabase.framework */; }; 84C37FC620DD8E1D00CA8CF5 /* RSDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC020DD8E0C00CA8CF5 /* RSDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 84C9FC6722629B9000D921D6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC6622629B3900D921D6 /* AppDelegate.swift */; }; 84C9FC7722629E1200D921D6 /* AdvancedPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC6B22629E1200D921D6 /* AdvancedPreferencesViewController.swift */; }; 84C9FC7822629E1200D921D6 /* GeneralPrefencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC6D22629E1200D921D6 /* GeneralPrefencesViewController.swift */; }; 84C9FC7922629E1200D921D6 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC6E22629E1200D921D6 /* PreferencesWindowController.swift */; }; 84C9FC7A22629E1200D921D6 /* AccountsTableViewBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC7022629E1200D921D6 /* AccountsTableViewBackgroundView.swift */; }; 84C9FC7B22629E1200D921D6 /* AccountsControlsBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC7122629E1200D921D6 /* AccountsControlsBackgroundView.swift */; }; 84C9FC7C22629E1200D921D6 /* AccountsPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC7222629E1200D921D6 /* AccountsPreferencesViewController.swift */; }; 84C9FC7D22629E1200D921D6 /* AccountsDetail.xib in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC7422629E1200D921D6 /* AccountsDetail.xib */; }; 84C9FC8222629E4800D921D6 /* Preferences.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8022629E4800D921D6 /* Preferences.storyboard */; }; 84C9FC8C22629E8F00D921D6 /* KeyboardShortcuts.html in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8722629E8F00D921D6 /* KeyboardShortcuts.html */; }; 84C9FC8E22629E8F00D921D6 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8922629E8F00D921D6 /* Credits.rtf */; }; 84C9FC8F22629E8F00D921D6 /* NetNewsWire.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8A22629E8F00D921D6 /* NetNewsWire.sdef */; }; 84C9FC9D2262A1A900D921D6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC9B2262A1A900D921D6 /* Assets.xcassets */; }; 84C9FCA12262A1B300D921D6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC9F2262A1B300D921D6 /* Main.storyboard */; }; 84C9FCA42262A1B800D921D6 /* LaunchScreenPhone.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FCA22262A1B800D921D6 /* LaunchScreenPhone.storyboard */; }; 84CAFCA422BC8C08007694F0 /* FetchRequestQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCA322BC8C08007694F0 /* FetchRequestQueue.swift */; }; 84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCA322BC8C08007694F0 /* FetchRequestQueue.swift */; }; 84CAFCAF22BC8C35007694F0 /* FetchRequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */; }; 84CAFCB022BC8C35007694F0 /* FetchRequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */; }; 84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; }; 84D52E951FE588BB00D14F5B /* DetailStatusBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */; }; 84DEE56522C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEE56422C32CA4005FC42C /* SmartFeedDelegate.swift */; }; 84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEE56422C32CA4005FC42C /* SmartFeedDelegate.swift */; }; 84E185B3203B74E500F69BFA /* SingleLineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */; }; 84E185C3203BB12600F69BFA /* MultilineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */; }; 84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */; }; 84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; }; 84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */; }; 84E8E0EB202F693600562D8F /* DetailWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0EA202F693600562D8F /* DetailWebView.swift */; }; 84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */; }; 84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; 84F2D5371FC22FCC00998D64 /* PseudoFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */; }; 84F2D5381FC22FCC00998D64 /* TodayFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */; }; 84F2D53A1FC2308B00998D64 /* UnreadFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */; }; 84F9EAE5213660A100CF2DE4 /* AppleScriptXCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAD1213660A100CF2DE4 /* AppleScriptXCTestCase.swift */; }; 84F9EAE6213660A100CF2DE4 /* ScriptingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAD2213660A100CF2DE4 /* ScriptingTests.swift */; }; 84F9EAE7213660A100CF2DE4 /* testNameOfAuthors.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAD4213660A100CF2DE4 /* testNameOfAuthors.applescript */; }; 84F9EAE8213660A100CF2DE4 /* testGetURL.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAD5213660A100CF2DE4 /* testGetURL.applescript */; }; 84F9EAE9213660A100CF2DE4 /* testNameAndUrlOfEveryFeed.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAD6213660A100CF2DE4 /* testNameAndUrlOfEveryFeed.applescript */; }; 84F9EAEA213660A100CF2DE4 /* testFeedExists.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAD7213660A100CF2DE4 /* testFeedExists.applescript */; }; 84F9EAEB213660A100CF2DE4 /* testIterativeCreateAndDeleteFeed.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAD8213660A100CF2DE4 /* testIterativeCreateAndDeleteFeed.applescript */; }; 84F9EAEC213660A100CF2DE4 /* selectAFeed.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAD9213660A100CF2DE4 /* selectAFeed.applescript */; }; 84F9EAED213660A100CF2DE4 /* uiScriptingTestSetup.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EADA213660A100CF2DE4 /* uiScriptingTestSetup.applescript */; }; 84F9EAEF213660A100CF2DE4 /* testNameOfEveryFolder.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EADC213660A100CF2DE4 /* testNameOfEveryFolder.applescript */; }; 84F9EAF0213660A100CF2DE4 /* testFeedOPML.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EADD213660A100CF2DE4 /* testFeedOPML.applescript */; }; 84F9EAF1213660A100CF2DE4 /* selectAnArticle.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EADE213660A100CF2DE4 /* selectAnArticle.applescript */; }; 84F9EAF2213660A100CF2DE4 /* testTitleOfArticlesWhose.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EADF213660A100CF2DE4 /* testTitleOfArticlesWhose.applescript */; }; 84F9EAF3213660A100CF2DE4 /* testCurrentArticleIsNil.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE0213660A100CF2DE4 /* testCurrentArticleIsNil.applescript */; }; 84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE1213660A100CF2DE4 /* testGenericScript.applescript */; }; 84F9EAF5213660A100CF2DE4 /* establishMainWindowStartingState.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */; }; 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; 9EA33BB92318F8C10097B644 /* AccountsFeedlyWebWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */; }; 9EA33BBA2318F8C10097B644 /* AccountsFeedlyWeb.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */; }; B528F81E23333C7E00E735DD /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = B528F81D23333C7E00E735DD /* page.html */; }; D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553737C20186C1F006D8857 /* Article+Scriptability.swift */; }; D57BE6E0204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */; }; D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5907D7E2004AC00005947E5 /* NSApplication+Scriptability.swift */; }; D5907D972004B7EB005947E5 /* Account+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5907D962004B7EB005947E5 /* Account+Scriptability.swift */; }; D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5907DB12004BB37005947E5 /* ScriptingObjectContainer.swift */; }; D5A2678C20130ECF00A8D3C0 /* Author+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2678B20130ECF00A8D3C0 /* Author+Scriptability.swift */; }; D5E4CC54202C1361009B4FFC /* AppDelegate+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E4CC53202C1361009B4FFC /* AppDelegate+Scriptability.swift */; }; D5E4CC64202C1AC1009B4FFC /* MainWindowController+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E4CC63202C1AC1009B4FFC /* MainWindowController+Scriptability.swift */; }; D5F4EDB5200744A700B9E363 /* ScriptingObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB4200744A700B9E363 /* ScriptingObject.swift */; }; D5F4EDB720074D6500B9E363 /* Feed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */; }; D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */; }; DD82AB0A231003F6002269DF /* SharingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD82AB09231003F6002269DF /* SharingTests.swift */; }; FF3ABF13232599810074C542 /* ArticleSorterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF09232599450074C542 /* ArticleSorterTests.swift */; }; FF3ABF1523259DDB0074C542 /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; }; FF3ABF162325AF5D0074C542 /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; }; FFD43E412340F488009E5CA3 /* UndoAvailableAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD43E372340F320009E5CA3 /* UndoAvailableAlertController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 5131463C235A7BBE00387FDC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 849C64581ED37A5D003D8FC0 /* Project object */; proxyType = 1; remoteGlobalIDString = 51314636235A7BBE00387FDC; remoteInfo = "NetNewsWire iOS Intents Extension"; }; 51554C00228B6EB50055115A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 51554BFC228B6EB50055115A /* SyncDatabase.xcodeproj */; proxyType = 2; remoteGlobalIDString = 51554BEB228B6E8F0055115A; remoteInfo = SyncDatabase; }; 51554C26228B71910055115A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 51554BFC228B6EB50055115A /* SyncDatabase.xcodeproj */; proxyType = 1; remoteGlobalIDString = 51554BEA228B6E8F0055115A; remoteInfo = SyncDatabase; }; 518B2ED72351B3DD00400001 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 849C64581ED37A5D003D8FC0 /* Project object */; proxyType = 1; remoteGlobalIDString = 840D617B2029031C009BC708; remoteInfo = "NetNewsWire-iOS"; }; 51C451AB226377C300C03939 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */; proxyType = 1; remoteGlobalIDString = 844BEE361F0AB3AA004AB7CD; remoteInfo = ArticlesDatabase; }; 51C451BB226377C900C03939 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 841D4D542106B3D500DD04E6 /* Articles.xcodeproj */; proxyType = 1; remoteGlobalIDString = 844BEE5A1F0AB3C8004AB7CD; remoteInfo = Articles; }; 51C451BF226377D000C03939 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 846E77301F6EF5D600A165E2 /* Account.xcodeproj */; proxyType = 1; remoteGlobalIDString = 848934F51F62484F00CEBD24; remoteInfo = Account; }; 65ED3FA4235DEF6C0081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F7A20DD8CF200CA8CF5 /* RSCore.xcodeproj */; proxyType = 1; remoteGlobalIDString = 84CFF4F31AC3C69700CEA6C8; remoteInfo = RSCore; }; 65ED3FA6235DEF6C0081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F9820DD8D0400CA8CF5 /* RSWeb.xcodeproj */; proxyType = 1; remoteGlobalIDString = 849C08B51E0CAC85006B03FA; remoteInfo = RSWeb; }; 65ED3FA8235DEF6C0081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F8F20DD8CFD00CA8CF5 /* RSTree.xcodeproj */; proxyType = 1; remoteGlobalIDString = 842A0BE01CFCB9BC00BF746C; remoteInfo = RSTree; }; 65ED3FAA235DEF6C0081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F8620DD8CF800CA8CF5 /* RSParser.xcodeproj */; proxyType = 1; remoteGlobalIDString = 84FF5F831EFA285800C15A01; remoteInfo = RSParser; }; 65ED3FAC235DEF6C0081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37FB920DD8E0C00CA8CF5 /* RSDatabase.xcodeproj */; proxyType = 1; remoteGlobalIDString = 84F22C541B52E0D9000060CE; remoteInfo = RSDatabase; }; 65ED3FAE235DEF6C0081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */; proxyType = 1; remoteGlobalIDString = 844BEE361F0AB3AA004AB7CD; remoteInfo = ArticlesDatabase; }; 65ED3FB0235DEF6C0081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 841D4D542106B3D500DD04E6 /* Articles.xcodeproj */; proxyType = 1; remoteGlobalIDString = 844BEE5A1F0AB3C8004AB7CD; remoteInfo = Articles; }; 65ED3FB2235DEF6C0081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 846E77301F6EF5D600A165E2 /* Account.xcodeproj */; proxyType = 1; remoteGlobalIDString = 848934F51F62484F00CEBD24; remoteInfo = Account; }; 65ED3FB4235DEF6C0081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 51554BFC228B6EB50055115A /* SyncDatabase.xcodeproj */; proxyType = 1; remoteGlobalIDString = 51554BEA228B6E8F0055115A; remoteInfo = SyncDatabase; }; 65ED41C4235E61550081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 849C64581ED37A5D003D8FC0 /* Project object */; proxyType = 1; remoteGlobalIDString = 6581C73220CED60000F4AD34; remoteInfo = "Subscribe to Feed"; }; 65ED41C6235E615E0081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 849C64581ED37A5D003D8FC0 /* Project object */; proxyType = 1; remoteGlobalIDString = 65ED4090235DEF770081F399; remoteInfo = "Subscribe to Feed MAS"; }; 65ED42AF235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 8DC2EF5B0486A6940098B216; remoteInfo = Sparkle; }; 65ED42B1235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 72A5D59C1D6927730009E5AC; remoteInfo = SparkleCore; }; 65ED42B3235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 72B398D21D3D879300EE297F; remoteInfo = Autoupdate; }; 65ED42B5235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 726E07AD1CAF08D6001A286B; remoteInfo = SparkleInstallerLauncher; }; 65ED42B7235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 724BB36C1D31D0B7005D534A; remoteInfo = SparkleInstallerConnection; }; 65ED42B9235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 724BB3931D333832005D534A; remoteInfo = SparkleInstallerStatus; }; 65ED42BB235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 726E07EF1CAF37BD001A286B; remoteInfo = SparkleDownloader; }; 65ED42BD235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 61B5F90209C4CEE200B25A18; remoteInfo = "Sparkle Test App"; }; 65ED42BF235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 726E4A161C86C88F00C57C6A; remoteInfo = TestAppHelper; }; 65ED42C1235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 612279D90DB5470200AB99EA; remoteInfo = "Sparkle Unit Tests"; }; 65ED42C3235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 5D06E8D00FD68C7C005AE3F6; remoteInfo = BinaryDelta; }; 65ED42C5235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 72D9549E1CBB415B006F28BD; remoteInfo = "sparkle-cli"; }; 65ED42C7235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 721C24451CB753E6005440CB; remoteInfo = "Installer Progress"; }; 65ED42C9235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 726B2B5D1C645FC900388755; remoteInfo = "UI Tests"; }; 65ED42CB235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = 7205C43E1E13049400E370AE; remoteInfo = generate_appcast; }; 65ED42CD235E71B40081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 2; remoteGlobalIDString = EA1E280F22B64522004AA304; remoteInfo = bsdiff; }; 65ED42CF235E71F60081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 1; remoteGlobalIDString = 8DC2EF4F0486A6940098B216; remoteInfo = Sparkle; }; 65ED42D1235E72000081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 1; remoteGlobalIDString = 726E07EE1CAF37BD001A286B; remoteInfo = SparkleDownloader; }; 65ED42D3235E72000081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 1; remoteGlobalIDString = 724BB36B1D31D0B7005D534A; remoteInfo = SparkleInstallerConnection; }; 65ED42D5235E72000081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 1; remoteGlobalIDString = 726E07AC1CAF08D6001A286B; remoteInfo = SparkleInstallerLauncher; }; 65ED42D7235E72000081F399 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; proxyType = 1; remoteGlobalIDString = 724BB3921D333832005D534A; remoteInfo = SparkleInstallerStatus; }; 840716692262A60D00344432 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 846E77301F6EF5D600A165E2 /* Account.xcodeproj */; proxyType = 2; remoteGlobalIDString = 848934F61F62484F00CEBD24; remoteInfo = Account; }; 8407166B2262A60D00344432 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 846E77301F6EF5D600A165E2 /* Account.xcodeproj */; proxyType = 2; remoteGlobalIDString = 848934FF1F62484F00CEBD24; remoteInfo = AccountTests; }; 840716722262A60F00344432 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 841D4D542106B3D500DD04E6 /* Articles.xcodeproj */; proxyType = 2; remoteGlobalIDString = 844BEE5B1F0AB3C8004AB7CD; remoteInfo = Articles; }; 840716742262A60F00344432 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 841D4D542106B3D500DD04E6 /* Articles.xcodeproj */; proxyType = 2; remoteGlobalIDString = 844BEE641F0AB3C9004AB7CD; remoteInfo = ArticlesTests; }; 8407167E2262A61100344432 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */; proxyType = 2; remoteGlobalIDString = 844BEE371F0AB3AA004AB7CD; remoteInfo = ArticlesDatabase; }; 840716802262A61100344432 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */; proxyType = 2; remoteGlobalIDString = 844BEE401F0AB3AB004AB7CD; remoteInfo = ArticlesDatabaseTests; }; 849C64721ED37A5D003D8FC0 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 849C64581ED37A5D003D8FC0 /* Project object */; proxyType = 1; remoteGlobalIDString = 849C645F1ED37A5D003D8FC0; remoteInfo = NetNewsWire; }; 84C37F8020DD8CF200CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F7A20DD8CF200CA8CF5 /* RSCore.xcodeproj */; proxyType = 2; remoteGlobalIDString = 84CFF4F41AC3C69700CEA6C8; remoteInfo = RSCore; }; 84C37F8220DD8CF200CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F7A20DD8CF200CA8CF5 /* RSCore.xcodeproj */; proxyType = 2; remoteGlobalIDString = 84CFF4FF1AC3C69700CEA6C8; remoteInfo = RSCoreTests; }; 84C37F8420DD8CF200CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F7A20DD8CF200CA8CF5 /* RSCore.xcodeproj */; proxyType = 2; remoteGlobalIDString = 842DD7BC1E14993900E061EB; remoteInfo = RSCoreiOS; }; 84C37F8B20DD8CF800CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F8620DD8CF800CA8CF5 /* RSParser.xcodeproj */; proxyType = 2; remoteGlobalIDString = 84FF5F841EFA285800C15A01; remoteInfo = RSParser; }; 84C37F8D20DD8CF800CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F8620DD8CF800CA8CF5 /* RSParser.xcodeproj */; proxyType = 2; remoteGlobalIDString = 84FF5F8D1EFA285800C15A01; remoteInfo = RSParserTests; }; 84C37F9420DD8CFE00CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F8F20DD8CFD00CA8CF5 /* RSTree.xcodeproj */; proxyType = 2; remoteGlobalIDString = 842A0BE11CFCB9BC00BF746C; remoteInfo = RSTree; }; 84C37F9620DD8CFE00CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F8F20DD8CFD00CA8CF5 /* RSTree.xcodeproj */; proxyType = 2; remoteGlobalIDString = 842A0BEB1CFCB9BC00BF746C; remoteInfo = RSTreeTests; }; 84C37F9E20DD8D0500CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F9820DD8D0400CA8CF5 /* RSWeb.xcodeproj */; proxyType = 2; remoteGlobalIDString = 849C08B61E0CAC85006B03FA; remoteInfo = RSWeb; }; 84C37FA020DD8D0500CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F9820DD8D0400CA8CF5 /* RSWeb.xcodeproj */; proxyType = 2; remoteGlobalIDString = 849C08BF1E0CAC86006B03FA; remoteInfo = RSWebTests; }; 84C37FA220DD8D0500CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F9820DD8D0400CA8CF5 /* RSWeb.xcodeproj */; proxyType = 2; remoteGlobalIDString = 849C08D51E0CACA3006B03FA; remoteInfo = RSWebiOS; }; 84C37FA720DD8D8400CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F7A20DD8CF200CA8CF5 /* RSCore.xcodeproj */; proxyType = 1; remoteGlobalIDString = 84CFF4F31AC3C69700CEA6C8; remoteInfo = RSCore; }; 84C37FAB20DD8D9000CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F9820DD8D0400CA8CF5 /* RSWeb.xcodeproj */; proxyType = 1; remoteGlobalIDString = 849C08B51E0CAC85006B03FA; remoteInfo = RSWeb; }; 84C37FAF20DD8D9900CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F8F20DD8CFD00CA8CF5 /* RSTree.xcodeproj */; proxyType = 1; remoteGlobalIDString = 842A0BE01CFCB9BC00BF746C; remoteInfo = RSTree; }; 84C37FB720DD8DBB00CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37F8620DD8CF800CA8CF5 /* RSParser.xcodeproj */; proxyType = 1; remoteGlobalIDString = 84FF5F831EFA285800C15A01; remoteInfo = RSParser; }; 84C37FBF20DD8E0C00CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37FB920DD8E0C00CA8CF5 /* RSDatabase.xcodeproj */; proxyType = 2; remoteGlobalIDString = 84F22C551B52E0D9000060CE; remoteInfo = RSDatabase; }; 84C37FC120DD8E0C00CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37FB920DD8E0C00CA8CF5 /* RSDatabase.xcodeproj */; proxyType = 2; remoteGlobalIDString = 84F22C5F1B52E0D9000060CE; remoteInfo = RSDatabaseTests; }; 84C37FC320DD8E0C00CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37FB920DD8E0C00CA8CF5 /* RSDatabase.xcodeproj */; proxyType = 2; remoteGlobalIDString = 8400ABF71E0CFBD800AA7C57; remoteInfo = RSDatabaseiOS; }; 84C37FC720DD8E1D00CA8CF5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84C37FB920DD8E0C00CA8CF5 /* RSDatabase.xcodeproj */; proxyType = 1; remoteGlobalIDString = 84F22C541B52E0D9000060CE; remoteInfo = RSDatabase; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 513C5CF1232571C2003D4054 /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( 513C5CF0232571C2003D4054 /* NetNewsWire iOS Share Extension.appex in Embed App Extensions */, 5131463E235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; }; 51C451DF2264C7F200C03939 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 51C451D32264C7F200C03939 /* RSWeb.framework in Embed Frameworks */, 51C451E12264C7F900C03939 /* RSTree.framework in Embed Frameworks */, 51C451F92264C83E00C03939 /* Account.framework in Embed Frameworks */, 51C451F12264C83100C03939 /* ArticlesDatabase.framework in Embed Frameworks */, 51C451F52264C83900C03939 /* Articles.framework in Embed Frameworks */, 51C451E92264C81000C03939 /* RSDatabase.framework in Embed Frameworks */, 51554C31228B71A10055115A /* SyncDatabase.framework in Embed Frameworks */, 51C451ED2264C81B00C03939 /* RSCore.framework in Embed Frameworks */, 51C451E52264C80600C03939 /* RSParser.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; 6581C75720CED60100F4AD34 /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( 840958632201629A002C1579 /* Subscribe to Feed.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; }; 65ED4070235DEF6C0081F399 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 65ED4071235DEF6C0081F399 /* RSWeb.framework in Embed Frameworks */, 65ED4072235DEF6C0081F399 /* RSDatabase.framework in Embed Frameworks */, 65ED4073235DEF6C0081F399 /* RSTree.framework in Embed Frameworks */, 65ED4074235DEF6C0081F399 /* ArticlesDatabase.framework in Embed Frameworks */, 65ED4076235DEF6C0081F399 /* Account.framework in Embed Frameworks */, 65ED4077235DEF6C0081F399 /* Articles.framework in Embed Frameworks */, 65ED4078235DEF6C0081F399 /* RSParser.framework in Embed Frameworks */, 65ED4079235DEF6C0081F399 /* SyncDatabase.framework in Embed Frameworks */, 65ED407A235DEF6C0081F399 /* RSCore.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; 65ED407B235DEF6C0081F399 /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( 65ED407C235DEF6C0081F399 /* Subscribe to Feed.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; }; 65ED42E0235E74240081F399 /* Embed XPC Services */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; dstSubfolderSpec = 16; files = ( 65ED42DD235E74230081F399 /* org.sparkle-project.InstallerStatus.xpc in Embed XPC Services */, 65ED42DB235E74230081F399 /* org.sparkle-project.InstallerConnection.xpc in Embed XPC Services */, 65ED42DC235E74230081F399 /* org.sparkle-project.InstallerLauncher.xpc in Embed XPC Services */, 65ED42DA235E74230081F399 /* org.sparkle-project.Downloader.xpc in Embed XPC Services */, ); name = "Embed XPC Services"; runOnlyForDeploymentPostprocessing = 0; }; 84B06F681ED37B9000F0B54B /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 84C37FAA20DD8D9000CA8CF5 /* RSWeb.framework in Embed Frameworks */, 84C37FC620DD8E1D00CA8CF5 /* RSDatabase.framework in Embed Frameworks */, 84C37FAE20DD8D9900CA8CF5 /* RSTree.framework in Embed Frameworks */, 51C451AA226377C200C03939 /* ArticlesDatabase.framework in Embed Frameworks */, 51C451BE226377D000C03939 /* Account.framework in Embed Frameworks */, 51C451BA226377C900C03939 /* Articles.framework in Embed Frameworks */, 84C37FB620DD8DBB00CA8CF5 /* RSParser.framework in Embed Frameworks */, 65ED42DF235E74230081F399 /* Sparkle.framework in Embed Frameworks */, 51554C25228B71910055115A /* SyncDatabase.framework in Embed Frameworks */, 84C37FA620DD8D8400CA8CF5 /* RSCore.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; D5907C9B20022EC7005947E5 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = TestScripts; dstSubfolderSpec = 7; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 49F40DEF2335B71000552BF4 /* newsfoot.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = newsfoot.js; sourceTree = ""; }; 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = ""; }; 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = ""; }; 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContainerViewController.swift; sourceTree = ""; }; 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSImage-Extensions.swift"; sourceTree = ""; }; 511D43EE231FBDE800FB1562 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreenPad.storyboard; sourceTree = ""; }; 511D4410231FC02D00FB1562 /* KeyboardManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = ""; }; 512363372369155100951F16 /* RoundedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedProgressView.swift; sourceTree = ""; }; 5123DB9E233EC6FD00282CC9 /* FeedInspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorView.swift; sourceTree = ""; }; 5126EE96226CB48A00C22AFC /* SceneCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneCoordinator.swift; sourceTree = ""; }; 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailKeyboardDelegate.swift; sourceTree = ""; }; 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = DetailKeyboardShortcuts.plist; sourceTree = ""; }; 512E08F722688F7C00BDCFDD /* MasterFeedTableViewSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterFeedTableViewSectionHeader.swift; sourceTree = ""; }; 512E092B2268B25500BDCFDD /* UISplitViewController-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISplitViewController-Extensions.swift"; sourceTree = ""; }; 51314617235A797400387FDC /* NetNewsWire_iOSintentextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSintentextension_target.xcconfig; sourceTree = ""; }; 51314637235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "NetNewsWire iOS Intents Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 51314665235A7E4600387FDC /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51314666235A7E4600387FDC /* IntentHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = ""; }; 51314684235A7EB900387FDC /* NetNewsWire_iOS_IntentsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetNewsWire_iOS_IntentsExtension.entitlements; sourceTree = ""; }; 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedIntentHandler.swift; sourceTree = ""; }; 51314706235C41FC00387FDC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; }; 51314714235C420900387FDC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = ""; }; 513228F2233037620033D4ED /* Reachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = ""; }; 513C5CE6232571C2003D4054 /* NetNewsWire iOS Share Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "NetNewsWire iOS Share Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 513C5CE8232571C2003D4054 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; 513C5CEB232571C2003D4054 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; 513C5CED232571C2003D4054 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5142192923522B5500E07E2C /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = ""; }; 514219362352510100E07E2C /* ImageScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageScrollView.swift; sourceTree = ""; }; 5142194A2353C1CF00E07E2C /* main_mac.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = main_mac.js; sourceTree = ""; }; 514219572353C28900E07E2C /* main_ios.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = main_ios.js; sourceTree = ""; }; 5144EA2E2279FAB600D19003 /* AccountsDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsDetailViewController.swift; sourceTree = ""; }; 5144EA352279FC3D00D19003 /* AccountsAddLocal.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsAddLocal.xib; sourceTree = ""; }; 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddLocalWindowController.swift; sourceTree = ""; }; 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ImportOPMLSheet.xib; sourceTree = ""; }; 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportOPMLWindowController.swift; sourceTree = ""; }; 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedbinWindowController.swift; sourceTree = ""; }; 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedbin.xib; sourceTree = ""; }; 5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MasterTimelineTitleView.xib; sourceTree = ""; }; 5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineTitleView.swift; sourceTree = ""; }; 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = ""; }; 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddControllerType.swift; sourceTree = ""; }; 51554BFC228B6EB50055115A /* SyncDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SyncDatabase.xcodeproj; path = Frameworks/SyncDatabase/SyncDatabase.xcodeproj; sourceTree = SOURCE_ROOT; }; 515D4FCB2325815A00EE1167 /* SafariExt.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = SafariExt.js; sourceTree = ""; }; 515D4FCD2325909200EE1167 /* NetNewsWire_iOS_ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetNewsWire_iOS_ShareExtension.entitlements; sourceTree = ""; }; 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSshareextension_target.xcconfig; sourceTree = ""; }; 516A091D23609A3600EAE89B /* SettingsAccountTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsAccountTableViewCell.xib; sourceTree = ""; }; 516A09382360A2AE00EAE89B /* SettingsAccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountTableViewCell.swift; sourceTree = ""; }; 516A093A2360A4A000EAE89B /* SettingsTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsTableViewCell.xib; sourceTree = ""; }; 516A093F2361240900EAE89B /* Account.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Account.storyboard; sourceTree = ""; }; 516A09412361248000EAE89B /* Inspector.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Inspector.storyboard; sourceTree = ""; }; 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareFolderPickerController.swift; sourceTree = ""; }; 517630032336215100E15FFF /* main.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = ""; }; 517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleViewControllerWebViewProvider.swift; sourceTree = ""; }; 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicLabel.swift; sourceTree = ""; }; 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicImageView.swift; sourceTree = ""; }; 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshInterval.swift; sourceTree = ""; }; 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountRefreshTimer.swift; sourceTree = ""; }; 518651AB23555EB20078E021 /* NNW3Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NNW3Document.swift; sourceTree = ""; }; 518651D9235621840078E021 /* ImageTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTransition.swift; sourceTree = ""; }; 5186A634235EF3A800C97195 /* VibrantLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrantLabel.swift; sourceTree = ""; }; 518B2ED22351B3DD00400001 /* NetNewsWire-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "NetNewsWire-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 518B2EE92351B4C200400001 /* NetNewsWire_iOSTests_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSTests_target.xcconfig; sourceTree = ""; }; 51934CC1230F5963006127BE /* ThemedNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedNavigationController.swift; sourceTree = ""; }; 51934CCD2310792F006127BE /* ActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityManager.swift; sourceTree = ""; }; 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTimelineFeedDelegate.swift; sourceTree = ""; }; 519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; }; 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RefreshInterval-Extensions.swift"; sourceTree = ""; }; 519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshIntervalViewController.swift; sourceTree = ""; }; 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalAccountViewController.swift; sourceTree = ""; }; 51A16990235E10D600EB091F /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = ""; }; 51A16991235E10D600EB091F /* AccountInspectorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountInspectorViewController.swift; sourceTree = ""; }; 51A16992235E10D600EB091F /* AddAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddAccountViewController.swift; sourceTree = ""; }; 51A16993235E10D600EB091F /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 51A16995235E10D600EB091F /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbinAccountViewController.swift; sourceTree = ""; }; 51AF460D232488C6001742EF /* Account-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account-Extensions.swift"; sourceTree = ""; }; 51B62E67233186730085F949 /* AvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarView.swift; sourceTree = ""; }; 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = ""; }; 51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; 51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = ""; }; 51C45250226506F400C03939 /* String-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String-Extensions.swift"; sourceTree = ""; }; 51C45254226507D200C03939 /* AppAssets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppAssets.swift; sourceTree = ""; }; 51C45255226507D200C03939 /* AppDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = ""; }; 51C45261226508F600C03939 /* MasterFeedUnreadCountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterFeedUnreadCountView.swift; sourceTree = ""; }; 51C45262226508F600C03939 /* MasterFeedTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterFeedTableViewCell.swift; sourceTree = ""; }; 51C45263226508F600C03939 /* MasterFeedTableViewCellLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterFeedTableViewCellLayout.swift; sourceTree = ""; }; 51C45264226508F600C03939 /* MasterFeedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterFeedViewController.swift; sourceTree = ""; }; 51C4526E2265091600C03939 /* MasterTimelineViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterTimelineViewController.swift; sourceTree = ""; }; 51C452702265091600C03939 /* MultilineUILabelSizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultilineUILabelSizer.swift; sourceTree = ""; }; 51C452712265091600C03939 /* MasterTimelineCellData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterTimelineCellData.swift; sourceTree = ""; }; 51C452722265091600C03939 /* MasterTimelineTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterTimelineTableViewCell.swift; sourceTree = ""; }; 51C452742265091600C03939 /* MasterUnreadIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterUnreadIndicatorView.swift; sourceTree = ""; }; 51C452752265091600C03939 /* MasterTimelineDefaultCellLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterTimelineDefaultCellLayout.swift; sourceTree = ""; }; 51C4527E2265092C00C03939 /* ArticleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleViewController.swift; sourceTree = ""; }; 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlattenedAccountFolderPickerData.swift; sourceTree = ""; }; 51C452822265093600C03939 /* Add.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Add.storyboard; sourceTree = ""; }; 51C452842265093600C03939 /* AddFeedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFeedViewController.swift; sourceTree = ""; }; 51C4528B2265095F00C03939 /* AddFolderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderViewController.swift; sourceTree = ""; }; 51C452B32265141B00C03939 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; }; 51C452B72265178500C03939 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = ""; }; 51CC9B3D231720B2000E842F /* MasterFeedDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterFeedDataSource.swift; sourceTree = ""; }; 51CE1C0823621EDA005548FC /* RefreshProgressView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RefreshProgressView.xib; sourceTree = ""; }; 51CE1C0A23622006005548FC /* RefreshProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshProgressView.swift; sourceTree = ""; }; 51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineDataSource.swift; sourceTree = ""; }; 51D87EE02311D34700E63F03 /* ActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityType.swift; sourceTree = ""; }; 51E3EB32229AB02C00645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; 51E3EB3C229AB08300645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleStatusSyncTimer.swift; sourceTree = ""; }; 51EAED95231363EF00A9EEE3 /* NonIntrinsicButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicButton.swift; sourceTree = ""; }; 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FolderTreeMenu.swift; path = AddFeed/FolderTreeMenu.swift; sourceTree = ""; }; 51EC892923511D3B0061B6F6 /* NetNewsWire_project_test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_project_test.xcconfig; sourceTree = ""; }; 51EF0F76227716200050506E /* FaviconGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconGenerator.swift; sourceTree = ""; }; 51EF0F78227716380050506E /* ColorHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorHash.swift; sourceTree = ""; }; 51EF0F7D2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineAccessibilityCellLayout.swift; sourceTree = ""; }; 51EF0F7F2277A8330050506E /* MasterTimelineCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineCellLayout.swift; sourceTree = ""; }; 51EF0F8D2279C9260050506E /* AccountsAdd.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsAdd.xib; sourceTree = ""; }; 51EF0F8F2279C9500050506E /* AccountsAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddViewController.swift; sourceTree = ""; }; 51EF0F912279CA620050506E /* AccountsAddTableCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddTableCellView.swift; sourceTree = ""; }; 51F85BEA22724CB600C787DC /* About.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = About.rtf; sourceTree = ""; }; 51F85BEC227251DF00C787DC /* Acknowledgments.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Acknowledgments.rtf; sourceTree = ""; }; 51F85BEE2272520B00C787DC /* Thanks.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Thanks.rtf; sourceTree = ""; }; 51F85BF02272524100C787DC /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; 51F85BF22272531500C787DC /* Dedication.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Dedication.rtf; sourceTree = ""; }; 51F85BF42273625800C787DC /* Bundle-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle-Extensions.swift"; sourceTree = ""; }; 51F85BF622749FA100C787DC /* UIFont-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont-Extensions.swift"; sourceTree = ""; }; 51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem-Extensions.swift"; sourceTree = ""; }; 51F85BFA2275D85000C787DC /* Array-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array-Extensions.swift"; sourceTree = ""; }; 51F85BFC2275DCA800C787DC /* SingleLineUILabelSizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleLineUILabelSizer.swift; sourceTree = ""; }; 51FA73A32332BE110090D516 /* ArticleExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractor.swift; sourceTree = ""; }; 51FA73A62332BE880090D516 /* ExtractedArticle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtractedArticle.swift; sourceTree = ""; }; 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorConfig.swift; sourceTree = ""; }; 51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = ""; }; 51FD40BD2341555600880194 /* UIImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage-Extensions.swift"; sourceTree = ""; }; 51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineUnreadCountView.swift; sourceTree = ""; }; 51FE10022345529D0056195D /* UserNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationManager.swift; sourceTree = ""; }; 51FFF0C3235EE8E5002762AA /* VibrantButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrantButton.swift; sourceTree = ""; }; 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsReaderAPI.xib; sourceTree = ""; }; 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsReaderAPIWindowController.swift; sourceTree = ""; }; 5F323808231DF9F000706F6B /* VibrantTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrantTableViewCell.swift; sourceTree = ""; }; 6543108B2322D90900658221 /* common */ = {isa = PBXFileReference; lastKnownFileType = folder; path = common; sourceTree = ""; }; 6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 6581C73420CED60100F4AD34 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionHandler.swift; sourceTree = ""; }; 6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionViewController.swift; sourceTree = ""; }; 6581C73C20CED60100F4AD34 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/SafariExtensionViewController.xib; sourceTree = ""; }; 6581C73E20CED60100F4AD34 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6581C73F20CED60100F4AD34 /* netnewswire-subscribe-to-feed.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "netnewswire-subscribe-to-feed.js"; sourceTree = ""; }; 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = ToolbarItemIcon.pdf; sourceTree = ""; }; 6581C74320CED60100F4AD34 /* Subscribe_to_Feed.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Subscribe_to_Feed.entitlements; sourceTree = ""; }; 65ED4083235DEF6C0081F399 /* NetNewsWire.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetNewsWire.app; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED409D235DEF770081F399 /* Subscribe to Feed.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED409F235DEFF00081F399 /* container-migration.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "container-migration.plist"; sourceTree = ""; }; 65ED40F2235DF5E00081F399 /* NetNewsWire_macapp_target_macappstore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_macapp_target_macappstore.xcconfig; sourceTree = ""; }; 65ED4186235E045B0081F399 /* NetNewsWire_safariextension_target_macappstore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_safariextension_target_macappstore.xcconfig; sourceTree = ""; }; 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Sparkle.xcodeproj; path = submodules/Sparkle/Sparkle.xcodeproj; sourceTree = SOURCE_ROOT; }; 8405DD892213E0E3008CE1BF /* DetailContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailContainerView.swift; sourceTree = ""; }; 8405DD9822153B6B008CE1BF /* TimelineContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineContainerView.swift; sourceTree = ""; }; 8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSView-Extensions.swift"; sourceTree = ""; }; 8405DDA122168920008CE1BF /* TimelineTableView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TimelineTableView.xib; sourceTree = ""; }; 8405DDA422168C62008CE1BF /* TimelineContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineContainerViewController.swift; sourceTree = ""; }; 840BEE4021D70E64009BBAFA /* CrashReportWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportWindowController.swift; sourceTree = ""; }; 840D617C2029031C009BC708 /* NetNewsWire.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetNewsWire.app; sourceTree = BUILT_PRODUCTS_DIR; }; 840D617E2029031C009BC708 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 840D61952029031D009BC708 /* NetNewsWire_iOSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetNewsWire_iOSTests.swift; sourceTree = ""; }; 840D61972029031D009BC708 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkCommandValidationStatus.swift; sourceTree = ""; }; 841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NothingInspectorViewController.swift; sourceTree = ""; }; 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderInspectorViewController.swift; sourceTree = ""; }; 841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuiltinSmartFeedInspectorViewController.swift; sourceTree = ""; }; 841D4D542106B3D500DD04E6 /* Articles.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Articles.xcodeproj; path = ../Frameworks/Articles/Articles.xcodeproj; sourceTree = ""; }; 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ArticlesDatabase.xcodeproj; path = ../Frameworks/ArticlesDatabase/ArticlesDatabase.xcodeproj; sourceTree = ""; }; 84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWebViewController.swift; sourceTree = ""; }; 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIconDownloader.swift; sourceTree = ""; }; 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadataDownloader.swift; sourceTree = ""; }; 8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedImageDownloader.swift; sourceTree = ""; }; 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSHTMLMetadata+Extension.swift"; sourceTree = ""; }; 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppNotifications.swift; sourceTree = ""; }; 842E45DC1ED8C54B000A8B52 /* Browser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Browser.swift; sourceTree = ""; }; 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallIconProvider.swift; sourceTree = ""; }; 8444C8F11FED81840051386C /* OPMLExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLExporter.swift; sourceTree = ""; }; 844B5B581FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarKeyboardDelegate.swift; sourceTree = ""; }; 844B5B5A1FEA00FB00C7C76A /* TimelineKeyboardDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineKeyboardDelegate.swift; sourceTree = ""; }; 844B5B641FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = GlobalKeyboardShortcuts.plist; sourceTree = ""; }; 844B5B661FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWIndowKeyboardHandler.swift; sourceTree = ""; }; 844B5B681FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = SidebarKeyboardShortcuts.plist; sourceTree = ""; }; 845213221FCA5B10003B6E93 /* ImageDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = ""; }; 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = TimelineKeyboardShortcuts.plist; sourceTree = ""; }; 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleFaviconDownloader.swift; sourceTree = ""; }; 845A29211FC9251E007B49E3 /* SidebarCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarCellLayout.swift; sourceTree = ""; }; 845A29231FC9255E007B49E3 /* SidebarCellAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarCellAppearance.swift; sourceTree = ""; }; 845B14A51FC2299E0013CF92 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarredFeedDelegate.swift; sourceTree = ""; }; 845EE7C01FC2488C00854A1F /* SmartFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeed.swift; sourceTree = ""; }; 846E77301F6EF5D600A165E2 /* Account.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Account.xcodeproj; path = ../Frameworks/Account/Account.xcodeproj; sourceTree = ""; }; 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkStatusCommand.swift; sourceTree = ""; }; 8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = ""; }; 847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFeedDelegate.swift; sourceTree = ""; }; 847CD6C9232F4CBF00FAC46D /* TimelineAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineAvatarView.swift; sourceTree = ""; }; 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAppleEventDescriptor+UserRecordFields.swift"; sourceTree = ""; }; 848362FC2262A30800DA1D35 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = ""; }; 848362FE2262A30E00DA1D35 /* template.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = template.html; sourceTree = ""; }; 848363012262A3BC00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/AddFeedSheet.xib; sourceTree = SOURCE_ROOT; }; 848363042262A3CC00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/AddFolderSheet.xib; sourceTree = SOURCE_ROOT; }; 848363072262A3DD00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 8483630A2262A3F000DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/RenameSheet.xib; sourceTree = SOURCE_ROOT; }; 8483630D2262A3FE00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Mac/Base.lproj/MainWindow.storyboard; sourceTree = SOURCE_ROOT; }; 848B937121C8C5540038DC0D /* CrashReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashReporter.swift; sourceTree = ""; }; 848D578D21543519005FFAD5 /* PasteboardFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardFeed.swift; sourceTree = ""; }; 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaviconDownloader.swift; sourceTree = ""; }; 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderWindowController.swift; sourceTree = ""; }; 849A97511ED9EAC0007D329B /* AddFeedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedController.swift; path = AddFeed/AddFeedController.swift; sourceTree = ""; }; 849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedWindowController.swift; path = AddFeed/AddFeedWindowController.swift; sourceTree = ""; }; 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleUtilities.swift; sourceTree = ""; }; 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultFeedsImporter.swift; sourceTree = ""; }; 849A975D1ED9EB72007D329B /* MainWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindowController.swift; sourceTree = ""; }; 849A97601ED9EB96007D329B /* SidebarOutlineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarOutlineView.swift; sourceTree = ""; }; 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedTreeControllerDelegate.swift; sourceTree = ""; }; 849A97621ED9EB96007D329B /* SidebarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarViewController.swift; sourceTree = ""; }; 849A97631ED9EB96007D329B /* UnreadCountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadCountView.swift; sourceTree = ""; }; 849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineTableRowView.swift; sourceTree = ""; }; 849A976A1ED9EBC8007D329B /* TimelineTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineTableView.swift; sourceTree = ""; }; 849A976B1ED9EBC8007D329B /* TimelineViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineViewController.swift; sourceTree = ""; }; 849A97701ED9EC04007D329B /* TimelineCellAppearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineCellAppearance.swift; sourceTree = ""; }; 849A97711ED9EC04007D329B /* TimelineCellData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineCellData.swift; sourceTree = ""; }; 849A97721ED9EC04007D329B /* TimelineCellLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineCellLayout.swift; sourceTree = ""; }; 849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleStringFormatter.swift; sourceTree = ""; }; 849A97741ED9EC04007D329B /* TimelineTableCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineTableCellView.swift; sourceTree = ""; }; 849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadIndicatorView.swift; sourceTree = ""; }; 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleRenderer.swift; sourceTree = ""; }; 849A977E1ED9EC42007D329B /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarStatusBarView.swift; sourceTree = ""; }; 849A97871ED9ECEF007D329B /* ArticleStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleStyle.swift; sourceTree = ""; }; 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleStylesManager.swift; sourceTree = ""; }; 849A97971ED9EFAA007D329B /* Node-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Node-Extensions.swift"; sourceTree = ""; }; 849A979E1ED9F130007D329B /* SidebarCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarCell.swift; sourceTree = ""; }; 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FolderTreeControllerDelegate.swift; sourceTree = ""; }; 849ADEE02359817D000E1B81 /* NNW3ImportController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NNW3ImportController.swift; sourceTree = ""; }; 849ADEE523598189000E1B81 /* NNW3OpenPanelAccessoryView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NNW3OpenPanelAccessoryView.xib; sourceTree = ""; }; 849ADEE7235981A0000E1B81 /* NNW3OpenPanelAccessoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NNW3OpenPanelAccessoryViewController.swift; sourceTree = ""; }; 849C64601ED37A5D003D8FC0 /* NetNewsWire.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetNewsWire.app; sourceTree = BUILT_PRODUCTS_DIR; }; 849C64671ED37A5D003D8FC0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 849C64711ED37A5D003D8FC0 /* NetNewsWireTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NetNewsWireTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 849C78872362AAFB009A71E4 /* ExportOPMLSheet.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ExportOPMLSheet.xib; sourceTree = ""; }; 849C78912362AB04009A71E4 /* ExportOPMLWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportOPMLWindowController.swift; sourceTree = ""; }; 849EE70E203919360082A1EA /* AppAssets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAssets.swift; sourceTree = ""; }; 849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServicePickerDelegate.swift; sourceTree = ""; }; 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToMicroBlogCommand.swift; sourceTree = ""; }; 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToMarsEditCommand.swift; sourceTree = ""; }; 84A37CB4201ECD610087C5AF /* RenameWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RenameWindowController.swift; sourceTree = ""; }; 84A3EE52223B667F00557320 /* DefaultFeeds.opml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DefaultFeeds.opml; sourceTree = ""; }; 84AD1EA92031617300BC20B7 /* PasteboardFolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardFolder.swift; sourceTree = ""; }; 84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeedPasteboardWriter.swift; sourceTree = ""; }; 84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarOutlineDataSource.swift; sourceTree = ""; }; 84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SidebarViewController+ContextualMenus.swift"; sourceTree = ""; }; 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteCommand.swift; sourceTree = ""; }; 84BAE64821CEDAF20046DB56 /* CrashReporterWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CrashReporterWindow.xib; sourceTree = ""; }; 84BB0F812333426400DED65E /* NetNewsWire.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetNewsWire.entitlements; sourceTree = ""; }; 84BBB12B20142A4700F054F5 /* Inspector.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Inspector.storyboard; sourceTree = ""; }; 84BBB12C20142A4700F054F5 /* InspectorWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InspectorWindowController.swift; sourceTree = ""; }; 84C37F7A20DD8CF200CA8CF5 /* RSCore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSCore.xcodeproj; path = submodules/RSCore/RSCore.xcodeproj; sourceTree = ""; }; 84C37F8620DD8CF800CA8CF5 /* RSParser.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSParser.xcodeproj; path = submodules/RSParser/RSParser.xcodeproj; sourceTree = ""; }; 84C37F8F20DD8CFD00CA8CF5 /* RSTree.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSTree.xcodeproj; path = submodules/RSTree/RSTree.xcodeproj; sourceTree = ""; }; 84C37F9820DD8D0400CA8CF5 /* RSWeb.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSWeb.xcodeproj; path = submodules/RSWeb/RSWeb.xcodeproj; sourceTree = ""; }; 84C37FB920DD8E0C00CA8CF5 /* RSDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSDatabase.xcodeproj; path = submodules/RSDatabase/RSDatabase.xcodeproj; sourceTree = ""; }; 84C9FC6622629B3900D921D6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 84C9FC6B22629E1200D921D6 /* AdvancedPreferencesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvancedPreferencesViewController.swift; sourceTree = ""; }; 84C9FC6D22629E1200D921D6 /* GeneralPrefencesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralPrefencesViewController.swift; sourceTree = ""; }; 84C9FC6E22629E1200D921D6 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; 84C9FC7022629E1200D921D6 /* AccountsTableViewBackgroundView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsTableViewBackgroundView.swift; sourceTree = ""; }; 84C9FC7122629E1200D921D6 /* AccountsControlsBackgroundView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsControlsBackgroundView.swift; sourceTree = ""; }; 84C9FC7222629E1200D921D6 /* AccountsPreferencesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsPreferencesViewController.swift; sourceTree = ""; }; 84C9FC7422629E1200D921D6 /* AccountsDetail.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsDetail.xib; sourceTree = ""; }; 84C9FC8122629E4800D921D6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Mac/Base.lproj/Preferences.storyboard; sourceTree = SOURCE_ROOT; }; 84C9FC8722629E8F00D921D6 /* KeyboardShortcuts.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = KeyboardShortcuts.html; sourceTree = ""; }; 84C9FC8922629E8F00D921D6 /* Credits.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; 84C9FC8A22629E8F00D921D6 /* NetNewsWire.sdef */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = NetNewsWire.sdef; sourceTree = ""; }; 84C9FC9022629ECB00D921D6 /* NetNewsWire.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetNewsWire.entitlements; sourceTree = ""; }; 84C9FC9122629F2200D921D6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84C9FC9B2262A1A900D921D6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 84C9FC9C2262A1A900D921D6 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84C9FCA02262A1B300D921D6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 84C9FCA32262A1B800D921D6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreenPhone.storyboard; sourceTree = ""; }; 84CAFCA322BC8C08007694F0 /* FetchRequestQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchRequestQueue.swift; sourceTree = ""; }; 84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchRequestOperation.swift; sourceTree = ""; }; 84CBDDAE1FD3674C005A61AA /* Technotes */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Technotes; sourceTree = ""; }; 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeedsController.swift; sourceTree = ""; }; 84D2200922B0BC4B0019E085 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; 84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailStatusBarView.swift; sourceTree = ""; }; 84DEE56422C32CA4005FC42C /* SmartFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeedDelegate.swift; sourceTree = ""; }; 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleLineTextFieldSizer.swift; sourceTree = ""; }; 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextFieldSizer.swift; sourceTree = ""; }; 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = ""; }; 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorAvatarDownloader.swift; sourceTree = ""; }; 84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimelineViewController+ContextualMenus.swift"; sourceTree = ""; }; 84E8E0EA202F693600562D8F /* DetailWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWebView.swift; sourceTree = ""; }; 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticlePasteboardWriter.swift; sourceTree = ""; }; 84F204DF1FAACBB30076E152 /* ArticleArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleArray.swift; sourceTree = ""; }; 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PseudoFeed.swift; sourceTree = ""; }; 84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodayFeedDelegate.swift; sourceTree = ""; }; 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadFeed.swift; sourceTree = ""; }; 84F9EAD1213660A100CF2DE4 /* AppleScriptXCTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleScriptXCTestCase.swift; sourceTree = ""; }; 84F9EAD2213660A100CF2DE4 /* ScriptingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScriptingTests.swift; sourceTree = ""; }; 84F9EAD4213660A100CF2DE4 /* testNameOfAuthors.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testNameOfAuthors.applescript; sourceTree = ""; }; 84F9EAD5213660A100CF2DE4 /* testGetURL.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testGetURL.applescript; sourceTree = ""; }; 84F9EAD6213660A100CF2DE4 /* testNameAndUrlOfEveryFeed.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testNameAndUrlOfEveryFeed.applescript; sourceTree = ""; }; 84F9EAD7213660A100CF2DE4 /* testFeedExists.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testFeedExists.applescript; sourceTree = ""; }; 84F9EAD8213660A100CF2DE4 /* testIterativeCreateAndDeleteFeed.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testIterativeCreateAndDeleteFeed.applescript; sourceTree = ""; }; 84F9EAD9213660A100CF2DE4 /* selectAFeed.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = selectAFeed.applescript; sourceTree = ""; }; 84F9EADA213660A100CF2DE4 /* uiScriptingTestSetup.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = uiScriptingTestSetup.applescript; sourceTree = ""; }; 84F9EADB213660A100CF2DE4 /* testURLsOfCurrentArticle.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testURLsOfCurrentArticle.applescript; sourceTree = ""; }; 84F9EADC213660A100CF2DE4 /* testNameOfEveryFolder.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testNameOfEveryFolder.applescript; sourceTree = ""; }; 84F9EADD213660A100CF2DE4 /* testFeedOPML.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testFeedOPML.applescript; sourceTree = ""; }; 84F9EADE213660A100CF2DE4 /* selectAnArticle.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = selectAnArticle.applescript; sourceTree = ""; }; 84F9EADF213660A100CF2DE4 /* testTitleOfArticlesWhose.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testTitleOfArticlesWhose.applescript; sourceTree = ""; }; 84F9EAE0213660A100CF2DE4 /* testCurrentArticleIsNil.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testCurrentArticleIsNil.applescript; sourceTree = ""; }; 84F9EAE1213660A100CF2DE4 /* testGenericScript.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = testGenericScript.applescript; sourceTree = ""; }; 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = establishMainWindowStartingState.applescript; sourceTree = ""; }; 84F9EAE4213660A100CF2DE4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = ""; }; 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedlyWebWindowController.swift; sourceTree = ""; }; 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedlyWeb.xib; sourceTree = ""; }; B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = ""; }; B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKPreferencesPrivate.h; sourceTree = ""; }; B528F81D23333C7E00E735DD /* page.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; D519E74722EE553300923F27 /* NetNewsWire_safariextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_safariextension_target.xcconfig; sourceTree = ""; }; D553737C20186C1F006D8857 /* Article+Scriptability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Article+Scriptability.swift"; sourceTree = ""; }; D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScriptCommand+NetNewsWire.swift"; sourceTree = ""; }; D5907CDC2002F0BE005947E5 /* NetNewsWire_project_release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_project_release.xcconfig; sourceTree = ""; }; D5907CDD2002F0BE005947E5 /* NetNewsWire_project_debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_project_debug.xcconfig; sourceTree = ""; }; D5907CDE2002F0BE005947E5 /* NetNewsWire_project.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_project.xcconfig; sourceTree = ""; }; D5907CDF2002F0F9005947E5 /* NetNewsWireTests_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWireTests_target.xcconfig; sourceTree = ""; }; D5907CE02002F0FA005947E5 /* NetNewsWire_macapp_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_macapp_target.xcconfig; sourceTree = ""; }; D5907D7E2004AC00005947E5 /* NSApplication+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSApplication+Scriptability.swift"; sourceTree = ""; }; D5907D962004B7EB005947E5 /* Account+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+Scriptability.swift"; sourceTree = ""; }; D5907DB12004BB37005947E5 /* ScriptingObjectContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptingObjectContainer.swift; sourceTree = ""; }; D5A2678B20130ECF00A8D3C0 /* Author+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Author+Scriptability.swift"; sourceTree = ""; }; D5E4CC53202C1361009B4FFC /* AppDelegate+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Scriptability.swift"; sourceTree = ""; }; D5E4CC63202C1AC1009B4FFC /* MainWindowController+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainWindowController+Scriptability.swift"; sourceTree = ""; }; D5F4EDB4200744A700B9E363 /* ScriptingObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptingObject.swift; sourceTree = ""; }; D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Feed+Scriptability.swift"; sourceTree = ""; }; D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Folder+Scriptability.swift"; sourceTree = ""; }; DD82AB09231003F6002269DF /* SharingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharingTests.swift; sourceTree = ""; }; FF3ABF09232599450074C542 /* ArticleSorterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleSorterTests.swift; sourceTree = ""; }; FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleSorter.swift; sourceTree = ""; }; FFD43E372340F320009E5CA3 /* UndoAvailableAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UndoAvailableAlertController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 51314634235A7BBE00387FDC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 513146B6235A8FD000387FDC /* RSDatabase.framework in Frameworks */, 513146BC235A8FD000387FDC /* RSWeb.framework in Frameworks */, 513146C3235A8FDB00387FDC /* ArticlesDatabase.framework in Frameworks */, 513146BF235A8FDB00387FDC /* Account.framework in Frameworks */, 513146C1235A8FDB00387FDC /* Articles.framework in Frameworks */, 513146BA235A8FD000387FDC /* RSTree.framework in Frameworks */, 513146C5235A8FDB00387FDC /* SyncDatabase.framework in Frameworks */, 513146B4235A8FD000387FDC /* RSCore.framework in Frameworks */, 513146B8235A8FD000387FDC /* RSParser.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 513C5CE3232571C2003D4054 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 513C5D0A232574D2003D4054 /* RSWeb.framework in Frameworks */, 513C5D0C232574DA003D4054 /* RSTree.framework in Frameworks */, 513C5CFD2325749A003D4054 /* Account.framework in Frameworks */, 513C5D00232574AF003D4054 /* Articles.framework in Frameworks */, 513C5D08232574C6003D4054 /* RSParser.framework in Frameworks */, 513C5D06232574C0003D4054 /* RSDatabase.framework in Frameworks */, 513C5D0E232574E4003D4054 /* SyncDatabase.framework in Frameworks */, 513C5D04232574B9003D4054 /* RSCore.framework in Frameworks */, 513C5D02232574B4003D4054 /* ArticlesDatabase.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 518B2ECF2351B3DD00400001 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 6581C73020CED60000F4AD34 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 65ED4042235DEF6C0081F399 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 65ED4043235DEF6C0081F399 /* RSWeb.framework in Frameworks */, 65ED4044235DEF6C0081F399 /* RSDatabase.framework in Frameworks */, 65ED4045235DEF6C0081F399 /* RSTree.framework in Frameworks */, 65ED4046235DEF6C0081F399 /* ArticlesDatabase.framework in Frameworks */, 65ED4047235DEF6C0081F399 /* RSParser.framework in Frameworks */, 65ED4048235DEF6C0081F399 /* Account.framework in Frameworks */, 65ED4049235DEF6C0081F399 /* Articles.framework in Frameworks */, 65ED404A235DEF6C0081F399 /* RSCore.framework in Frameworks */, 65ED404B235DEF6C0081F399 /* SyncDatabase.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 65ED4094235DEF770081F399 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 840D61792029031C009BC708 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 51C452B42265141B00C03939 /* WebKit.framework in Frameworks */, 51C451D22264C7F200C03939 /* RSWeb.framework in Frameworks */, 51C451E02264C7F900C03939 /* RSTree.framework in Frameworks */, 51C451F82264C83E00C03939 /* Account.framework in Frameworks */, 51C451F02264C83100C03939 /* ArticlesDatabase.framework in Frameworks */, 51C451F42264C83900C03939 /* Articles.framework in Frameworks */, 51C451E82264C81000C03939 /* RSDatabase.framework in Frameworks */, 51C451EC2264C81B00C03939 /* RSCore.framework in Frameworks */, 51554C30228B71A10055115A /* SyncDatabase.framework in Frameworks */, 51C451E42264C80600C03939 /* RSParser.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 849C645D1ED37A5D003D8FC0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 65ED42DE235E74230081F399 /* Sparkle.framework in Frameworks */, 65ED42D9235E740D0081F399 /* Sparkle.framework in Frameworks */, 84C37FA920DD8D9000CA8CF5 /* RSWeb.framework in Frameworks */, 84C37FC520DD8E1D00CA8CF5 /* RSDatabase.framework in Frameworks */, 84C37FAD20DD8D9900CA8CF5 /* RSTree.framework in Frameworks */, 51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */, 84C37FB520DD8DBB00CA8CF5 /* RSParser.framework in Frameworks */, 51C451BD226377D000C03939 /* Account.framework in Frameworks */, 51C451B9226377C900C03939 /* Articles.framework in Frameworks */, 84C37FA520DD8D8400CA8CF5 /* RSCore.framework in Frameworks */, 51554C24228B71910055115A /* SyncDatabase.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 849C646E1ED37A5D003D8FC0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 511D43CE231FA51100FB1562 /* Resources */ = { isa = PBXGroup; children = ( 844B5B641FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist */, 844B5B681FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist */, 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */, 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */, ); path = Resources; sourceTree = ""; }; 5123DB95233EC69300282CC9 /* Inspector */ = { isa = PBXGroup; children = ( 516A09412361248000EAE89B /* Inspector.storyboard */, 51A16991235E10D600EB091F /* AccountInspectorViewController.swift */, 5123DB9E233EC6FD00282CC9 /* FeedInspectorView.swift */, ); path = Inspector; sourceTree = ""; }; 5127B235222B4849006D641D /* Keyboard */ = { isa = PBXGroup; children = ( 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */, ); path = Keyboard; sourceTree = ""; }; 512E08DD22687FA000BDCFDD /* Tree */ = { isa = PBXGroup; children = ( 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */, 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */, 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */, ); path = Tree; sourceTree = ""; }; 513145F9235A55A700387FDC /* Intents */ = { isa = PBXGroup; children = ( 51314707235C41FC00387FDC /* Intents.intentdefinition */, 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */, ); path = Intents; sourceTree = ""; }; 51314643235A7C2300387FDC /* IntentsExtension */ = { isa = PBXGroup; children = ( 51314666235A7E4600387FDC /* IntentHandler.swift */, 51314665235A7E4600387FDC /* Info.plist */, 51314684235A7EB900387FDC /* NetNewsWire_iOS_IntentsExtension.entitlements */, ); path = IntentsExtension; sourceTree = ""; }; 513228F1233037620033D4ED /* Network */ = { isa = PBXGroup; children = ( 513228F2233037620033D4ED /* Reachability.swift */, ); path = Network; sourceTree = ""; }; 513C5CE7232571C2003D4054 /* ShareExtension */ = { isa = PBXGroup; children = ( 513C5CE8232571C2003D4054 /* ShareViewController.swift */, 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */, 513C5CEA232571C2003D4054 /* MainInterface.storyboard */, 513C5CED232571C2003D4054 /* Info.plist */, 515D4FCB2325815A00EE1167 /* SafariExt.js */, 515D4FCD2325909200EE1167 /* NetNewsWire_iOS_ShareExtension.entitlements */, ); path = ShareExtension; sourceTree = ""; }; 5144EA39227A377700D19003 /* OPML */ = { isa = PBXGroup; children = ( 5144EA3A227A379E00D19003 /* ImportOPMLSheet.xib */, 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */, 849C78872362AAFB009A71E4 /* ExportOPMLSheet.xib */, 849C78912362AB04009A71E4 /* ExportOPMLWindowController.swift */, ); path = OPML; sourceTree = ""; }; 51554BFD228B6EB50055115A /* Products */ = { isa = PBXGroup; children = ( 51554C01228B6EB50055115A /* SyncDatabase.framework */, ); name = Products; sourceTree = ""; }; 516A093E236123A800EAE89B /* Account */ = { isa = PBXGroup; children = ( 516A093F2361240900EAE89B /* Account.storyboard */, 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */, 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */, ); path = Account; sourceTree = ""; }; 5183CCEA226F70350010922C /* Timer */ = { isa = PBXGroup; children = ( 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */, 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */, 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */, ); path = Timer; sourceTree = ""; }; 5183CCEB227117C70010922C /* Settings */ = { isa = PBXGroup; children = ( 51A16990235E10D600EB091F /* Settings.storyboard */, 51A16995235E10D600EB091F /* AboutViewController.swift */, 51A16992235E10D600EB091F /* AddAccountViewController.swift */, 51A1698D235E10D600EB091F /* RefreshIntervalViewController.swift */, 516A09382360A2AE00EAE89B /* SettingsAccountTableViewCell.swift */, 516A091D23609A3600EAE89B /* SettingsAccountTableViewCell.xib */, 516A093A2360A4A000EAE89B /* SettingsTableViewCell.xib */, 51A16993235E10D600EB091F /* SettingsViewController.swift */, ); path = Settings; sourceTree = ""; }; 518651A423555EB20078E021 /* NNW3 */ = { isa = PBXGroup; children = ( 849ADEE02359817D000E1B81 /* NNW3ImportController.swift */, 518651AB23555EB20078E021 /* NNW3Document.swift */, 849ADEE523598189000E1B81 /* NNW3OpenPanelAccessoryView.xib */, 849ADEE7235981A0000E1B81 /* NNW3OpenPanelAccessoryViewController.swift */, ); path = NNW3; sourceTree = ""; }; 51934CCC231078DC006127BE /* Activity */ = { isa = PBXGroup; children = ( 51934CCD2310792F006127BE /* ActivityManager.swift */, 51D87EE02311D34700E63F03 /* ActivityType.swift */, ); path = Activity; sourceTree = ""; }; 519D740423243C68008BB345 /* Model Extensions */ = { isa = PBXGroup; children = ( 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */, 51AF460D232488C6001742EF /* Account-Extensions.swift */, ); path = "Model Extensions"; sourceTree = ""; }; 51C45245226506C800C03939 /* UIKit Extensions */ = { isa = PBXGroup; children = ( 51F85BFA2275D85000C787DC /* Array-Extensions.swift */, 51F85BF42273625800C787DC /* Bundle-Extensions.swift */, 51EAED95231363EF00A9EEE3 /* NonIntrinsicButton.swift */, 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */, 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */, 512363372369155100951F16 /* RoundedProgressView.swift */, 51C45250226506F400C03939 /* String-Extensions.swift */, 51934CC1230F5963006127BE /* ThemedNavigationController.swift */, 51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */, 51F85BF622749FA100C787DC /* UIFont-Extensions.swift */, 51FD40BD2341555600880194 /* UIImage-Extensions.swift */, 512E092B2268B25500BDCFDD /* UISplitViewController-Extensions.swift */, 51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */, 51FFF0C3235EE8E5002762AA /* VibrantButton.swift */, 5186A634235EF3A800C97195 /* VibrantLabel.swift */, 5F323808231DF9F000706F6B /* VibrantTableViewCell.swift */, ); path = "UIKit Extensions"; sourceTree = ""; }; 51C4525D226508F600C03939 /* MasterFeed */ = { isa = PBXGroup; children = ( 51C45264226508F600C03939 /* MasterFeedViewController.swift */, 51CC9B3D231720B2000E842F /* MasterFeedDataSource.swift */, 51CE1C0A23622006005548FC /* RefreshProgressView.swift */, 51CE1C0823621EDA005548FC /* RefreshProgressView.xib */, 51C45260226508F600C03939 /* Cell */, ); path = MasterFeed; sourceTree = ""; }; 51C45260226508F600C03939 /* Cell */ = { isa = PBXGroup; children = ( 512E08F722688F7C00BDCFDD /* MasterFeedTableViewSectionHeader.swift */, 51C45262226508F600C03939 /* MasterFeedTableViewCell.swift */, 51C45263226508F600C03939 /* MasterFeedTableViewCellLayout.swift */, 51C45261226508F600C03939 /* MasterFeedUnreadCountView.swift */, ); path = Cell; sourceTree = ""; }; 51C4526D2265091600C03939 /* MasterTimeline */ = { isa = PBXGroup; children = ( 51C4526E2265091600C03939 /* MasterTimelineViewController.swift */, 51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */, 5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */, 5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */, 51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */, FFD43E372340F320009E5CA3 /* UndoAvailableAlertController.swift */, 51C4526F2265091600C03939 /* Cell */, ); path = MasterTimeline; sourceTree = ""; }; 51C4526F2265091600C03939 /* Cell */ = { isa = PBXGroup; children = ( 51EF0F7D2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift */, 51C452712265091600C03939 /* MasterTimelineCellData.swift */, 51EF0F7F2277A8330050506E /* MasterTimelineCellLayout.swift */, 51C452752265091600C03939 /* MasterTimelineDefaultCellLayout.swift */, 51C452722265091600C03939 /* MasterTimelineTableViewCell.swift */, 51C452742265091600C03939 /* MasterUnreadIndicatorView.swift */, 51C452702265091600C03939 /* MultilineUILabelSizer.swift */, 51F85BFC2275DCA800C787DC /* SingleLineUILabelSizer.swift */, ); path = Cell; sourceTree = ""; }; 51C4527D2265092C00C03939 /* Article */ = { isa = PBXGroup; children = ( 51C4527E2265092C00C03939 /* ArticleViewController.swift */, 517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */, 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */, 5142192923522B5500E07E2C /* ImageViewController.swift */, 514219362352510100E07E2C /* ImageScrollView.swift */, 518651D9235621840078E021 /* ImageTransition.swift */, ); path = Article; sourceTree = ""; }; 51C452802265093600C03939 /* Add */ = { isa = PBXGroup; children = ( 51C452822265093600C03939 /* Add.storyboard */, 51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */, 514B7D1E23219F3C00BAC947 /* AddControllerType.swift */, 51C452842265093600C03939 /* AddFeedViewController.swift */, 51C4528B2265095F00C03939 /* AddFolderViewController.swift */, ); path = Add; sourceTree = ""; }; 51C452A822650DA100C03939 /* Article Rendering */ = { isa = PBXGroup; children = ( 49F40DEF2335B71000552BF4 /* newsfoot.js */, 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */, 848362FE2262A30E00DA1D35 /* template.html */, 517630032336215100E15FFF /* main.js */, ); path = "Article Rendering"; sourceTree = ""; }; 51C452AD2265102800C03939 /* Timeline */ = { isa = PBXGroup; children = ( 84F204DF1FAACBB30076E152 /* ArticleArray.swift */, FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */, 84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */, 84CAFCA322BC8C08007694F0 /* FetchRequestQueue.swift */, ); path = Timeline; sourceTree = ""; }; 51C452B22265141B00C03939 /* Frameworks */ = { isa = PBXGroup; children = ( 51C452B32265141B00C03939 /* WebKit.framework */, ); name = Frameworks; sourceTree = ""; }; 51FA739A2332BDE70090D516 /* Article Extractor */ = { isa = PBXGroup; children = ( 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */, 51FA73A32332BE110090D516 /* ArticleExtractor.swift */, 51FA73A62332BE880090D516 /* ExtractedArticle.swift */, ); path = "Article Extractor"; sourceTree = ""; }; 51FE0FF9234552490056195D /* UserNotifications */ = { isa = PBXGroup; children = ( 51FE10022345529D0056195D /* UserNotificationManager.swift */, ); path = UserNotifications; sourceTree = ""; }; 6581C73620CED60100F4AD34 /* SafariExtension */ = { isa = PBXGroup; children = ( 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */, 6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */, 6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */, 6581C73E20CED60100F4AD34 /* Info.plist */, 6581C73F20CED60100F4AD34 /* netnewswire-subscribe-to-feed.js */, 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */, 6581C74320CED60100F4AD34 /* Subscribe_to_Feed.entitlements */, ); path = SafariExtension; sourceTree = ""; }; 65ED429A235E71B40081F399 /* Products */ = { isa = PBXGroup; children = ( 65ED42B0235E71B40081F399 /* Sparkle.framework */, 65ED42B2235E71B40081F399 /* SparkleCore.framework */, 65ED42B4235E71B40081F399 /* Autoupdate */, 65ED42B6235E71B40081F399 /* org.sparkle-project.InstallerLauncher.xpc */, 65ED42B8235E71B40081F399 /* org.sparkle-project.InstallerConnection.xpc */, 65ED42BA235E71B40081F399 /* org.sparkle-project.InstallerStatus.xpc */, 65ED42BC235E71B40081F399 /* org.sparkle-project.Downloader.xpc */, 65ED42BE235E71B40081F399 /* Sparkle Test App.app */, 65ED42C0235E71B40081F399 /* TestAppHelper.xpc */, 65ED42C2235E71B40081F399 /* Sparkle Unit Tests.xctest */, 65ED42C4235E71B40081F399 /* BinaryDelta */, 65ED42C6235E71B40081F399 /* sparkle.app */, 65ED42C8235E71B40081F399 /* Updater.app */, 65ED42CA235E71B40081F399 /* UI Tests.xctest */, 65ED42CC235E71B40081F399 /* generate_appcast */, 65ED42CE235E71B40081F399 /* libbsdiff.a */, ); name = Products; sourceTree = ""; }; 840716652262A60D00344432 /* Products */ = { isa = PBXGroup; children = ( 8407166A2262A60D00344432 /* Account.framework */, 8407166C2262A60D00344432 /* AccountTests.xctest */, ); name = Products; sourceTree = ""; }; 8407166E2262A60F00344432 /* Products */ = { isa = PBXGroup; children = ( 840716732262A60F00344432 /* Articles.framework */, 840716752262A60F00344432 /* ArticlesTests.xctest */, ); name = Products; sourceTree = ""; }; 8407167A2262A61100344432 /* Products */ = { isa = PBXGroup; children = ( 8407167F2262A61100344432 /* ArticlesDatabase.framework */, 840716812262A61100344432 /* ArticlesDatabaseTests.xctest */, ); name = Products; sourceTree = ""; }; 840D61942029031D009BC708 /* NetNewsWire-iOSTests */ = { isa = PBXGroup; children = ( 840D61952029031D009BC708 /* NetNewsWire_iOSTests.swift */, 840D61972029031D009BC708 /* Info.plist */, ); path = "NetNewsWire-iOSTests"; sourceTree = ""; }; 8426119C1FCB6ED40086A189 /* HTMLMetadata */ = { isa = PBXGroup; children = ( 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */, ); path = HTMLMetadata; sourceTree = ""; }; 842E45E11ED8C681000A8B52 /* MainWindow */ = { isa = PBXGroup; children = ( 8483630C2262A3FE00DA1D35 /* MainWindow.storyboard */, 849A975D1ED9EB72007D329B /* MainWindowController.swift */, 519B8D322143397200FA689C /* SharingServiceDelegate.swift */, 849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */, 51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */, 844B5B6B1FEA224B00C7C76A /* Keyboard */, 849A975F1ED9EB95007D329B /* Sidebar */, 849A97681ED9EBC8007D329B /* Timeline */, 849A977C1ED9EC42007D329B /* Detail */, 849A97551ED9EAC3007D329B /* Add Feed */, 849A97411ED9EAA9007D329B /* Add Folder */, 5144EA39227A377700D19003 /* OPML */, 518651A423555EB20078E021 /* NNW3 */, ); path = MainWindow; sourceTree = ""; }; 8444C9011FED81880051386C /* Exporters */ = { isa = PBXGroup; children = ( 8444C8F11FED81840051386C /* OPMLExporter.swift */, ); path = Exporters; sourceTree = ""; }; 844B5B6A1FEA224000C7C76A /* Keyboard */ = { isa = PBXGroup; children = ( 844B5B581FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift */, ); path = Keyboard; sourceTree = ""; }; 844B5B6B1FEA224B00C7C76A /* Keyboard */ = { isa = PBXGroup; children = ( 844B5B661FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift */, ); path = Keyboard; sourceTree = ""; }; 844B5B6C1FEA282400C7C76A /* Keyboard */ = { isa = PBXGroup; children = ( 844B5B5A1FEA00FB00C7C76A /* TimelineKeyboardDelegate.swift */, ); path = Keyboard; sourceTree = ""; }; 845213211FCA5B10003B6E93 /* Images */ = { isa = PBXGroup; children = ( 845213221FCA5B10003B6E93 /* ImageDownloader.swift */, 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */, 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */, 8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */, 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */, ); path = Images; sourceTree = ""; }; 845A29251FC928C7007B49E3 /* Cell */ = { isa = PBXGroup; children = ( 849A979E1ED9F130007D329B /* SidebarCell.swift */, 845A29231FC9255E007B49E3 /* SidebarCellAppearance.swift */, 845A29211FC9251E007B49E3 /* SidebarCellLayout.swift */, ); path = Cell; sourceTree = ""; }; 84702AB31FA27AE8006B8943 /* Commands */ = { isa = PBXGroup; children = ( 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */, 84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */, 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */, 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */, 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */, ); path = Commands; sourceTree = ""; }; 848B937021C8C5540038DC0D /* CrashReporter */ = { isa = PBXGroup; children = ( 84BAE64821CEDAF20046DB56 /* CrashReporterWindow.xib */, 848B937121C8C5540038DC0D /* CrashReporter.swift */, 840BEE4021D70E64009BBAFA /* CrashReportWindowController.swift */, ); path = CrashReporter; sourceTree = ""; }; 848F6AE31FC29CFA002D422E /* Favicons */ = { isa = PBXGroup; children = ( 51EF0F78227716380050506E /* ColorHash.swift */, 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */, 51EF0F76227716200050506E /* FaviconGenerator.swift */, 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */, 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */, ); path = Favicons; sourceTree = ""; }; 849A97411ED9EAA9007D329B /* Add Folder */ = { isa = PBXGroup; children = ( 848363032262A3CC00DA1D35 /* AddFolderSheet.xib */, 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */, ); name = "Add Folder"; path = AddFolder; sourceTree = ""; }; 849A97551ED9EAC3007D329B /* Add Feed */ = { isa = PBXGroup; children = ( 848363002262A3BC00DA1D35 /* AddFeedSheet.xib */, 849A97511ED9EAC0007D329B /* AddFeedController.swift */, 849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */, 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */, ); name = "Add Feed"; sourceTree = ""; }; 849A97561ED9EB0D007D329B /* Data */ = { isa = PBXGroup; children = ( 849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */, 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */, 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */, ); path = Data; sourceTree = ""; }; 849A975F1ED9EB95007D329B /* Sidebar */ = { isa = PBXGroup; children = ( 849A97621ED9EB96007D329B /* SidebarViewController.swift */, 84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */, 84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */, 849A97601ED9EB96007D329B /* SidebarOutlineView.swift */, 849A97631ED9EB96007D329B /* UnreadCountView.swift */, 848D578D21543519005FFAD5 /* PasteboardFeed.swift */, 84AD1EA92031617300BC20B7 /* PasteboardFolder.swift */, 849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */, 844B5B6A1FEA224000C7C76A /* Keyboard */, 845A29251FC928C7007B49E3 /* Cell */, 84A37CB3201ECD610087C5AF /* Renaming */, ); path = Sidebar; sourceTree = ""; }; 849A97681ED9EBC8007D329B /* Timeline */ = { isa = PBXGroup; children = ( 8405DDA422168C62008CE1BF /* TimelineContainerViewController.swift */, 8405DD9822153B6B008CE1BF /* TimelineContainerView.swift */, 8405DDA122168920008CE1BF /* TimelineTableView.xib */, 849A976B1ED9EBC8007D329B /* TimelineViewController.swift */, 84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */, 849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */, 849A976A1ED9EBC8007D329B /* TimelineTableView.swift */, 844B5B6C1FEA282400C7C76A /* Keyboard */, 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */, 849A976F1ED9EC04007D329B /* Cell */, ); path = Timeline; sourceTree = ""; }; 849A976F1ED9EC04007D329B /* Cell */ = { isa = PBXGroup; children = ( 849A97741ED9EC04007D329B /* TimelineTableCellView.swift */, 849A97701ED9EC04007D329B /* TimelineCellAppearance.swift */, 849A97721ED9EC04007D329B /* TimelineCellLayout.swift */, 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */, 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */, 849A97711ED9EC04007D329B /* TimelineCellData.swift */, 849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */, 847CD6C9232F4CBF00FAC46D /* TimelineAvatarView.swift */, ); path = Cell; sourceTree = ""; }; 849A977C1ED9EC42007D329B /* Detail */ = { isa = PBXGroup; children = ( 849A977E1ED9EC42007D329B /* DetailViewController.swift */, 8405DD892213E0E3008CE1BF /* DetailContainerView.swift */, 84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */, 84E8E0EA202F693600562D8F /* DetailWebView.swift */, 84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */, B528F81D23333C7E00E735DD /* page.html */, 5142194A2353C1CF00E07E2C /* main_mac.js */, 848362FC2262A30800DA1D35 /* styleSheet.css */, 5127B235222B4849006D641D /* Keyboard */, ); path = Detail; sourceTree = ""; }; 849A97861ED9ECEF007D329B /* Article Styles */ = { isa = PBXGroup; children = ( 849A97871ED9ECEF007D329B /* ArticleStyle.swift */, 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */, ); name = "Article Styles"; path = Shared/ArticleStyles; sourceTree = SOURCE_ROOT; }; 849A97961ED9EFAA007D329B /* Extensions */ = { isa = PBXGroup; children = ( 849A97971ED9EFAA007D329B /* Node-Extensions.swift */, 8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */, 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */, ); path = Extensions; sourceTree = ""; }; 849C64571ED37A5D003D8FC0 = { isa = PBXGroup; children = ( 845B14A51FC2299E0013CF92 /* README.md */, 84D2200922B0BC4B0019E085 /* CONTRIBUTING.md */, 84CBDDAE1FD3674C005A61AA /* Technotes */, 84C9FC6522629B3900D921D6 /* Mac */, 84C9FC922262A0E600D921D6 /* iOS */, 84C9FC6822629C9A00D921D6 /* Shared */, 84C9FCA52262A1E600D921D6 /* Tests */, D5907CDA2002F084005947E5 /* xcconfig */, 849C64611ED37A5D003D8FC0 /* Products */, 84C37F7A20DD8CF200CA8CF5 /* RSCore.xcodeproj */, 84C37FB920DD8E0C00CA8CF5 /* RSDatabase.xcodeproj */, 84C37F8620DD8CF800CA8CF5 /* RSParser.xcodeproj */, 84C37F8F20DD8CFD00CA8CF5 /* RSTree.xcodeproj */, 84C37F9820DD8D0400CA8CF5 /* RSWeb.xcodeproj */, 51C452B22265141B00C03939 /* Frameworks */, ); sourceTree = ""; usesTabs = 1; }; 849C64611ED37A5D003D8FC0 /* Products */ = { isa = PBXGroup; children = ( 849C64601ED37A5D003D8FC0 /* NetNewsWire.app */, 849C64711ED37A5D003D8FC0 /* NetNewsWireTests.xctest */, 840D617C2029031C009BC708 /* NetNewsWire.app */, 6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */, 513C5CE6232571C2003D4054 /* NetNewsWire iOS Share Extension.appex */, 518B2ED22351B3DD00400001 /* NetNewsWire-iOSTests.xctest */, 51314637235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex */, 65ED4083235DEF6C0081F399 /* NetNewsWire.app */, 65ED409D235DEF770081F399 /* Subscribe to Feed.appex */, ); name = Products; sourceTree = ""; }; 84A37CB3201ECD610087C5AF /* Renaming */ = { isa = PBXGroup; children = ( 848363092262A3F000DA1D35 /* RenameSheet.xib */, 84A37CB4201ECD610087C5AF /* RenameWindowController.swift */, ); path = Renaming; sourceTree = ""; }; 84BBB12A20142A4700F054F5 /* Inspector */ = { isa = PBXGroup; children = ( 84BBB12B20142A4700F054F5 /* Inspector.storyboard */, 84BBB12C20142A4700F054F5 /* InspectorWindowController.swift */, 8472058020142E8900AD578B /* FeedInspectorViewController.swift */, 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */, 841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */, 841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */, ); path = Inspector; sourceTree = ""; }; 84C37F7B20DD8CF200CA8CF5 /* Products */ = { isa = PBXGroup; children = ( 84C37F8120DD8CF200CA8CF5 /* RSCore.framework */, 84C37F8320DD8CF200CA8CF5 /* RSCoreTests.xctest */, 84C37F8520DD8CF200CA8CF5 /* RSCore.framework */, ); name = Products; sourceTree = ""; }; 84C37F8720DD8CF800CA8CF5 /* Products */ = { isa = PBXGroup; children = ( 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */, 84C37F8E20DD8CF800CA8CF5 /* RSParserTests.xctest */, ); name = Products; sourceTree = ""; }; 84C37F9020DD8CFD00CA8CF5 /* Products */ = { isa = PBXGroup; children = ( 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */, 84C37F9720DD8CFE00CA8CF5 /* RSTreeTests.xctest */, ); name = Products; sourceTree = ""; }; 84C37F9920DD8D0400CA8CF5 /* Products */ = { isa = PBXGroup; children = ( 84C37F9F20DD8D0500CA8CF5 /* RSWeb.framework */, 84C37FA120DD8D0500CA8CF5 /* RSWebTests.xctest */, 84C37FA320DD8D0500CA8CF5 /* RSWeb.framework */, ); name = Products; sourceTree = ""; }; 84C37FBA20DD8E0C00CA8CF5 /* Products */ = { isa = PBXGroup; children = ( 84C37FC020DD8E0C00CA8CF5 /* RSDatabase.framework */, 84C37FC220DD8E0C00CA8CF5 /* RSDatabaseTests.xctest */, 84C37FC420DD8E0C00CA8CF5 /* RSDatabase.framework */, ); name = Products; sourceTree = ""; }; 84C9FC6522629B3900D921D6 /* Mac */ = { isa = PBXGroup; children = ( 848363062262A3DD00DA1D35 /* Main.storyboard */, 84C9FC6622629B3900D921D6 /* AppDelegate.swift */, 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */, 849EE70E203919360082A1EA /* AppAssets.swift */, 842E45DC1ED8C54B000A8B52 /* Browser.swift */, 51E3EB32229AB02C00645299 /* ErrorHandler.swift */, 842E45E11ED8C681000A8B52 /* MainWindow */, 84BBB12A20142A4700F054F5 /* Inspector */, 84C9FC6922629E1200D921D6 /* Preferences */, 848B937021C8C5540038DC0D /* CrashReporter */, D5907D6F2004AB67005947E5 /* Scriptability */, 6581C73620CED60100F4AD34 /* SafariExtension */, 84C9FC8322629E8F00D921D6 /* Resources */, 84FB9A2C1EDCD6A4003D53B9 /* Frameworks */, B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */, B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */, ); path = Mac; sourceTree = ""; }; 84C9FC6822629C9A00D921D6 /* Shared */ = { isa = PBXGroup; children = ( 846E77301F6EF5D600A165E2 /* Account.xcodeproj */, 841D4D542106B3D500DD04E6 /* Articles.xcodeproj */, 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */, 51554BFC228B6EB50055115A /* SyncDatabase.xcodeproj */, 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */, 51C452AD2265102800C03939 /* Timeline */, 84702AB31FA27AE8006B8943 /* Commands */, 51934CCC231078DC006127BE /* Activity */, 51FA739A2332BDE70090D516 /* Article Extractor */, 51C452A822650DA100C03939 /* Article Rendering */, 849A97861ED9ECEF007D329B /* Article Styles */, 84DAEE201F86CAE00058304B /* Importers */, 8444C9011FED81880051386C /* Exporters */, 51FE0FF9234552490056195D /* UserNotifications */, 84F2D5341FC22FCB00998D64 /* SmartFeeds */, 848F6AE31FC29CFA002D422E /* Favicons */, 845213211FCA5B10003B6E93 /* Images */, 8426119C1FCB6ED40086A189 /* HTMLMetadata */, 5183CCEA226F70350010922C /* Timer */, 849A97561ED9EB0D007D329B /* Data */, 512E08DD22687FA000BDCFDD /* Tree */, 849A97961ED9EFAA007D329B /* Extensions */, 513228F1233037620033D4ED /* Network */, 511D43CE231FA51100FB1562 /* Resources */, ); path = Shared; sourceTree = ""; }; 84C9FC6922629E1200D921D6 /* Preferences */ = { isa = PBXGroup; children = ( 84C9FC8022629E4800D921D6 /* Preferences.storyboard */, 84C9FC6E22629E1200D921D6 /* PreferencesWindowController.swift */, 84C9FC6A22629E1200D921D6 /* Advanced */, 84C9FC6C22629E1200D921D6 /* General */, 84C9FC6F22629E1200D921D6 /* Accounts */, ); path = Preferences; sourceTree = ""; }; 84C9FC6A22629E1200D921D6 /* Advanced */ = { isa = PBXGroup; children = ( 84C9FC6B22629E1200D921D6 /* AdvancedPreferencesViewController.swift */, ); path = Advanced; sourceTree = ""; }; 84C9FC6C22629E1200D921D6 /* General */ = { isa = PBXGroup; children = ( 84C9FC6D22629E1200D921D6 /* GeneralPrefencesViewController.swift */, ); path = General; sourceTree = ""; }; 84C9FC6F22629E1200D921D6 /* Accounts */ = { isa = PBXGroup; children = ( 84C9FC7022629E1200D921D6 /* AccountsTableViewBackgroundView.swift */, 84C9FC7122629E1200D921D6 /* AccountsControlsBackgroundView.swift */, 84C9FC7222629E1200D921D6 /* AccountsPreferencesViewController.swift */, 51EF0F8D2279C9260050506E /* AccountsAdd.xib */, 51EF0F8F2279C9500050506E /* AccountsAddViewController.swift */, 51EF0F912279CA620050506E /* AccountsAddTableCellView.swift */, 84C9FC7422629E1200D921D6 /* AccountsDetail.xib */, 5144EA2E2279FAB600D19003 /* AccountsDetailViewController.swift */, 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */, 5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */, 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */, 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */, 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */, 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */, 5144EA352279FC3D00D19003 /* AccountsAddLocal.xib */, 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */, ); path = Accounts; sourceTree = ""; }; 84C9FC8322629E8F00D921D6 /* Resources */ = { isa = PBXGroup; children = ( 849C64671ED37A5D003D8FC0 /* Assets.xcassets */, 84C9FC8922629E8F00D921D6 /* Credits.rtf */, 84C9FC8A22629E8F00D921D6 /* NetNewsWire.sdef */, 84C9FC9022629ECB00D921D6 /* NetNewsWire.entitlements */, 84C9FC9122629F2200D921D6 /* Info.plist */, 65ED409F235DEFF00081F399 /* container-migration.plist */, 84C9FC8622629E8F00D921D6 /* KeyboardShortcuts */, ); path = Resources; sourceTree = ""; }; 84C9FC8622629E8F00D921D6 /* KeyboardShortcuts */ = { isa = PBXGroup; children = ( 84C9FC8722629E8F00D921D6 /* KeyboardShortcuts.html */, ); path = KeyboardShortcuts; sourceTree = ""; }; 84C9FC922262A0E600D921D6 /* iOS */ = { isa = PBXGroup; children = ( 84C9FCA22262A1B800D921D6 /* LaunchScreenPhone.storyboard */, 511D43ED231FBDE800FB1562 /* LaunchScreenPad.storyboard */, 84C9FC9F2262A1B300D921D6 /* Main.storyboard */, 840D617E2029031C009BC708 /* AppDelegate.swift */, 519E743422C663F900A78E47 /* SceneDelegate.swift */, 5126EE96226CB48A00C22AFC /* SceneCoordinator.swift */, 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */, 511D4410231FC02D00FB1562 /* KeyboardManager.swift */, 51C45254226507D200C03939 /* AppAssets.swift */, 51C45255226507D200C03939 /* AppDefaults.swift */, 51E3EB3C229AB08300645299 /* ErrorHandler.swift */, 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */, 51B62E67233186730085F949 /* AvatarView.swift */, 51C4525D226508F600C03939 /* MasterFeed */, 51C4526D2265091600C03939 /* MasterTimeline */, 51C4527D2265092C00C03939 /* Article */, 516A093E236123A800EAE89B /* Account */, 51C452802265093600C03939 /* Add */, 5123DB95233EC69300282CC9 /* Inspector */, 513145F9235A55A700387FDC /* Intents */, 5183CCEB227117C70010922C /* Settings */, 519D740423243C68008BB345 /* Model Extensions */, 51C45245226506C800C03939 /* UIKit Extensions */, 513C5CE7232571C2003D4054 /* ShareExtension */, 51314643235A7C2300387FDC /* IntentsExtension */, 84C9FC9A2262A1A900D921D6 /* Resources */, ); path = iOS; sourceTree = ""; }; 84C9FC9A2262A1A900D921D6 /* Resources */ = { isa = PBXGroup; children = ( 51F85BEA22724CB600C787DC /* About.rtf */, 51F85BF02272524100C787DC /* Credits.rtf */, 51F85BEC227251DF00C787DC /* Acknowledgments.rtf */, 51F85BEE2272520B00C787DC /* Thanks.rtf */, 51F85BF22272531500C787DC /* Dedication.rtf */, 51BB7C302335ACDE008E8144 /* page.html */, 514219572353C28900E07E2C /* main_ios.js */, 51C452B72265178500C03939 /* styleSheet.css */, 84C9FC9B2262A1A900D921D6 /* Assets.xcassets */, 84C9FC9C2262A1A900D921D6 /* Info.plist */, 84BB0F812333426400DED65E /* NetNewsWire.entitlements */, ); path = Resources; sourceTree = ""; }; 84C9FCA52262A1E600D921D6 /* Tests */ = { isa = PBXGroup; children = ( 84F9EACF213660A100CF2DE4 /* NetNewsWireTests */, 840D61942029031D009BC708 /* NetNewsWire-iOSTests */, ); path = Tests; sourceTree = ""; }; 84DAEE201F86CAE00058304B /* Importers */ = { isa = PBXGroup; children = ( 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */, 84A3EE52223B667F00557320 /* DefaultFeeds.opml */, ); path = Importers; sourceTree = ""; }; 84F2D5341FC22FCB00998D64 /* SmartFeeds */ = { isa = PBXGroup; children = ( 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */, 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */, 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */, 845EE7C01FC2488C00854A1F /* SmartFeed.swift */, 84DEE56422C32CA4005FC42C /* SmartFeedDelegate.swift */, 84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */, 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */, 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */, 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */, 84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */, ); path = SmartFeeds; sourceTree = ""; }; 84F9EACF213660A100CF2DE4 /* NetNewsWireTests */ = { isa = PBXGroup; children = ( 84F9EAD0213660A100CF2DE4 /* ScriptingTests */, FF3ABF09232599450074C542 /* ArticleSorterTests.swift */, DD82AB09231003F6002269DF /* SharingTests.swift */, 84F9EAE4213660A100CF2DE4 /* Info.plist */, ); path = NetNewsWireTests; sourceTree = ""; }; 84F9EAD0213660A100CF2DE4 /* ScriptingTests */ = { isa = PBXGroup; children = ( 84F9EAD1213660A100CF2DE4 /* AppleScriptXCTestCase.swift */, 84F9EAD2213660A100CF2DE4 /* ScriptingTests.swift */, 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */, 84F9EAD3213660A100CF2DE4 /* scripts */, ); path = ScriptingTests; sourceTree = ""; }; 84F9EAD3213660A100CF2DE4 /* scripts */ = { isa = PBXGroup; children = ( 84F9EAD4213660A100CF2DE4 /* testNameOfAuthors.applescript */, 84F9EAD5213660A100CF2DE4 /* testGetURL.applescript */, 84F9EAD6213660A100CF2DE4 /* testNameAndUrlOfEveryFeed.applescript */, 84F9EAD7213660A100CF2DE4 /* testFeedExists.applescript */, 84F9EAD8213660A100CF2DE4 /* testIterativeCreateAndDeleteFeed.applescript */, 84F9EAD9213660A100CF2DE4 /* selectAFeed.applescript */, 84F9EADA213660A100CF2DE4 /* uiScriptingTestSetup.applescript */, 84F9EADB213660A100CF2DE4 /* testURLsOfCurrentArticle.applescript */, 84F9EADC213660A100CF2DE4 /* testNameOfEveryFolder.applescript */, 84F9EADD213660A100CF2DE4 /* testFeedOPML.applescript */, 84F9EADE213660A100CF2DE4 /* selectAnArticle.applescript */, 84F9EADF213660A100CF2DE4 /* testTitleOfArticlesWhose.applescript */, 84F9EAE0213660A100CF2DE4 /* testCurrentArticleIsNil.applescript */, 84F9EAE1213660A100CF2DE4 /* testGenericScript.applescript */, 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */, ); path = scripts; sourceTree = ""; }; 84FB9A2C1EDCD6A4003D53B9 /* Frameworks */ = { isa = PBXGroup; children = ( 847752FE2008879500D93690 /* CoreServices.framework */, 6581C73420CED60100F4AD34 /* Cocoa.framework */, 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */, ); name = Frameworks; sourceTree = ""; }; D5907CDA2002F084005947E5 /* xcconfig */ = { isa = PBXGroup; children = ( D5907CDD2002F0BE005947E5 /* NetNewsWire_project_debug.xcconfig */, 51EC892923511D3B0061B6F6 /* NetNewsWire_project_test.xcconfig */, D5907CDC2002F0BE005947E5 /* NetNewsWire_project_release.xcconfig */, D5907CDE2002F0BE005947E5 /* NetNewsWire_project.xcconfig */, D5907CE02002F0FA005947E5 /* NetNewsWire_macapp_target.xcconfig */, 65ED40F2235DF5E00081F399 /* NetNewsWire_macapp_target_macappstore.xcconfig */, D5907CDF2002F0F9005947E5 /* NetNewsWireTests_target.xcconfig */, D519E74722EE553300923F27 /* NetNewsWire_safariextension_target.xcconfig */, 65ED4186235E045B0081F399 /* NetNewsWire_safariextension_target_macappstore.xcconfig */, 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */, 51314617235A797400387FDC /* NetNewsWire_iOSintentextension_target.xcconfig */, 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */, 518B2EE92351B4C200400001 /* NetNewsWire_iOSTests_target.xcconfig */, 6543108B2322D90900658221 /* common */, ); path = xcconfig; sourceTree = ""; }; D5907D6F2004AB67005947E5 /* Scriptability */ = { isa = PBXGroup; children = ( D5907D962004B7EB005947E5 /* Account+Scriptability.swift */, D5E4CC53202C1361009B4FFC /* AppDelegate+Scriptability.swift */, D553737C20186C1F006D8857 /* Article+Scriptability.swift */, D5A2678B20130ECF00A8D3C0 /* Author+Scriptability.swift */, D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */, D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */, D5E4CC63202C1AC1009B4FFC /* MainWindowController+Scriptability.swift */, D5907D7E2004AC00005947E5 /* NSApplication+Scriptability.swift */, D5907DB12004BB37005947E5 /* ScriptingObjectContainer.swift */, D5F4EDB4200744A700B9E363 /* ScriptingObject.swift */, D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */, ); path = Scriptability; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 51314636235A7BBE00387FDC /* NetNewsWire iOS Intents Extension */ = { isa = PBXNativeTarget; buildConfigurationList = 5131463F235A7BBE00387FDC /* Build configuration list for PBXNativeTarget "NetNewsWire iOS Intents Extension" */; buildPhases = ( 51314633235A7BBE00387FDC /* Sources */, 51314634235A7BBE00387FDC /* Frameworks */, 51314635235A7BBE00387FDC /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "NetNewsWire iOS Intents Extension"; productName = "NetNewsWire iOS Intents Extension"; productReference = 51314637235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex */; productType = "com.apple.product-type.app-extension"; }; 513C5CE5232571C2003D4054 /* NetNewsWire iOS Share Extension */ = { isa = PBXNativeTarget; buildConfigurationList = 513C5CFC232571C2003D4054 /* Build configuration list for PBXNativeTarget "NetNewsWire iOS Share Extension" */; buildPhases = ( 513C5CE2232571C2003D4054 /* Sources */, 513C5CE3232571C2003D4054 /* Frameworks */, 513C5CE4232571C2003D4054 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "NetNewsWire iOS Share Extension"; productName = "NetNewsWire iOS Share Extension"; productReference = 513C5CE6232571C2003D4054 /* NetNewsWire iOS Share Extension.appex */; productType = "com.apple.product-type.app-extension"; }; 518B2ED12351B3DD00400001 /* NetNewsWire-iOSTests */ = { isa = PBXNativeTarget; buildConfigurationList = 518B2EE72351B3DD00400001 /* Build configuration list for PBXNativeTarget "NetNewsWire-iOSTests" */; buildPhases = ( 518B2ECE2351B3DD00400001 /* Sources */, 518B2ECF2351B3DD00400001 /* Frameworks */, 518B2ED02351B3DD00400001 /* Resources */, ); buildRules = ( ); dependencies = ( 518B2ED82351B3DD00400001 /* PBXTargetDependency */, ); name = "NetNewsWire-iOSTests"; productName = "NetNewsWire-iOSTests"; productReference = 518B2ED22351B3DD00400001 /* NetNewsWire-iOSTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 6581C73220CED60000F4AD34 /* Subscribe to Feed */ = { isa = PBXNativeTarget; buildConfigurationList = 6581C75620CED60100F4AD34 /* Build configuration list for PBXNativeTarget "Subscribe to Feed" */; buildPhases = ( 6581C72F20CED60000F4AD34 /* Sources */, 6581C73020CED60000F4AD34 /* Frameworks */, 6581C73120CED60000F4AD34 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "Subscribe to Feed"; productName = "Subscribe to Feed"; productReference = 6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */; productType = "com.apple.product-type.app-extension"; }; 65ED3FA2235DEF6C0081F399 /* NetNewsWire MAS */ = { isa = PBXNativeTarget; buildConfigurationList = 65ED407F235DEF6C0081F399 /* Build configuration list for PBXNativeTarget "NetNewsWire MAS" */; buildPhases = ( 65ED3FB5235DEF6C0081F399 /* Run Script: Update ArticleExtractorConfig.swift */, 65ED3FB6235DEF6C0081F399 /* Sources */, 65ED4041235DEF6C0081F399 /* Run Script: Reset ArticleExtractorConfig.swift */, 65ED4042235DEF6C0081F399 /* Frameworks */, 65ED404D235DEF6C0081F399 /* Resources */, 65ED406F235DEF6C0081F399 /* Run Script: Automated build numbers */, 65ED4070235DEF6C0081F399 /* Embed Frameworks */, 65ED407B235DEF6C0081F399 /* Embed App Extensions */, 65ED407D235DEF6C0081F399 /* Run Script: Verify No Build Settings */, ); buildRules = ( ); dependencies = ( 65ED41C7235E615E0081F399 /* PBXTargetDependency */, 65ED3FA3235DEF6C0081F399 /* PBXTargetDependency */, 65ED3FA5235DEF6C0081F399 /* PBXTargetDependency */, 65ED3FA7235DEF6C0081F399 /* PBXTargetDependency */, 65ED3FA9235DEF6C0081F399 /* PBXTargetDependency */, 65ED3FAB235DEF6C0081F399 /* PBXTargetDependency */, 65ED3FAD235DEF6C0081F399 /* PBXTargetDependency */, 65ED3FAF235DEF6C0081F399 /* PBXTargetDependency */, 65ED3FB1235DEF6C0081F399 /* PBXTargetDependency */, 65ED3FB3235DEF6C0081F399 /* PBXTargetDependency */, ); name = "NetNewsWire MAS"; productName = NetNewsWire; productReference = 65ED4083235DEF6C0081F399 /* NetNewsWire.app */; productType = "com.apple.product-type.application"; }; 65ED4090235DEF770081F399 /* Subscribe to Feed MAS */ = { isa = PBXNativeTarget; buildConfigurationList = 65ED4099235DEF770081F399 /* Build configuration list for PBXNativeTarget "Subscribe to Feed MAS" */; buildPhases = ( 65ED4091235DEF770081F399 /* Sources */, 65ED4094235DEF770081F399 /* Frameworks */, 65ED4095235DEF770081F399 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "Subscribe to Feed MAS"; productName = "Subscribe to Feed"; productReference = 65ED409D235DEF770081F399 /* Subscribe to Feed.appex */; productType = "com.apple.product-type.app-extension"; }; 840D617B2029031C009BC708 /* NetNewsWire-iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 840D61A32029031E009BC708 /* Build configuration list for PBXNativeTarget "NetNewsWire-iOS" */; buildPhases = ( 517D2D80233A46ED00FF3E35 /* Run Script: Update ArticleExtractorConfig.swift */, 840D61782029031C009BC708 /* Sources */, 517D2D81233A47AD00FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */, 840D61792029031C009BC708 /* Frameworks */, 840D617A2029031C009BC708 /* Resources */, 51C451DF2264C7F200C03939 /* Embed Frameworks */, 513C5CF1232571C2003D4054 /* Embed App Extensions */, 515D50802326D02600EE1167 /* Run Script: Verify No Build Settings */, ); buildRules = ( ); dependencies = ( 5131463D235A7BBE00387FDC /* PBXTargetDependency */, ); name = "NetNewsWire-iOS"; productName = "NetNewsWire-iOS"; productReference = 840D617C2029031C009BC708 /* NetNewsWire.app */; productType = "com.apple.product-type.application"; }; 849C645F1ED37A5D003D8FC0 /* NetNewsWire */ = { isa = PBXNativeTarget; buildConfigurationList = 849C647A1ED37A5D003D8FC0 /* Build configuration list for PBXNativeTarget "NetNewsWire" */; buildPhases = ( 51D6803823330CFF0097A009 /* Run Script: Update ArticleExtractorConfig.swift */, 849C645C1ED37A5D003D8FC0 /* Sources */, 517D2D82233A53D600FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */, 849C645D1ED37A5D003D8FC0 /* Frameworks */, 849C645E1ED37A5D003D8FC0 /* Resources */, 84C987A52000AC9E0066B150 /* Run Script: Automated build numbers */, 84B06F681ED37B9000F0B54B /* Embed Frameworks */, 6581C75720CED60100F4AD34 /* Embed App Extensions */, 65ED42E0235E74240081F399 /* Embed XPC Services */, D519E77022EE5B4100923F27 /* Run Script: Verify No Build Settings */, 8423E3E3220158E700C3795B /* Run Script: Code Sign Sparkle */, ); buildRules = ( ); dependencies = ( 65ED41C5235E61550081F399 /* PBXTargetDependency */, 84C37FA820DD8D8400CA8CF5 /* PBXTargetDependency */, 84C37FAC20DD8D9000CA8CF5 /* PBXTargetDependency */, 84C37FB020DD8D9900CA8CF5 /* PBXTargetDependency */, 84C37FB820DD8DBB00CA8CF5 /* PBXTargetDependency */, 84C37FC820DD8E1D00CA8CF5 /* PBXTargetDependency */, 51C451AC226377C300C03939 /* PBXTargetDependency */, 51C451BC226377C900C03939 /* PBXTargetDependency */, 51C451C0226377D000C03939 /* PBXTargetDependency */, 51554C27228B71910055115A /* PBXTargetDependency */, 65ED42D0235E71F60081F399 /* PBXTargetDependency */, 65ED42D2235E72000081F399 /* PBXTargetDependency */, 65ED42D4235E72000081F399 /* PBXTargetDependency */, 65ED42D6235E72000081F399 /* PBXTargetDependency */, 65ED42D8235E72000081F399 /* PBXTargetDependency */, ); name = NetNewsWire; productName = NetNewsWire; productReference = 849C64601ED37A5D003D8FC0 /* NetNewsWire.app */; productType = "com.apple.product-type.application"; }; 849C64701ED37A5D003D8FC0 /* NetNewsWireTests */ = { isa = PBXNativeTarget; buildConfigurationList = 849C647D1ED37A5D003D8FC0 /* Build configuration list for PBXNativeTarget "NetNewsWireTests" */; buildPhases = ( 849C646D1ED37A5D003D8FC0 /* Sources */, 849C646E1ED37A5D003D8FC0 /* Frameworks */, 849C646F1ED37A5D003D8FC0 /* Resources */, D5907C9B20022EC7005947E5 /* CopyFiles */, ); buildRules = ( ); dependencies = ( 849C64731ED37A5D003D8FC0 /* PBXTargetDependency */, ); name = NetNewsWireTests; productName = NetNewsWireTests; productReference = 849C64711ED37A5D003D8FC0 /* NetNewsWireTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 849C64581ED37A5D003D8FC0 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1120; LastUpgradeCheck = 0930; ORGANIZATIONNAME = "Ranchero Software"; TargetAttributes = { 51314636235A7BBE00387FDC = { CreatedOnToolsVersion = 11.2; DevelopmentTeam = SHJK2V3AJG; LastSwiftMigration = 1120; ProvisioningStyle = Automatic; }; 513C5CE5232571C2003D4054 = { CreatedOnToolsVersion = 11.0; DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; }; 518B2ED12351B3DD00400001 = { CreatedOnToolsVersion = 11.2; DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; TestTargetID = 840D617B2029031C009BC708; }; 6581C73220CED60000F4AD34 = { DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; }; 65ED3FA2235DEF6C0081F399 = { DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; }; 65ED4090235DEF770081F399 = { DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { enabled = 1; }; }; }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.HardenedRuntime = { enabled = 1; }; }; }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; }; }; buildConfigurationList = 849C645B1ED37A5D003D8FC0 /* Build configuration list for PBXProject "NetNewsWire" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( English, en, Base, ); mainGroup = 849C64571ED37A5D003D8FC0; productRefGroup = 849C64611ED37A5D003D8FC0 /* Products */; projectDirPath = ""; projectReferences = ( { ProductGroup = 840716652262A60D00344432 /* Products */; ProjectRef = 846E77301F6EF5D600A165E2 /* Account.xcodeproj */; }, { ProductGroup = 8407166E2262A60F00344432 /* Products */; ProjectRef = 841D4D542106B3D500DD04E6 /* Articles.xcodeproj */; }, { ProductGroup = 8407167A2262A61100344432 /* Products */; ProjectRef = 841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */; }, { ProductGroup = 84C37F7B20DD8CF200CA8CF5 /* Products */; ProjectRef = 84C37F7A20DD8CF200CA8CF5 /* RSCore.xcodeproj */; }, { ProductGroup = 84C37FBA20DD8E0C00CA8CF5 /* Products */; ProjectRef = 84C37FB920DD8E0C00CA8CF5 /* RSDatabase.xcodeproj */; }, { ProductGroup = 84C37F8720DD8CF800CA8CF5 /* Products */; ProjectRef = 84C37F8620DD8CF800CA8CF5 /* RSParser.xcodeproj */; }, { ProductGroup = 84C37F9020DD8CFD00CA8CF5 /* Products */; ProjectRef = 84C37F8F20DD8CFD00CA8CF5 /* RSTree.xcodeproj */; }, { ProductGroup = 84C37F9920DD8D0400CA8CF5 /* Products */; ProjectRef = 84C37F9820DD8D0400CA8CF5 /* RSWeb.xcodeproj */; }, { ProductGroup = 65ED429A235E71B40081F399 /* Products */; ProjectRef = 65ED4299235E71B40081F399 /* Sparkle.xcodeproj */; }, { ProductGroup = 51554BFD228B6EB50055115A /* Products */; ProjectRef = 51554BFC228B6EB50055115A /* SyncDatabase.xcodeproj */; }, ); projectRoot = ""; targets = ( 849C645F1ED37A5D003D8FC0 /* NetNewsWire */, 65ED3FA2235DEF6C0081F399 /* NetNewsWire MAS */, 849C64701ED37A5D003D8FC0 /* NetNewsWireTests */, 840D617B2029031C009BC708 /* NetNewsWire-iOS */, 6581C73220CED60000F4AD34 /* Subscribe to Feed */, 65ED4090235DEF770081F399 /* Subscribe to Feed MAS */, 513C5CE5232571C2003D4054 /* NetNewsWire iOS Share Extension */, 51314636235A7BBE00387FDC /* NetNewsWire iOS Intents Extension */, 518B2ED12351B3DD00400001 /* NetNewsWire-iOSTests */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ 51554C01228B6EB50055115A /* SyncDatabase.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = SyncDatabase.framework; remoteRef = 51554C00228B6EB50055115A /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42B0235E71B40081F399 /* Sparkle.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = Sparkle.framework; remoteRef = 65ED42AF235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42B2235E71B40081F399 /* SparkleCore.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = SparkleCore.framework; remoteRef = 65ED42B1235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42B4235E71B40081F399 /* Autoupdate */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = Autoupdate; remoteRef = 65ED42B3235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42B6235E71B40081F399 /* org.sparkle-project.InstallerLauncher.xpc */ = { isa = PBXReferenceProxy; fileType = "wrapper.xpc-service"; path = "org.sparkle-project.InstallerLauncher.xpc"; remoteRef = 65ED42B5235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42B8235E71B40081F399 /* org.sparkle-project.InstallerConnection.xpc */ = { isa = PBXReferenceProxy; fileType = "wrapper.xpc-service"; path = "org.sparkle-project.InstallerConnection.xpc"; remoteRef = 65ED42B7235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42BA235E71B40081F399 /* org.sparkle-project.InstallerStatus.xpc */ = { isa = PBXReferenceProxy; fileType = "wrapper.xpc-service"; path = "org.sparkle-project.InstallerStatus.xpc"; remoteRef = 65ED42B9235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42BC235E71B40081F399 /* org.sparkle-project.Downloader.xpc */ = { isa = PBXReferenceProxy; fileType = "wrapper.xpc-service"; path = "org.sparkle-project.Downloader.xpc"; remoteRef = 65ED42BB235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42BE235E71B40081F399 /* Sparkle Test App.app */ = { isa = PBXReferenceProxy; fileType = wrapper.application; path = "Sparkle Test App.app"; remoteRef = 65ED42BD235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42C0235E71B40081F399 /* TestAppHelper.xpc */ = { isa = PBXReferenceProxy; fileType = "wrapper.xpc-service"; path = TestAppHelper.xpc; remoteRef = 65ED42BF235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42C2235E71B40081F399 /* Sparkle Unit Tests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = "Sparkle Unit Tests.xctest"; remoteRef = 65ED42C1235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42C4235E71B40081F399 /* BinaryDelta */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = BinaryDelta; remoteRef = 65ED42C3235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42C6235E71B40081F399 /* sparkle.app */ = { isa = PBXReferenceProxy; fileType = wrapper.application; path = sparkle.app; remoteRef = 65ED42C5235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42C8235E71B40081F399 /* Updater.app */ = { isa = PBXReferenceProxy; fileType = wrapper.application; path = Updater.app; remoteRef = 65ED42C7235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42CA235E71B40081F399 /* UI Tests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = "UI Tests.xctest"; remoteRef = 65ED42C9235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42CC235E71B40081F399 /* generate_appcast */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = generate_appcast; remoteRef = 65ED42CB235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED42CE235E71B40081F399 /* libbsdiff.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libbsdiff.a; remoteRef = 65ED42CD235E71B40081F399 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 8407166A2262A60D00344432 /* Account.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = Account.framework; remoteRef = 840716692262A60D00344432 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 8407166C2262A60D00344432 /* AccountTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = AccountTests.xctest; remoteRef = 8407166B2262A60D00344432 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 840716732262A60F00344432 /* Articles.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = Articles.framework; remoteRef = 840716722262A60F00344432 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 840716752262A60F00344432 /* ArticlesTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = ArticlesTests.xctest; remoteRef = 840716742262A60F00344432 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 8407167F2262A61100344432 /* ArticlesDatabase.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = ArticlesDatabase.framework; remoteRef = 8407167E2262A61100344432 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 840716812262A61100344432 /* ArticlesDatabaseTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = ArticlesDatabaseTests.xctest; remoteRef = 840716802262A61100344432 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 84C37F8120DD8CF200CA8CF5 /* RSCore.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = RSCore.framework; remoteRef = 84C37F8020DD8CF200CA8CF5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 84C37F8320DD8CF200CA8CF5 /* RSCoreTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = RSCoreTests.xctest; remoteRef = 84C37F8220DD8CF200CA8CF5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 84C37F8520DD8CF200CA8CF5 /* RSCore.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = RSCore.framework; remoteRef = 84C37F8420DD8CF200CA8CF5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = RSParser.framework; remoteRef = 84C37F8B20DD8CF800CA8CF5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 84C37F8E20DD8CF800CA8CF5 /* RSParserTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = RSParserTests.xctest; remoteRef = 84C37F8D20DD8CF800CA8CF5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = RSTree.framework; remoteRef = 84C37F9420DD8CFE00CA8CF5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 84C37F9720DD8CFE00CA8CF5 /* RSTreeTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = RSTreeTests.xctest; remoteRef = 84C37F9620DD8CFE00CA8CF5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 84C37F9F20DD8D0500CA8CF5 /* RSWeb.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = RSWeb.framework; remoteRef = 84C37F9E20DD8D0500CA8CF5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 84C37FA120DD8D0500CA8CF5 /* RSWebTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = RSWebTests.xctest; remoteRef = 84C37FA020DD8D0500CA8CF5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 84C37FA320DD8D0500CA8CF5 /* RSWeb.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = RSWeb.framework; remoteRef = 84C37FA220DD8D0500CA8CF5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 84C37FC020DD8E0C00CA8CF5 /* RSDatabase.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = RSDatabase.framework; remoteRef = 84C37FBF20DD8E0C00CA8CF5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 84C37FC220DD8E0C00CA8CF5 /* RSDatabaseTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = RSDatabaseTests.xctest; remoteRef = 84C37FC120DD8E0C00CA8CF5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 84C37FC420DD8E0C00CA8CF5 /* RSDatabase.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = RSDatabase.framework; remoteRef = 84C37FC320DD8E0C00CA8CF5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ 51314635235A7BBE00387FDC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 513C5CE4232571C2003D4054 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */, 513C5CEC232571C2003D4054 /* MainInterface.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 518B2ED02351B3DD00400001 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 6581C73120CED60000F4AD34 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 6581C74220CED60100F4AD34 /* ToolbarItemIcon.pdf in Resources */, 6581C73D20CED60100F4AD34 /* SafariExtensionViewController.xib in Resources */, 6581C74020CED60100F4AD34 /* netnewswire-subscribe-to-feed.js in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 65ED404D235DEF6C0081F399 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 65ED404E235DEF6C0081F399 /* NNW3OpenPanelAccessoryView.xib in Resources */, 65ED404F235DEF6C0081F399 /* GlobalKeyboardShortcuts.plist in Resources */, 65ED4050235DEF6C0081F399 /* DetailKeyboardShortcuts.plist in Resources */, 65ED4051235DEF6C0081F399 /* TimelineKeyboardShortcuts.plist in Resources */, 65ED4052235DEF6C0081F399 /* template.html in Resources */, 65ED4053235DEF6C0081F399 /* AccountsFeedlyWeb.xib in Resources */, 65ED4054235DEF6C0081F399 /* Main.storyboard in Resources */, 65ED4055235DEF6C0081F399 /* AccountsAdd.xib in Resources */, 65ED4056235DEF6C0081F399 /* NetNewsWire.sdef in Resources */, 65ED4057235DEF6C0081F399 /* AccountsDetail.xib in Resources */, 65ED4058235DEF6C0081F399 /* main.js in Resources */, 65ED40A1235DEFF00081F399 /* container-migration.plist in Resources */, 65ED4059235DEF6C0081F399 /* AccountsAddLocal.xib in Resources */, 65ED405A235DEF6C0081F399 /* main_mac.js in Resources */, 65ED405B235DEF6C0081F399 /* KeyboardShortcuts.html in Resources */, 65ED405C235DEF6C0081F399 /* ImportOPMLSheet.xib in Resources */, 65ED405D235DEF6C0081F399 /* SidebarKeyboardShortcuts.plist in Resources */, 65ED405E235DEF6C0081F399 /* DefaultFeeds.opml in Resources */, 65ED405F235DEF6C0081F399 /* Preferences.storyboard in Resources */, 65ED4060235DEF6C0081F399 /* (null) in Resources */, 65ED4061235DEF6C0081F399 /* Assets.xcassets in Resources */, 65ED4062235DEF6C0081F399 /* styleSheet.css in Resources */, 65ED4063235DEF6C0081F399 /* RenameSheet.xib in Resources */, 65ED4064235DEF6C0081F399 /* AddFolderSheet.xib in Resources */, 65ED4065235DEF6C0081F399 /* AccountsFeedbin.xib in Resources */, 65ED4066235DEF6C0081F399 /* TimelineTableView.xib in Resources */, 65ED4067235DEF6C0081F399 /* page.html in Resources */, 65ED4068235DEF6C0081F399 /* MainWindow.storyboard in Resources */, 65ED4069235DEF6C0081F399 /* AccountsReaderAPI.xib in Resources */, 65ED406A235DEF6C0081F399 /* newsfoot.js in Resources */, 65ED406B235DEF6C0081F399 /* CrashReporterWindow.xib in Resources */, 65ED406C235DEF6C0081F399 /* Credits.rtf in Resources */, 65ED406D235DEF6C0081F399 /* Inspector.storyboard in Resources */, 65ED406E235DEF6C0081F399 /* AddFeedSheet.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 65ED4095235DEF770081F399 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 65ED4096235DEF770081F399 /* ToolbarItemIcon.pdf in Resources */, 65ED4097235DEF770081F399 /* SafariExtensionViewController.xib in Resources */, 65ED4098235DEF770081F399 /* netnewswire-subscribe-to-feed.js in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 840D617A2029031C009BC708 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 517630052336215100E15FFF /* main.js in Resources */, 5148F44B2336DB4700F8CD8B /* MasterTimelineTitleView.xib in Resources */, 511D43D0231FA62500FB1562 /* TimelineKeyboardShortcuts.plist in Resources */, 51C452862265093600C03939 /* Add.storyboard in Resources */, 511D43EF231FBDE900FB1562 /* LaunchScreenPad.storyboard in Resources */, 511D43D2231FA62C00FB1562 /* GlobalKeyboardShortcuts.plist in Resources */, 84C9FCA12262A1B300D921D6 /* Main.storyboard in Resources */, 51BB7C312335ACDE008E8144 /* page.html in Resources */, 516A093723609A3600EAE89B /* SettingsAccountTableViewCell.xib in Resources */, 51F85BF32272531500C787DC /* Dedication.rtf in Resources */, 516A09422361248000EAE89B /* Inspector.storyboard in Resources */, 84C9FCA42262A1B800D921D6 /* LaunchScreenPhone.storyboard in Resources */, 51F85BEB22724CB600C787DC /* About.rtf in Resources */, 51F85BED227251DF00C787DC /* Acknowledgments.rtf in Resources */, 516A093B2360A4A000EAE89B /* SettingsTableViewCell.xib in Resources */, 511D43D1231FA62800FB1562 /* SidebarKeyboardShortcuts.plist in Resources */, 516A09402361240900EAE89B /* Account.storyboard in Resources */, 51C452AB22650DC600C03939 /* template.html in Resources */, 51F85BF12272524100C787DC /* Credits.rtf in Resources */, 84A3EE61223B667F00557320 /* DefaultFeeds.opml in Resources */, 511D43CF231FA62200FB1562 /* DetailKeyboardShortcuts.plist in Resources */, 51A1699A235E10D700EB091F /* Settings.storyboard in Resources */, 49F40DF92335B71000552BF4 /* newsfoot.js in Resources */, 51F85BEF2272520B00C787DC /* Thanks.rtf in Resources */, 51CE1C0923621EDA005548FC /* RefreshProgressView.xib in Resources */, 84C9FC9D2262A1A900D921D6 /* Assets.xcassets in Resources */, 514219582353C28900E07E2C /* main_ios.js in Resources */, 51C452B82265178500C03939 /* styleSheet.css in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 849C645E1ED37A5D003D8FC0 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 849ADEE623598189000E1B81 /* NNW3OpenPanelAccessoryView.xib in Resources */, 844B5B651FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist in Resources */, 5127B23A222B4849006D641D /* DetailKeyboardShortcuts.plist in Resources */, 845479881FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist in Resources */, 848362FF2262A30E00DA1D35 /* template.html in Resources */, 9EA33BBA2318F8C10097B644 /* AccountsFeedlyWeb.xib in Resources */, 848363082262A3DD00DA1D35 /* Main.storyboard in Resources */, 51EF0F8E2279C9260050506E /* AccountsAdd.xib in Resources */, 84C9FC8F22629E8F00D921D6 /* NetNewsWire.sdef in Resources */, 84C9FC7D22629E1200D921D6 /* AccountsDetail.xib in Resources */, 517630042336215100E15FFF /* main.js in Resources */, 65ED40A0235DEFF00081F399 /* container-migration.plist in Resources */, 5144EA362279FC3D00D19003 /* AccountsAddLocal.xib in Resources */, 5142194B2353C1CF00E07E2C /* main_mac.js in Resources */, 84C9FC8C22629E8F00D921D6 /* KeyboardShortcuts.html in Resources */, 5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */, 844B5B691FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist in Resources */, 84A3EE5F223B667F00557320 /* DefaultFeeds.opml in Resources */, 849C78902362AAFC009A71E4 /* ExportOPMLSheet.xib in Resources */, 84C9FC8222629E4800D921D6 /* Preferences.storyboard in Resources */, 849C64681ED37A5D003D8FC0 /* Assets.xcassets in Resources */, 848362FD2262A30800DA1D35 /* styleSheet.css in Resources */, 8483630B2262A3F000DA1D35 /* RenameSheet.xib in Resources */, 848363052262A3CC00DA1D35 /* AddFolderSheet.xib in Resources */, 5144EA52227B8E4500D19003 /* AccountsFeedbin.xib in Resources */, 8405DDA222168920008CE1BF /* TimelineTableView.xib in Resources */, B528F81E23333C7E00E735DD /* page.html in Resources */, 8483630E2262A3FE00DA1D35 /* MainWindow.storyboard in Resources */, 55E15BCB229D65A900D6602A /* AccountsReaderAPI.xib in Resources */, 49F40DF82335B71000552BF4 /* newsfoot.js in Resources */, 84BAE64921CEDAF20046DB56 /* CrashReporterWindow.xib in Resources */, 84C9FC8E22629E8F00D921D6 /* Credits.rtf in Resources */, 84BBB12D20142A4700F054F5 /* Inspector.storyboard in Resources */, 848363022262A3BD00DA1D35 /* AddFeedSheet.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 849C646F1ED37A5D003D8FC0 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 515D50802326D02600EE1167 /* Run Script: Verify No Build Settings */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Run Script: Verify No Build Settings"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n"; }; 517D2D80233A46ED00FF3E35 /* Run Script: Update ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Run Script: Update ArticleExtractorConfig.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "FAILED=false\n\nif [ -z \"${MERCURY_CLIENT_ID}\" ]; then\nFAILED=true\nfi\n\nif [ -z \"${MERCURY_CLIENT_SECRET}\" ]; then\nFAILED=true\nfi\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedbin Mercury credetials. ArticleExtractorConfig.swift not changed.\"\nexit 0\nfi\n\nsed -i .tmp \"s|{MERCURYID}|${MERCURY_CLIENT_ID}|g; s|{MERCURYSECRET}|${MERCURY_CLIENT_SECRET}|g\" \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n\nrm -f \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift.tmp\"\n\necho \"All env values found!\"\n"; }; 517D2D81233A47AD00FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Run Script: Reset ArticleExtractorConfig.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n"; }; 517D2D82233A53D600FF3E35 /* Run Script: Reset ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Run Script: Reset ArticleExtractorConfig.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n"; }; 51D6803823330CFF0097A009 /* Run Script: Update ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Run Script: Update ArticleExtractorConfig.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "FAILED=false\n\nif [ -z \"${MERCURY_CLIENT_ID}\" ]; then\nFAILED=true\nfi\n\nif [ -z \"${MERCURY_CLIENT_SECRET}\" ]; then\nFAILED=true\nfi\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedbin Mercury credetials. ArticleExtractorConfig.swift not changed.\"\nexit 0\nfi\n\nsed -i .tmp \"s|{MERCURYID}|${MERCURY_CLIENT_ID}|g; s|{MERCURYSECRET}|${MERCURY_CLIENT_SECRET}|g\" \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n\nrm -f \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift.tmp\"\n\necho \"All env values found!\"\n\n"; }; 65ED3FB5235DEF6C0081F399 /* Run Script: Update ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Run Script: Update ArticleExtractorConfig.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "FAILED=false\n\nif [ -z \"${MERCURY_CLIENT_ID}\" ]; then\nFAILED=true\nfi\n\nif [ -z \"${MERCURY_CLIENT_SECRET}\" ]; then\nFAILED=true\nfi\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedbin Mercury credetials. ArticleExtractorConfig.swift not changed.\"\nexit 0\nfi\n\nsed -i .tmp \"s|{MERCURYID}|${MERCURY_CLIENT_ID}|g; s|{MERCURYSECRET}|${MERCURY_CLIENT_SECRET}|g\" \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n\nrm -f \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift.tmp\"\n\necho \"All env values found!\"\n\n"; }; 65ED4041235DEF6C0081F399 /* Run Script: Reset ArticleExtractorConfig.swift */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Run Script: Reset ArticleExtractorConfig.swift"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "git checkout \"${SRCROOT}/Shared/Article Extractor/ArticleExtractorConfig.swift\"\n"; }; 65ED406F235DEF6C0081F399 /* Run Script: Automated build numbers */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script: Automated build numbers"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# See https://blog.curtisherbert.com/automated-build-numbers/\n\n# WARNING: If automated build numbers are restored then take \n# care to ensure any app extensions are versioned the same as the app.\n# ask Daniel (jalkut@red-sweater.com) if in doubt about this. \n#git=`sh /etc/profile; which git`\n#branch_name=`$git symbolic-ref HEAD | sed -e 's,.*/\\\\(.*\\\\),\\\\1,'`\n#git_count=`$git rev-list $branch_name |wc -l | sed 's/^ *//;s/ *$//'`\n#simple_branch_name=`$git rev-parse --abbrev-ref HEAD`\n\n#build_number=\"$git_count\"\n#if [ $CONFIGURATION != \"Release\" ]; then\n#build_number+=\"-$simple_branch_name\"\n#fi\n\n#plist=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n#dsym_plist=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n#/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $build_number\" \"$plist\"\n#if [ -f \"$DSYM_INFO_PLIST\" ] ; then\n#/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $build_number\" \"$dsym_plist\"\n#fi\n"; }; 65ED407D235DEF6C0081F399 /* Run Script: Verify No Build Settings */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Run Script: Verify No Build Settings"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n"; }; 8423E3E3220158E700C3795B /* Run Script: Code Sign Sparkle */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Run Script: Code Sign Sparkle"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# Remove unused Sparkle components\nSPARKLE_DIR=\"${CODESIGNING_FOLDER_PATH}/Contents/Frameworks/Sparkle.framework\"\nfind \"${SPARKLE_DIR}\" -name Updater.app -execdir rm -rf {} \\;\nfind \"${SPARKLE_DIR}\" -name Autoupdate -execdir rm -rf {} \\;\n\n# Sign XPC Helpers and their internal binaries\ncodesign --verbose --force -o runtime --preserve-metadata=entitlements --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \"${CODESIGNING_FOLDER_PATH}/Contents/XPCServices/org.sparkle-project.InstallerLauncher.xpc/Contents/MacOS/Updater.app\"\ncodesign --verbose --force -o runtime --preserve-metadata=entitlements --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \"${CODESIGNING_FOLDER_PATH}/Contents/XPCServices/org.sparkle-project.InstallerLauncher.xpc/Contents/MacOS/Autoupdate\"\ncodesign --verbose --entitlements \"${PROJECT_DIR}/submodules/Sparkle/Downloader/org.sparkle-project.Downloader.entitlements\" --force -o runtime --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \"${CODESIGNING_FOLDER_PATH}/Contents/XPCServices/org.sparkle-project.Downloader.xpc\"\ncodesign --verbose --force -o runtime --preserve-metadata=entitlements --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \"${CODESIGNING_FOLDER_PATH}/Contents/XPCServices/org.sparkle-project.InstallerLauncher.xpc\"\ncodesign --verbose --force -o runtime --preserve-metadata=entitlements --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \"${CODESIGNING_FOLDER_PATH}/Contents/XPCServices/org.sparkle-project.InstallerConnection.xpc\"\ncodesign --verbose --force -o runtime --preserve-metadata=entitlements --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \"${CODESIGNING_FOLDER_PATH}/Contents/XPCServices/org.sparkle-project.InstallerStatus.xpc\"\n"; }; 84C987A52000AC9E0066B150 /* Run Script: Automated build numbers */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script: Automated build numbers"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# See https://blog.curtisherbert.com/automated-build-numbers/\n\n# WARNING: If automated build numbers are restored then take \n# care to ensure any app extensions are versioned the same as the app.\n# ask Daniel (jalkut@red-sweater.com) if in doubt about this. \n#git=`sh /etc/profile; which git`\n#branch_name=`$git symbolic-ref HEAD | sed -e 's,.*/\\\\(.*\\\\),\\\\1,'`\n#git_count=`$git rev-list $branch_name |wc -l | sed 's/^ *//;s/ *$//'`\n#simple_branch_name=`$git rev-parse --abbrev-ref HEAD`\n\n#build_number=\"$git_count\"\n#if [ $CONFIGURATION != \"Release\" ]; then\n#build_number+=\"-$simple_branch_name\"\n#fi\n\n#plist=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n#dsym_plist=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n#/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $build_number\" \"$plist\"\n#if [ -f \"$DSYM_INFO_PLIST\" ] ; then\n#/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $build_number\" \"$dsym_plist\"\n#fi\n"; }; D519E77022EE5B4100923F27 /* Run Script: Verify No Build Settings */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Run Script: Verify No Build Settings"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 51314633235A7BBE00387FDC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 513146B3235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */, 51314705235C41FC00387FDC /* Intents.intentdefinition in Sources */, 51314668235A7E4600387FDC /* IntentHandler.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 513C5CE2232571C2003D4054 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 515D4FC123257A3200EE1167 /* FolderTreeControllerDelegate.swift in Sources */, 515D4FCA23257CB500EE1167 /* Node-Extensions.swift in Sources */, 513C5CE9232571C2003D4054 /* ShareViewController.swift in Sources */, 5170743A232AABFC00A461A3 /* FlattenedAccountFolderPickerData.swift in Sources */, 51707439232AA97100A461A3 /* ShareFolderPickerController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 518B2ECE2351B3DD00400001 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 518B2EE82351B45600400001 /* NetNewsWire_iOSTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 6581C72F20CED60000F4AD34 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 6581C73A20CED60100F4AD34 /* SafariExtensionViewController.swift in Sources */, 6581C73820CED60100F4AD34 /* SafariExtensionHandler.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 65ED3FB6235DEF6C0081F399 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */, 65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */, 65ED3FB9235DEF6C0081F399 /* TimelineAvatarView.swift in Sources */, 65ED3FBA235DEF6C0081F399 /* ArticleExtractorConfig.swift in Sources */, 65ED3FBB235DEF6C0081F399 /* InspectorWindowController.swift in Sources */, 65ED3FBC235DEF6C0081F399 /* ColorHash.swift in Sources */, 65ED3FBD235DEF6C0081F399 /* AppDefaults.swift in Sources */, 65ED3FBE235DEF6C0081F399 /* Account+Scriptability.swift in Sources */, 65ED3FBF235DEF6C0081F399 /* NothingInspectorViewController.swift in Sources */, 65ED3FC0235DEF6C0081F399 /* AppNotifications.swift in Sources */, 65ED3FC1235DEF6C0081F399 /* TimelineKeyboardDelegate.swift in Sources */, 65ED3FC2235DEF6C0081F399 /* Browser.swift in Sources */, 65ED3FC3235DEF6C0081F399 /* DetailWebViewController.swift in Sources */, 65ED3FC4235DEF6C0081F399 /* OPMLExporter.swift in Sources */, 65ED3FC5235DEF6C0081F399 /* MainWindowController.swift in Sources */, 65ED3FC6235DEF6C0081F399 /* UnreadFeed.swift in Sources */, 65ED3FC7235DEF6C0081F399 /* Reachability.swift in Sources */, 65ED3FC8235DEF6C0081F399 /* SidebarCellLayout.swift in Sources */, 65ED3FC9235DEF6C0081F399 /* SmartFeedPasteboardWriter.swift in Sources */, 65ED3FCA235DEF6C0081F399 /* SmartFeedsController.swift in Sources */, 65ED3FCB235DEF6C0081F399 /* SidebarViewController.swift in Sources */, 65ED3FCC235DEF6C0081F399 /* AccountsFeedlyWebWindowController.swift in Sources */, 65ED3FCD235DEF6C0081F399 /* SidebarOutlineView.swift in Sources */, 65ED3FCE235DEF6C0081F399 /* DetailKeyboardDelegate.swift in Sources */, 65ED3FCF235DEF6C0081F399 /* TimelineContainerView.swift in Sources */, 65ED3FD0235DEF6C0081F399 /* Author+Scriptability.swift in Sources */, 65ED3FD1235DEF6C0081F399 /* PseudoFeed.swift in Sources */, 65ED3FD2235DEF6C0081F399 /* AccountsAddViewController.swift in Sources */, 65ED3FD3235DEF6C0081F399 /* NSScriptCommand+NetNewsWire.swift in Sources */, 65ED3FD4235DEF6C0081F399 /* Article+Scriptability.swift in Sources */, 65ED3FD5235DEF6C0081F399 /* SmartFeed.swift in Sources */, 65ED3FD6235DEF6C0081F399 /* MarkStatusCommand.swift in Sources */, 65ED3FD7235DEF6C0081F399 /* NSApplication+Scriptability.swift in Sources */, 65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */, 65ED3FD9235DEF6C0081F399 /* SidebarCell.swift in Sources */, 65ED3FDA235DEF6C0081F399 /* ArticleStatusSyncTimer.swift in Sources */, 65ED3FDB235DEF6C0081F399 /* FeedTreeControllerDelegate.swift in Sources */, 65ED3FDC235DEF6C0081F399 /* UnreadCountView.swift in Sources */, 65ED3FDD235DEF6C0081F399 /* ActivityType.swift in Sources */, 65ED3FDE235DEF6C0081F399 /* CrashReportWindowController.swift in Sources */, 65ED3FDF235DEF6C0081F399 /* FeedIconDownloader.swift in Sources */, 65ED3FE0235DEF6C0081F399 /* AccountsControlsBackgroundView.swift in Sources */, 65ED3FE1235DEF6C0081F399 /* MarkCommandValidationStatus.swift in Sources */, 65ED3FE2235DEF6C0081F399 /* ArticlePasteboardWriter.swift in Sources */, 65ED3FE3235DEF6C0081F399 /* ArticleUtilities.swift in Sources */, 65ED3FE4235DEF6C0081F399 /* NNW3OpenPanelAccessoryViewController.swift in Sources */, 65ED3FE5235DEF6C0081F399 /* DefaultFeedsImporter.swift in Sources */, 65ED3FE6235DEF6C0081F399 /* RenameWindowController.swift in Sources */, 65ED3FE7235DEF6C0081F399 /* SendToMicroBlogCommand.swift in Sources */, 65ED3FE8235DEF6C0081F399 /* ArticleStyle.swift in Sources */, 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */, 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */, 65ED3FEB235DEF6C0081F399 /* (null) in Sources */, 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */, 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */, 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */, 65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */, 65ED3FF0235DEF6C0081F399 /* ArticleStylesManager.swift in Sources */, 65ED3FF1235DEF6C0081F399 /* DetailContainerView.swift in Sources */, 65ED3FF2235DEF6C0081F399 /* SharingServiceDelegate.swift in Sources */, 65ED3FF3235DEF6C0081F399 /* ArticleSorter.swift in Sources */, 65ED3FF4235DEF6C0081F399 /* TimelineViewController+ContextualMenus.swift in Sources */, 65ED3FF5235DEF6C0081F399 /* ArticleStringFormatter.swift in Sources */, 65ED3FF6235DEF6C0081F399 /* MultilineTextFieldSizer.swift in Sources */, 65ED3FF7235DEF6C0081F399 /* SearchFeedDelegate.swift in Sources */, 65ED3FF8235DEF6C0081F399 /* ErrorHandler.swift in Sources */, 65ED3FF9235DEF6C0081F399 /* ActivityManager.swift in Sources */, 65ED3FFA235DEF6C0081F399 /* FeedInspectorViewController.swift in Sources */, 65ED3FFB235DEF6C0081F399 /* AccountsReaderAPIWindowController.swift in Sources */, 65ED3FFC235DEF6C0081F399 /* AccountsAddLocalWindowController.swift in Sources */, 65ED3FFD235DEF6C0081F399 /* PasteboardFolder.swift in Sources */, 65ED3FFE235DEF6C0081F399 /* AccountsFeedbinWindowController.swift in Sources */, 65ED3FFF235DEF6C0081F399 /* SidebarOutlineDataSource.swift in Sources */, 65ED4000235DEF6C0081F399 /* SidebarCellAppearance.swift in Sources */, 65ED4001235DEF6C0081F399 /* StarredFeedDelegate.swift in Sources */, 65ED4002235DEF6C0081F399 /* FaviconDownloader.swift in Sources */, 65ED4003235DEF6C0081F399 /* AdvancedPreferencesViewController.swift in Sources */, 65ED4004235DEF6C0081F399 /* SharingServicePickerDelegate.swift in Sources */, 65ED4005235DEF6C0081F399 /* Node-Extensions.swift in Sources */, 65ED4006235DEF6C0081F399 /* AppAssets.swift in Sources */, 65ED4007235DEF6C0081F399 /* AddFeedController.swift in Sources */, 65ED4008235DEF6C0081F399 /* AccountRefreshTimer.swift in Sources */, 65ED4009235DEF6C0081F399 /* SidebarStatusBarView.swift in Sources */, 65ED400A235DEF6C0081F399 /* SearchTimelineFeedDelegate.swift in Sources */, 65ED400B235DEF6C0081F399 /* TodayFeedDelegate.swift in Sources */, 65ED400C235DEF6C0081F399 /* FolderInspectorViewController.swift in Sources */, 65ED400D235DEF6C0081F399 /* SmartFeedDelegate.swift in Sources */, 65ED400E235DEF6C0081F399 /* ImageDownloader.swift in Sources */, 65ED400F235DEF6C0081F399 /* ArticleExtractorButton.swift in Sources */, 65ED4010235DEF6C0081F399 /* AccountsAddTableCellView.swift in Sources */, 65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */, 65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */, 65ED4013235DEF6C0081F399 /* MainWIndowKeyboardHandler.swift in Sources */, 65ED4014235DEF6C0081F399 /* PasteboardFeed.swift in Sources */, 65ED4015235DEF6C0081F399 /* AccountsDetailViewController.swift in Sources */, 65ED4016235DEF6C0081F399 /* DetailViewController.swift in Sources */, 65ED4017235DEF6C0081F399 /* AppDelegate.swift in Sources */, 65ED4018235DEF6C0081F399 /* AccountsTableViewBackgroundView.swift in Sources */, 65ED4019235DEF6C0081F399 /* FetchRequestOperation.swift in Sources */, 65ED401A235DEF6C0081F399 /* HTMLMetadataDownloader.swift in Sources */, 65ED401B235DEF6C0081F399 /* TimelineViewController.swift in Sources */, 65ED401C235DEF6C0081F399 /* FaviconGenerator.swift in Sources */, 65ED401D235DEF6C0081F399 /* RefreshInterval.swift in Sources */, 65ED401E235DEF6C0081F399 /* TimelineCellData.swift in Sources */, 65ED401F235DEF6C0081F399 /* BuiltinSmartFeedInspectorViewController.swift in Sources */, 65ED4020235DEF6C0081F399 /* AppDelegate+Scriptability.swift in Sources */, 65ED4021235DEF6C0081F399 /* NNW3Document.swift in Sources */, 65ED4022235DEF6C0081F399 /* ScriptingObject.swift in Sources */, 65ED4023235DEF6C0081F399 /* Folder+Scriptability.swift in Sources */, 65ED4024235DEF6C0081F399 /* TimelineCellLayout.swift in Sources */, 65ED4025235DEF6C0081F399 /* DetailWebView.swift in Sources */, 65ED4026235DEF6C0081F399 /* TimelineTableRowView.swift in Sources */, 65ED4027235DEF6C0081F399 /* UnreadIndicatorView.swift in Sources */, 65ED4028235DEF6C0081F399 /* ExtractedArticle.swift in Sources */, 65ED4029235DEF6C0081F399 /* DeleteCommand.swift in Sources */, 65ED402A235DEF6C0081F399 /* AddFeedWindowController.swift in Sources */, 65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */, 65ED402C235DEF6C0081F399 /* TimelineTableView.swift in Sources */, 65ED402D235DEF6C0081F399 /* DetailStatusBarView.swift in Sources */, 65ED402E235DEF6C0081F399 /* MainWindowController+Scriptability.swift in Sources */, 65ED402F235DEF6C0081F399 /* PreferencesWindowController.swift in Sources */, 65ED4030235DEF6C0081F399 /* SmallIconProvider.swift in Sources */, 65ED4031235DEF6C0081F399 /* ArticleExtractor.swift in Sources */, 65ED4032235DEF6C0081F399 /* FetchRequestQueue.swift in Sources */, 65ED4033235DEF6C0081F399 /* SidebarKeyboardDelegate.swift in Sources */, 65ED4034235DEF6C0081F399 /* AccountsPreferencesViewController.swift in Sources */, 65ED4035235DEF6C0081F399 /* FolderTreeMenu.swift in Sources */, 65ED4036235DEF6C0081F399 /* NNW3ImportController.swift in Sources */, 65ED4037235DEF6C0081F399 /* FolderTreeControllerDelegate.swift in Sources */, 65ED4038235DEF6C0081F399 /* RSImage-Extensions.swift in Sources */, 65ED4039235DEF6C0081F399 /* SingleFaviconDownloader.swift in Sources */, 65ED403A235DEF6C0081F399 /* Feed+Scriptability.swift in Sources */, 65ED403B235DEF6C0081F399 /* AuthorAvatarDownloader.swift in Sources */, 65ED403C235DEF6C0081F399 /* SingleLineTextFieldSizer.swift in Sources */, 65ED403D235DEF6C0081F399 /* TimelineTableCellView.swift in Sources */, 65ED403E235DEF6C0081F399 /* TimelineCellAppearance.swift in Sources */, 65ED403F235DEF6C0081F399 /* ArticleRenderer.swift in Sources */, 65ED4040235DEF6C0081F399 /* GeneralPrefencesViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 65ED4091235DEF770081F399 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 65ED4092235DEF770081F399 /* SafariExtensionViewController.swift in Sources */, 65ED4093235DEF770081F399 /* SafariExtensionHandler.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 840D61782029031C009BC708 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 840D617F2029031C009BC708 /* AppDelegate.swift in Sources */, 51236339236915B100951F16 /* RoundedProgressView.swift in Sources */, 512E08E72268801200BDCFDD /* FeedTreeControllerDelegate.swift in Sources */, 51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */, 51EF0F79227716380050506E /* ColorHash.swift in Sources */, 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */, 51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */, 51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */, 5186A635235EF3A800C97195 /* VibrantLabel.swift in Sources */, 51F85BF92274AA7B00C787DC /* UIBarButtonItem-Extensions.swift in Sources */, 51B62E68233186730085F949 /* AvatarView.swift in Sources */, 51C45296226509D300C03939 /* OPMLExporter.swift in Sources */, 51C45291226509C800C03939 /* SmartFeed.swift in Sources */, 51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */, 51C45269226508F600C03939 /* MasterFeedTableViewCell.swift in Sources */, 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */, 517630232336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift in Sources */, 51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */, 51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */, 51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */, 513146B2235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */, 51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */, 51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */, 51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */, 516A09392360A2AE00EAE89B /* SettingsAccountTableViewCell.swift in Sources */, 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */, 51A1699C235E10D700EB091F /* AddAccountViewController.swift in Sources */, 51A16999235E10D700EB091F /* LocalAccountViewController.swift in Sources */, 514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */, 51FA73A52332BE110090D516 /* ArticleExtractor.swift in Sources */, 51314704235C41FC00387FDC /* Intents.intentdefinition in Sources */, FF3ABF162325AF5D0074C542 /* ArticleSorter.swift in Sources */, 51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */, 51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */, 51FA73AB2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */, 51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */, 51C4526B226508F600C03939 /* MasterFeedViewController.swift in Sources */, 5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */, 51FD40C72341555A00880194 /* UIImage-Extensions.swift in Sources */, 84CAFCB022BC8C35007694F0 /* FetchRequestOperation.swift in Sources */, 51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */, 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */, 51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */, 51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */, 51F85BF52273625800C787DC /* Bundle-Extensions.swift in Sources */, 51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */, 51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */, 5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */, 512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */, 51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */, 51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */, 51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */, 51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */, 51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */, 514B7D1F23219F3C00BAC947 /* AddControllerType.swift in Sources */, 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */, 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */, 51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */, 5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */, 51C45258226508CF00C03939 /* AppAssets.swift in Sources */, 51FA73A82332BE880090D516 /* ExtractedArticle.swift in Sources */, 51C4527C2265091600C03939 /* MasterTimelineDefaultCellLayout.swift in Sources */, 51C4529A22650A0400C03939 /* ArticleStyle.swift in Sources */, 51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */, 51C4526A226508F600C03939 /* MasterFeedTableViewCellLayout.swift in Sources */, 51C452AE2265104D00C03939 /* ArticleStringFormatter.swift in Sources */, 512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */, 51C4529922650A0000C03939 /* ArticleStylesManager.swift in Sources */, 5123DB9F233EC6FD00282CC9 /* FeedInspectorView.swift in Sources */, 51EF0F802277A8330050506E /* MasterTimelineCellLayout.swift in Sources */, 51F85BF722749FA100C787DC /* UIFont-Extensions.swift in Sources */, 51C452AF2265108300C03939 /* ArticleArray.swift in Sources */, 51C4528E2265099C00C03939 /* SmartFeedsController.swift in Sources */, 51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */, 84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */, 51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */, 51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, 51A1699F235E10D700EB091F /* AboutViewController.swift in Sources */, 51C45290226509C100C03939 /* PseudoFeed.swift in Sources */, 51C452A922650DC600C03939 /* ArticleRenderer.swift in Sources */, 5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */, 51C45297226509E300C03939 /* DefaultFeedsImporter.swift in Sources */, 512E094D2268B8AB00BDCFDD /* DeleteCommand.swift in Sources */, 51F85BFB2275D85000C787DC /* Array-Extensions.swift in Sources */, 51C452AC22650FD200C03939 /* AppNotifications.swift in Sources */, 51EF0F7E2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift in Sources */, 51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */, 51C452762265091600C03939 /* MasterTimelineViewController.swift in Sources */, 5183CCE9226F68D90010922C /* AccountRefreshTimer.swift in Sources */, 51C452882265093600C03939 /* AddFeedViewController.swift in Sources */, 51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */, 51934CCE2310792F006127BE /* ActivityManager.swift in Sources */, 518651DA235621840078E021 /* ImageTransition.swift in Sources */, 514219372352510100E07E2C /* ImageScrollView.swift in Sources */, 51A16997235E10D700EB091F /* RefreshIntervalViewController.swift in Sources */, 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, 84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, 512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */, 519D740623243CC0008BB345 /* RefreshInterval-Extensions.swift in Sources */, 51C45268226508F600C03939 /* MasterFeedUnreadCountView.swift in Sources */, 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */, 51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */, 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */, 51CC9B3E231720B2000E842F /* MasterFeedDataSource.swift in Sources */, FFD43E412340F488009E5CA3 /* UndoAvailableAlertController.swift in Sources */, 51C452A322650A1E00C03939 /* HTMLMetadataDownloader.swift in Sources */, 51C4528D2265095F00C03939 /* AddFolderViewController.swift in Sources */, 51C452782265091600C03939 /* MasterTimelineCellData.swift in Sources */, 5148F4552336DB7000F8CD8B /* MasterTimelineTitleView.swift in Sources */, 51FFF0C4235EE8E5002762AA /* VibrantButton.swift in Sources */, 513228FC233037630033D4ED /* Reachability.swift in Sources */, 51C45259226508D300C03939 /* AppDefaults.swift in Sources */, 51CE1C0B23622007005548FC /* RefreshProgressView.swift in Sources */, 511D4419231FC02D00FB1562 /* KeyboardManager.swift in Sources */, 51A1699D235E10D700EB091F /* SettingsViewController.swift in Sources */, 51C45293226509C800C03939 /* StarredFeedDelegate.swift in Sources */, 51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */, 51934CCB230F599B006127BE /* ThemedNavigationController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 849C645C1ED37A5D003D8FC0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */, 848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */, 847CD6CA232F4CBF00FAC46D /* TimelineAvatarView.swift in Sources */, 51FA73AA2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */, 84BBB12E20142A4700F054F5 /* InspectorWindowController.swift in Sources */, 51EF0F7A22771B890050506E /* ColorHash.swift in Sources */, 84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */, D5907D972004B7EB005947E5 /* Account+Scriptability.swift in Sources */, 841ABA4E20145E7300980E11 /* NothingInspectorViewController.swift in Sources */, 842E45CE1ED8C308000A8B52 /* AppNotifications.swift in Sources */, 844B5B5B1FEA00FB00C7C76A /* TimelineKeyboardDelegate.swift in Sources */, 842E45DD1ED8C54B000A8B52 /* Browser.swift in Sources */, 84216D0322128B9D0049B9B9 /* DetailWebViewController.swift in Sources */, 8444C8F21FED81840051386C /* OPMLExporter.swift in Sources */, 849A975E1ED9EB72007D329B /* MainWindowController.swift in Sources */, 84F2D53A1FC2308B00998D64 /* UnreadFeed.swift in Sources */, 513228FB233037630033D4ED /* Reachability.swift in Sources */, 845A29221FC9251E007B49E3 /* SidebarCellLayout.swift in Sources */, 84AD1EBA2031649C00BC20B7 /* SmartFeedPasteboardWriter.swift in Sources */, 849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */, 84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */, 849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */, 9EA33BB92318F8C10097B644 /* AccountsFeedlyWebWindowController.swift in Sources */, 849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */, 5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */, 8405DD9922153B6B008CE1BF /* TimelineContainerView.swift in Sources */, D5A2678C20130ECF00A8D3C0 /* Author+Scriptability.swift in Sources */, 84F2D5371FC22FCC00998D64 /* PseudoFeed.swift in Sources */, 51EF0F902279C9500050506E /* AccountsAddViewController.swift in Sources */, D57BE6E0204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift in Sources */, D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */, 845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */, 84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */, D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */, 8405DD9C22153BD7008CE1BF /* NSView-Extensions.swift in Sources */, 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */, 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, 849A97651ED9EB96007D329B /* FeedTreeControllerDelegate.swift in Sources */, 849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */, 51FE10092346739D0056195D /* ActivityType.swift in Sources */, 840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */, 8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */, 84C9FC7B22629E1200D921D6 /* AccountsControlsBackgroundView.swift in Sources */, 84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */, 84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */, 849A975B1ED9EB0D007D329B /* ArticleUtilities.swift in Sources */, 849ADEE8235981A0000E1B81 /* NNW3OpenPanelAccessoryViewController.swift in Sources */, 849A975C1ED9EB0D007D329B /* DefaultFeedsImporter.swift in Sources */, 84A37CB5201ECD610087C5AF /* RenameWindowController.swift in Sources */, 84A14FF320048CA70046AD9A /* SendToMicroBlogCommand.swift in Sources */, 849A97891ED9ECEF007D329B /* ArticleStyle.swift in Sources */, 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */, 84B7178C201E66580091657D /* SidebarViewController+ContextualMenus.swift in Sources */, 842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */, 84A1500520048DDF0046AD9A /* SendToMarsEditCommand.swift in Sources */, 51FE10032345529D0056195D /* UserNotificationManager.swift in Sources */, D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */, 849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */, 8405DD8A2213E0E3008CE1BF /* DetailContainerView.swift in Sources */, 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */, FF3ABF1523259DDB0074C542 /* ArticleSorter.swift in Sources */, 84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */, 849A97791ED9EC04007D329B /* ArticleStringFormatter.swift in Sources */, 84E185C3203BB12600F69BFA /* MultilineTextFieldSizer.swift in Sources */, 8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */, 51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */, 51FE100A234673A00056195D /* ActivityManager.swift in Sources */, 8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */, 55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */, 5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */, 84AD1EAA2031617300BC20B7 /* PasteboardFolder.swift in Sources */, 5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */, 84AD1EBC2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift in Sources */, 845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */, 845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */, 848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */, 84C9FC7722629E1200D921D6 /* AdvancedPreferencesViewController.swift in Sources */, 849EE72120391F560082A1EA /* SharingServicePickerDelegate.swift in Sources */, 849A97981ED9EFAA007D329B /* Node-Extensions.swift in Sources */, 849EE70F203919360082A1EA /* AppAssets.swift in Sources */, 849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */, 5183CCE8226F68D90010922C /* AccountRefreshTimer.swift in Sources */, 849A97831ED9EC63007D329B /* SidebarStatusBarView.swift in Sources */, 51938DF2231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */, 84F2D5381FC22FCC00998D64 /* TodayFeedDelegate.swift in Sources */, 841ABA5E20145E9200980E11 /* FolderInspectorViewController.swift in Sources */, 84DEE56522C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, 845213231FCA5B11003B6E93 /* ImageDownloader.swift in Sources */, 51FA73B72332D5F70090D516 /* ArticleExtractorButton.swift in Sources */, 51EF0F922279CA620050506E /* AccountsAddTableCellView.swift in Sources */, 849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */, 8405DDA522168C62008CE1BF /* TimelineContainerViewController.swift in Sources */, 844B5B671FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift in Sources */, 848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */, 5144EA2F2279FAB600D19003 /* AccountsDetailViewController.swift in Sources */, 849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */, 84C9FC6722629B9000D921D6 /* AppDelegate.swift in Sources */, 84C9FC7A22629E1200D921D6 /* AccountsTableViewBackgroundView.swift in Sources */, 84CAFCAF22BC8C35007694F0 /* FetchRequestOperation.swift in Sources */, 8426119E1FCB6ED40086A189 /* HTMLMetadataDownloader.swift in Sources */, 849A976E1ED9EBC8007D329B /* TimelineViewController.swift in Sources */, 5154368B229404D1005E1CDF /* FaviconGenerator.swift in Sources */, 5183CCE6226F4E110010922C /* RefreshInterval.swift in Sources */, 849A97771ED9EC04007D329B /* TimelineCellData.swift in Sources */, 841ABA6020145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift in Sources */, D5E4CC54202C1361009B4FFC /* AppDelegate+Scriptability.swift in Sources */, 518651B223555EB20078E021 /* NNW3Document.swift in Sources */, D5F4EDB5200744A700B9E363 /* ScriptingObject.swift in Sources */, D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */, 849A97781ED9EC04007D329B /* TimelineCellLayout.swift in Sources */, 84E8E0EB202F693600562D8F /* DetailWebView.swift in Sources */, 849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */, 849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */, 51FA73A72332BE880090D516 /* ExtractedArticle.swift in Sources */, 84B99C9D1FAE83C600ECDEDB /* DeleteCommand.swift in Sources */, 849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */, 5144EA40227A37EC00D19003 /* ImportOPMLWindowController.swift in Sources */, 849A976D1ED9EBC8007D329B /* TimelineTableView.swift in Sources */, 84D52E951FE588BB00D14F5B /* DetailStatusBarView.swift in Sources */, D5E4CC64202C1AC1009B4FFC /* MainWindowController+Scriptability.swift in Sources */, 84C9FC7922629E1200D921D6 /* PreferencesWindowController.swift in Sources */, 84411E711FE5FBFA004B527F /* SmallIconProvider.swift in Sources */, 51FA73A42332BE110090D516 /* ArticleExtractor.swift in Sources */, 84CAFCA422BC8C08007694F0 /* FetchRequestQueue.swift in Sources */, 844B5B591FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift in Sources */, 84C9FC7C22629E1200D921D6 /* AccountsPreferencesViewController.swift in Sources */, 51EC114C2149FE3300B296E3 /* FolderTreeMenu.swift in Sources */, 849ADEE42359817E000E1B81 /* NNW3ImportController.swift in Sources */, 849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */, 51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */, 845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */, D5F4EDB720074D6500B9E363 /* Feed+Scriptability.swift in Sources */, 84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */, 84E185B3203B74E500F69BFA /* SingleLineTextFieldSizer.swift in Sources */, 849A977A1ED9EC04007D329B /* TimelineTableCellView.swift in Sources */, 849A97761ED9EC04007D329B /* TimelineCellAppearance.swift in Sources */, 849A977F1ED9EC42007D329B /* ArticleRenderer.swift in Sources */, 84C9FC7822629E1200D921D6 /* GeneralPrefencesViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 849C646D1ED37A5D003D8FC0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 51CE1C712367721A005548FC /* testURLsOfCurrentArticle.applescript in Sources */, FF3ABF13232599810074C542 /* ArticleSorterTests.swift in Sources */, DD82AB0A231003F6002269DF /* SharingTests.swift in Sources */, 84F9EAEB213660A100CF2DE4 /* testIterativeCreateAndDeleteFeed.applescript in Sources */, 84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */, 84F9EAE6213660A100CF2DE4 /* ScriptingTests.swift in Sources */, 847E64A02262783000E00365 /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */, 84F9EAED213660A100CF2DE4 /* uiScriptingTestSetup.applescript in Sources */, 84F9EAE8213660A100CF2DE4 /* testGetURL.applescript in Sources */, 84F9EAEA213660A100CF2DE4 /* testFeedExists.applescript in Sources */, 84F9EAF1213660A100CF2DE4 /* selectAnArticle.applescript in Sources */, 84F9EAE9213660A100CF2DE4 /* testNameAndUrlOfEveryFeed.applescript in Sources */, 84F9EAF3213660A100CF2DE4 /* testCurrentArticleIsNil.applescript in Sources */, 84F9EAE7213660A100CF2DE4 /* testNameOfAuthors.applescript in Sources */, 84F9EAEF213660A100CF2DE4 /* testNameOfEveryFolder.applescript in Sources */, 84F9EAF5213660A100CF2DE4 /* establishMainWindowStartingState.applescript in Sources */, 84F9EAF0213660A100CF2DE4 /* testFeedOPML.applescript in Sources */, 84F9EAF2213660A100CF2DE4 /* testTitleOfArticlesWhose.applescript in Sources */, 84F9EAE5213660A100CF2DE4 /* AppleScriptXCTestCase.swift in Sources */, 84F9EAEC213660A100CF2DE4 /* selectAFeed.applescript in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 5131463D235A7BBE00387FDC /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 51314636235A7BBE00387FDC /* NetNewsWire iOS Intents Extension */; targetProxy = 5131463C235A7BBE00387FDC /* PBXContainerItemProxy */; }; 51554C27228B71910055115A /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SyncDatabase; targetProxy = 51554C26228B71910055115A /* PBXContainerItemProxy */; }; 518B2ED82351B3DD00400001 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 840D617B2029031C009BC708 /* NetNewsWire-iOS */; targetProxy = 518B2ED72351B3DD00400001 /* PBXContainerItemProxy */; }; 51C451AC226377C300C03939 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = ArticlesDatabase; targetProxy = 51C451AB226377C300C03939 /* PBXContainerItemProxy */; }; 51C451BC226377C900C03939 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = Articles; targetProxy = 51C451BB226377C900C03939 /* PBXContainerItemProxy */; }; 51C451C0226377D000C03939 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = Account; targetProxy = 51C451BF226377D000C03939 /* PBXContainerItemProxy */; }; 65ED3FA3235DEF6C0081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RSCore; targetProxy = 65ED3FA4235DEF6C0081F399 /* PBXContainerItemProxy */; }; 65ED3FA5235DEF6C0081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RSWeb; targetProxy = 65ED3FA6235DEF6C0081F399 /* PBXContainerItemProxy */; }; 65ED3FA7235DEF6C0081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RSTree; targetProxy = 65ED3FA8235DEF6C0081F399 /* PBXContainerItemProxy */; }; 65ED3FA9235DEF6C0081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RSParser; targetProxy = 65ED3FAA235DEF6C0081F399 /* PBXContainerItemProxy */; }; 65ED3FAB235DEF6C0081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RSDatabase; targetProxy = 65ED3FAC235DEF6C0081F399 /* PBXContainerItemProxy */; }; 65ED3FAD235DEF6C0081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = ArticlesDatabase; targetProxy = 65ED3FAE235DEF6C0081F399 /* PBXContainerItemProxy */; }; 65ED3FAF235DEF6C0081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = Articles; targetProxy = 65ED3FB0235DEF6C0081F399 /* PBXContainerItemProxy */; }; 65ED3FB1235DEF6C0081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = Account; targetProxy = 65ED3FB2235DEF6C0081F399 /* PBXContainerItemProxy */; }; 65ED3FB3235DEF6C0081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SyncDatabase; targetProxy = 65ED3FB4235DEF6C0081F399 /* PBXContainerItemProxy */; }; 65ED41C5235E61550081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 6581C73220CED60000F4AD34 /* Subscribe to Feed */; targetProxy = 65ED41C4235E61550081F399 /* PBXContainerItemProxy */; }; 65ED41C7235E615E0081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 65ED4090235DEF770081F399 /* Subscribe to Feed MAS */; targetProxy = 65ED41C6235E615E0081F399 /* PBXContainerItemProxy */; }; 65ED42D0235E71F60081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = Sparkle; targetProxy = 65ED42CF235E71F60081F399 /* PBXContainerItemProxy */; }; 65ED42D2235E72000081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SparkleDownloader; targetProxy = 65ED42D1235E72000081F399 /* PBXContainerItemProxy */; }; 65ED42D4235E72000081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SparkleInstallerConnection; targetProxy = 65ED42D3235E72000081F399 /* PBXContainerItemProxy */; }; 65ED42D6235E72000081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SparkleInstallerLauncher; targetProxy = 65ED42D5235E72000081F399 /* PBXContainerItemProxy */; }; 65ED42D8235E72000081F399 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SparkleInstallerStatus; targetProxy = 65ED42D7235E72000081F399 /* PBXContainerItemProxy */; }; 849C64731ED37A5D003D8FC0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 849C645F1ED37A5D003D8FC0 /* NetNewsWire */; targetProxy = 849C64721ED37A5D003D8FC0 /* PBXContainerItemProxy */; }; 84C37FA820DD8D8400CA8CF5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RSCore; targetProxy = 84C37FA720DD8D8400CA8CF5 /* PBXContainerItemProxy */; }; 84C37FAC20DD8D9000CA8CF5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RSWeb; targetProxy = 84C37FAB20DD8D9000CA8CF5 /* PBXContainerItemProxy */; }; 84C37FB020DD8D9900CA8CF5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RSTree; targetProxy = 84C37FAF20DD8D9900CA8CF5 /* PBXContainerItemProxy */; }; 84C37FB820DD8DBB00CA8CF5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RSParser; targetProxy = 84C37FB720DD8DBB00CA8CF5 /* PBXContainerItemProxy */; }; 84C37FC820DD8E1D00CA8CF5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RSDatabase; targetProxy = 84C37FC720DD8E1D00CA8CF5 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 511D43ED231FBDE800FB1562 /* LaunchScreenPad.storyboard */ = { isa = PBXVariantGroup; children = ( 511D43EE231FBDE800FB1562 /* Base */, ); name = LaunchScreenPad.storyboard; sourceTree = ""; }; 51314707235C41FC00387FDC /* Intents.intentdefinition */ = { isa = PBXVariantGroup; children = ( 51314706235C41FC00387FDC /* Base */, 51314714235C420900387FDC /* en */, ); name = Intents.intentdefinition; sourceTree = ""; }; 513C5CEA232571C2003D4054 /* MainInterface.storyboard */ = { isa = PBXVariantGroup; children = ( 513C5CEB232571C2003D4054 /* Base */, ); name = MainInterface.storyboard; sourceTree = ""; }; 6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */ = { isa = PBXVariantGroup; children = ( 6581C73C20CED60100F4AD34 /* Base */, ); name = SafariExtensionViewController.xib; sourceTree = ""; }; 848363002262A3BC00DA1D35 /* AddFeedSheet.xib */ = { isa = PBXVariantGroup; children = ( 848363012262A3BC00DA1D35 /* Base */, ); name = AddFeedSheet.xib; sourceTree = ""; }; 848363032262A3CC00DA1D35 /* AddFolderSheet.xib */ = { isa = PBXVariantGroup; children = ( 848363042262A3CC00DA1D35 /* Base */, ); name = AddFolderSheet.xib; sourceTree = ""; }; 848363062262A3DD00DA1D35 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 848363072262A3DD00DA1D35 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 848363092262A3F000DA1D35 /* RenameSheet.xib */ = { isa = PBXVariantGroup; children = ( 8483630A2262A3F000DA1D35 /* Base */, ); name = RenameSheet.xib; sourceTree = ""; }; 8483630C2262A3FE00DA1D35 /* MainWindow.storyboard */ = { isa = PBXVariantGroup; children = ( 8483630D2262A3FE00DA1D35 /* Base */, ); name = MainWindow.storyboard; sourceTree = ""; }; 84C9FC8022629E4800D921D6 /* Preferences.storyboard */ = { isa = PBXVariantGroup; children = ( 84C9FC8122629E4800D921D6 /* Base */, ); name = Preferences.storyboard; sourceTree = ""; }; 84C9FC9F2262A1B300D921D6 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 84C9FCA02262A1B300D921D6 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 84C9FCA22262A1B800D921D6 /* LaunchScreenPhone.storyboard */ = { isa = PBXVariantGroup; children = ( 84C9FCA32262A1B800D921D6 /* Base */, ); name = LaunchScreenPhone.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 51314640235A7BBE00387FDC /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 51314617235A797400387FDC /* NetNewsWire_iOSintentextension_target.xcconfig */; buildSettings = { }; name = Debug; }; 51314641235A7BBE00387FDC /* Test */ = { isa = XCBuildConfiguration; baseConfigurationReference = 51314617235A797400387FDC /* NetNewsWire_iOSintentextension_target.xcconfig */; buildSettings = { }; name = Test; }; 51314642235A7BBE00387FDC /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 51314617235A797400387FDC /* NetNewsWire_iOSintentextension_target.xcconfig */; buildSettings = { }; name = Release; }; 513C5CF2232571C2003D4054 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */; buildSettings = { }; name = Debug; }; 513C5CF3232571C2003D4054 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */; buildSettings = { }; name = Release; }; 518B2ED92351B3DD00400001 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 518B2EE92351B4C200400001 /* NetNewsWire_iOSTests_target.xcconfig */; buildSettings = { }; name = Debug; }; 518B2EDA2351B3DD00400001 /* Test */ = { isa = XCBuildConfiguration; baseConfigurationReference = 518B2EE92351B4C200400001 /* NetNewsWire_iOSTests_target.xcconfig */; buildSettings = { }; name = Test; }; 518B2EDB2351B3DD00400001 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 518B2EE92351B4C200400001 /* NetNewsWire_iOSTests_target.xcconfig */; buildSettings = { }; name = Release; }; 51EC892A23511DA80061B6F6 /* Test */ = { isa = XCBuildConfiguration; baseConfigurationReference = 51EC892923511D3B0061B6F6 /* NetNewsWire_project_test.xcconfig */; buildSettings = { }; name = Test; }; 51EC892B23511DA80061B6F6 /* Test */ = { isa = XCBuildConfiguration; baseConfigurationReference = D5907CE02002F0FA005947E5 /* NetNewsWire_macapp_target.xcconfig */; buildSettings = { }; name = Test; }; 51EC892C23511DA80061B6F6 /* Test */ = { isa = XCBuildConfiguration; baseConfigurationReference = D5907CDF2002F0F9005947E5 /* NetNewsWireTests_target.xcconfig */; buildSettings = { }; name = Test; }; 51EC892D23511DA80061B6F6 /* Test */ = { isa = XCBuildConfiguration; baseConfigurationReference = 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */; buildSettings = { }; name = Test; }; 51EC892E23511DA80061B6F6 /* Test */ = { isa = XCBuildConfiguration; baseConfigurationReference = D519E74722EE553300923F27 /* NetNewsWire_safariextension_target.xcconfig */; buildSettings = { }; name = Test; }; 51EC892F23511DA80061B6F6 /* Test */ = { isa = XCBuildConfiguration; baseConfigurationReference = 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */; buildSettings = { }; name = Test; }; 6581C74720CED60100F4AD34 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = D519E74722EE553300923F27 /* NetNewsWire_safariextension_target.xcconfig */; buildSettings = { }; name = Debug; }; 6581C74820CED60100F4AD34 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = D519E74722EE553300923F27 /* NetNewsWire_safariextension_target.xcconfig */; buildSettings = { }; name = Release; }; 65ED4080235DEF6C0081F399 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 65ED40F2235DF5E00081F399 /* NetNewsWire_macapp_target_macappstore.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = "Apple Development"; PRODUCT_NAME = NetNewsWire; }; name = Debug; }; 65ED4081235DEF6C0081F399 /* Test */ = { isa = XCBuildConfiguration; baseConfigurationReference = 65ED40F2235DF5E00081F399 /* NetNewsWire_macapp_target_macappstore.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = "Apple Development"; PRODUCT_NAME = NetNewsWire; }; name = Test; }; 65ED4082235DEF6C0081F399 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 65ED40F2235DF5E00081F399 /* NetNewsWire_macapp_target_macappstore.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = "Apple Development"; PRODUCT_NAME = NetNewsWire; }; name = Release; }; 65ED409A235DEF770081F399 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 65ED4186235E045B0081F399 /* NetNewsWire_safariextension_target_macappstore.xcconfig */; buildSettings = { PRODUCT_NAME = "Subscribe to Feed"; }; name = Debug; }; 65ED409B235DEF770081F399 /* Test */ = { isa = XCBuildConfiguration; baseConfigurationReference = 65ED4186235E045B0081F399 /* NetNewsWire_safariextension_target_macappstore.xcconfig */; buildSettings = { PRODUCT_NAME = "Subscribe to Feed"; }; name = Test; }; 65ED409C235DEF770081F399 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 65ED4186235E045B0081F399 /* NetNewsWire_safariextension_target_macappstore.xcconfig */; buildSettings = { PRODUCT_NAME = "Subscribe to Feed"; }; name = Release; }; 840D61A42029031E009BC708 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */; buildSettings = { }; name = Debug; }; 840D61A52029031E009BC708 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */; buildSettings = { }; name = Release; }; 849C64781ED37A5D003D8FC0 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = D5907CDD2002F0BE005947E5 /* NetNewsWire_project_debug.xcconfig */; buildSettings = { }; name = Debug; }; 849C64791ED37A5D003D8FC0 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = D5907CDC2002F0BE005947E5 /* NetNewsWire_project_release.xcconfig */; buildSettings = { }; name = Release; }; 849C647B1ED37A5D003D8FC0 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = D5907CE02002F0FA005947E5 /* NetNewsWire_macapp_target.xcconfig */; buildSettings = { }; name = Debug; }; 849C647C1ED37A5D003D8FC0 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = D5907CE02002F0FA005947E5 /* NetNewsWire_macapp_target.xcconfig */; buildSettings = { }; name = Release; }; 849C647E1ED37A5D003D8FC0 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = D5907CDF2002F0F9005947E5 /* NetNewsWireTests_target.xcconfig */; buildSettings = { }; name = Debug; }; 849C647F1ED37A5D003D8FC0 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = D5907CDF2002F0F9005947E5 /* NetNewsWireTests_target.xcconfig */; buildSettings = { }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 5131463F235A7BBE00387FDC /* Build configuration list for PBXNativeTarget "NetNewsWire iOS Intents Extension" */ = { isa = XCConfigurationList; buildConfigurations = ( 51314640235A7BBE00387FDC /* Debug */, 51314641235A7BBE00387FDC /* Test */, 51314642235A7BBE00387FDC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 513C5CFC232571C2003D4054 /* Build configuration list for PBXNativeTarget "NetNewsWire iOS Share Extension" */ = { isa = XCConfigurationList; buildConfigurations = ( 513C5CF2232571C2003D4054 /* Debug */, 51EC892F23511DA80061B6F6 /* Test */, 513C5CF3232571C2003D4054 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 518B2EE72351B3DD00400001 /* Build configuration list for PBXNativeTarget "NetNewsWire-iOSTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 518B2ED92351B3DD00400001 /* Debug */, 518B2EDA2351B3DD00400001 /* Test */, 518B2EDB2351B3DD00400001 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 6581C75620CED60100F4AD34 /* Build configuration list for PBXNativeTarget "Subscribe to Feed" */ = { isa = XCConfigurationList; buildConfigurations = ( 6581C74720CED60100F4AD34 /* Debug */, 51EC892E23511DA80061B6F6 /* Test */, 6581C74820CED60100F4AD34 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 65ED407F235DEF6C0081F399 /* Build configuration list for PBXNativeTarget "NetNewsWire MAS" */ = { isa = XCConfigurationList; buildConfigurations = ( 65ED4080235DEF6C0081F399 /* Debug */, 65ED4081235DEF6C0081F399 /* Test */, 65ED4082235DEF6C0081F399 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 65ED4099235DEF770081F399 /* Build configuration list for PBXNativeTarget "Subscribe to Feed MAS" */ = { isa = XCConfigurationList; buildConfigurations = ( 65ED409A235DEF770081F399 /* Debug */, 65ED409B235DEF770081F399 /* Test */, 65ED409C235DEF770081F399 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 840D61A32029031E009BC708 /* Build configuration list for PBXNativeTarget "NetNewsWire-iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 840D61A42029031E009BC708 /* Debug */, 51EC892D23511DA80061B6F6 /* Test */, 840D61A52029031E009BC708 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 849C645B1ED37A5D003D8FC0 /* Build configuration list for PBXProject "NetNewsWire" */ = { isa = XCConfigurationList; buildConfigurations = ( 849C64781ED37A5D003D8FC0 /* Debug */, 51EC892A23511DA80061B6F6 /* Test */, 849C64791ED37A5D003D8FC0 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 849C647A1ED37A5D003D8FC0 /* Build configuration list for PBXNativeTarget "NetNewsWire" */ = { isa = XCConfigurationList; buildConfigurations = ( 849C647B1ED37A5D003D8FC0 /* Debug */, 51EC892B23511DA80061B6F6 /* Test */, 849C647C1ED37A5D003D8FC0 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 849C647D1ED37A5D003D8FC0 /* Build configuration list for PBXNativeTarget "NetNewsWireTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 849C647E1ED37A5D003D8FC0 /* Debug */, 51EC892C23511DA80061B6F6 /* Test */, 849C647F1ED37A5D003D8FC0 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 849C64581ED37A5D003D8FC0 /* Project object */; } plist-1.7.0/tests/data/utf16_bplist.plist000064400000000000000000000025421046102023000164410ustar 00000000000000bplist00TnameXlongTextk& or betteroThe sun was shining on the sea, Shining with all his might: He did his very best to make The billows smooth and bright -- And this was odd, because it was The middle of the night. The moon was shining sulkily, Because she thought the sun Had got no business to be there After the day was done -- "It's very rude of him," she said, "To come and spoil the fun!" The sea was wet as wet could be, The sands were dry as dry. You could not see a cloud, because No cloud was in the sky: No birds were flying overhead -- There were no birds to fly. In a Wonderland they lie Dreaming as the days go by, Dreaming as the summer die. & 28plist-1.7.0/tests/data/xml-animals.plist000064400000000000000000000014011046102023000163320ustar 00000000000000 AnimalColors lamb black pig pink worm pink AnimalSmells lamb lambish pig piggish worm wormy AnimalSounds Lisa Why is the worm talking like a lamb? lamb baa pig oink worm baa plist-1.7.0/tests/data/xml.plist000064400000000000000000000016311046102023000147150ustar 00000000000000 Author William Shakespeare Lines It is a tale told by an idiot, Full of sound and fury, signifying nothing. Death 1564 Height 1.6 Data AAAAvgAAAA MAAAAeAAAA Birthdate 1981-05-16T11:32:06Z Blank BiggestNumber 18446744073709551615 SmallestNumber -9223372036854775808 HexademicalNumber 0xDEADBEEF IsTrue IsNotFalse plist-1.7.0/tests/data/xml_error.plist000064400000000000000000000007411046102023000161270ustar 00000000000000 Author William Shakespeare Lines It is a tale told by an idiot, Full of sound and fury, signifying nothing. Death 1564 Height 1.6 DataAb\x90\x93\x9c\xa5\xbb\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xcd"; test_fuzzer_data_err(data); } #[test] fn binary_zero_offset_size() { let data = include_bytes!("data/binary_zero_offset_size.plist"); test_fuzzer_data_err(data); } #[test] fn binary_nan_date() { let data = b"bplist00\xd6\x01\x02\x01\x04\x05\x06\x07\x0a\x0b\x0c\x0d\x0eULinesUDeathVHeightYBthridateVAuthorTData\xa2\x08\x09_\x10\x1eIt is a tale told by an idiot,_\x10+Full of sound and fury, signifying nothing.\x11\x06\x1c#?\xf9\x99\x99\x99\x99\x99\x9a3\xff\xff\xff\xffe\x00\x00\x00_\x13\x10William ShakespeareO\x10\xe5\x00\x00\x00\xbe\x00\x00\x00\x03\x00\x00\x00\x1e\x00\x00\x00\x08\x15\x1b!(14>Ab\x90\x93\x9c\xa5\xbb\xd4\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xcd"; test_fuzzer_data_err(data); } #[test] fn binary_circular_array() { let data = include_bytes!("data/binary_circular_array.plist"); test_fuzzer_data_err(data); } // Issue 20 - not found by fuzzing but this is a convenient place to put the test. #[test] fn issue_20_binary_with_data_in_trailer() { let data = b"bplist00\xd0\x08\0\0\0\0\0\0\x01\x01\0\0\0\0\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\t"; test_fuzzer_data_ok(data); } #[test] fn issue_22_binary_with_byte_ref_size() { let data = b"bplist00\xd1\x01\x02TTestQ1\x08\x0b\x10\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12"; test_fuzzer_data_ok(data); } #[test] fn overflow_instant_add() { let data = b"bplist00\x10\x01\x00\x00\x00\x00\x00\x003~\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00"; test_fuzzer_data_err(data); } #[test] fn overflow_instant_sub() { let data = b"bplist00\x10\x01\x00\x00\x00\x00\x00\x003\xfe\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00"; test_fuzzer_data_err(data); } fn test_fuzzer_data(data: &[u8]) -> Result { let cursor = Cursor::new(data); Value::from_reader(cursor) } fn test_fuzzer_data_ok(data: &[u8]) { test_fuzzer_data(data).unwrap(); } fn test_fuzzer_data_err(data: &[u8]) { assert!(test_fuzzer_data(data).is_err()); }