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>),
}