1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use super::{LoadError, SourceName};
use crate::parser::{css, sassfile, Span};
use crate::{Error, ParseError};
use std::io::Read;
use std::sync::Arc;

/// The full data of a source file.
///
/// This type contains both the contents (internally as a `Vec<u8>`)
/// of a file and information on from where (and why) it was loaded.
/// You can create a `SourceFile` with the [`SourceFile::read`]
/// constructor, but normally you will get one from an
/// [`input::Context`][crate::input::Context].
///
/// A `SourceFile` knows what format it is in, so it can apply the
/// correct parser in the [`parse`][Self::parse] method.
/// Currently, the `scss` and `css` formats are supported.
/// If rsass adds support for the `sass` (indented) format, it will
/// also be supported by this type.
///
/// This type is internally reference counted.
#[derive(Clone)]
pub struct SourceFile {
    data: Arc<Impl>,
}

struct Impl {
    data: Vec<u8>,
    source: SourceName,
    format: SourceFormat,
}

impl SourceFile {
    /// Create a `SourceFile` from something readable and a name.
    ///
    /// The format will be determined from the suffix of the `source`
    /// file name.
    ///
    /// This will return an error if reading the `file` fails, or if a
    /// supported format cannot be determined from the `source` name.
    pub fn read<T: Read>(
        file: &mut T,
        source: SourceName,
    ) -> Result<Self, LoadError> {
        let format = SourceFormat::try_from(source.name())?;
        let mut data = vec![];
        file.read_to_end(&mut data)
            .map_err(|e| LoadError::Input(source.name().to_string(), e))?;
        Ok(Self::new(data, source, format))
    }

    /// Handle some raw byte data as an input file with a given source
    /// name.
    ///
    /// The `data` is expected to be in the `scss` format, the `source`
    /// does not need a suffix (e.g. it can be `SourceName::root("-")`
    /// as per convention for standard input).
    pub fn scss_bytes(data: impl Into<Vec<u8>>, source: SourceName) -> Self {
        Self::new(data.into(), source, SourceFormat::Scss)
    }

    /// Handle some raw byte data as an input file with a given source
    /// name.
    ///
    /// The `data` is expected to be in the `css` format, the `source`
    /// does not need a suffix (e.g. it can be `SourceName::root("-")`
    /// as per convention for standard input).
    pub fn css_bytes(data: impl Into<Vec<u8>>, source: SourceName) -> Self {
        Self::new(data.into(), source, SourceFormat::Css)
    }

    fn new(data: Vec<u8>, source: SourceName, format: SourceFormat) -> Self {
        SourceFile {
            data: Arc::new(Impl {
                data,
                source,
                format,
            }),
        }
    }

    /// Parse this source.
    ///
    /// The correct parser will be applied based on the (known) format
    /// of this `SourceFile`.
    pub fn parse(&self) -> Result<Parsed, Error> {
        let data = Span::new(self);
        match self.data.format {
            SourceFormat::Scss => {
                Ok(Parsed::Scss(ParseError::check(sassfile(data))?))
            }
            SourceFormat::Css => {
                Ok(Parsed::Css(ParseError::check(css::file(data))?))
            }
        }
    }

    pub(crate) fn data(&self) -> &[u8] {
        &self.data.data
    }
    pub(crate) fn source(&self) -> &SourceName {
        &self.data.source
    }
    pub(crate) fn path(&self) -> &str {
        self.data.source.name()
    }
}

impl Ord for SourceFile {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.data.source.cmp(&other.data.source)
    }
}
impl PartialOrd for SourceFile {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}
impl PartialEq for SourceFile {
    fn eq(&self, other: &Self) -> bool {
        self.data.source == other.data.source
    }
}
impl Eq for SourceFile {}

/// A supported input format.
///
/// Rsass handles the scss format and raw css.
/// TODO: In the future, the sass format may also be supported.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum SourceFormat {
    /// The scss format is the main input format.
    Scss,
    /// The css format
    Css,
}

impl TryFrom<&str> for SourceFormat {
    type Error = LoadError;
    fn try_from(name: &str) -> Result<SourceFormat, LoadError> {
        if name.ends_with(".scss") {
            Ok(SourceFormat::Scss)
        } else if name.ends_with(".css") {
            Ok(SourceFormat::Css)
        } else {
            Err(LoadError::UnknownFormat(name.into()))
        }
    }
}

/// Parsed source that is either css or sass data.
#[derive(Clone, Debug)]
pub enum Parsed {
    /// Raw css data.
    Css(Vec<crate::css::Item>),
    /// Sass (scss) data.
    Scss(Vec<crate::sass::Item>),
}