Repository: casey/just
File: src/loader.rs (Rust)

Code before:

pub(crate) struct Loader {
  arena: Arena<String>,
}

impl Loader {
  pub(crate) fn new() -> Self {
    Loader {
      arena: Arena::new(),
    }
  }

  pub(crate) fn load_without_includes<'src>(&'src self, path: &Path) -> RunResult<&'src str> {
    let src = fs::read_to_string(path).map_err(|io_error| Error::Load {
      path: path.to_owned(),
      io_error,
    })?;
    Ok(self.arena.alloc(src))
  }

  pub(crate) fn load_with_includes<'src>(&'src self, path: &Path) -> RunResult<&'src str> {
    let src = self.perform_load(path, HashSet::new())?;
    Ok(self.arena.alloc(src))
  }

  fn perform_load(&self, path: &Path, seen_paths: HashSet<PathBuf>) -> RunResult<String> {
    let src = fs::read_to_string(path).map_err(|io_error| Error::Load {
      path: path.to_owned(),
      io_error,
    })?;

    self.process_includes(src, path, seen_paths)
  }

  /// Given the original contents of a Justfile (with include directives), load all the included
  /// paths to produce one String with the contents of all the files concatenated.
  ///
  /// Note that this does not do any parsing yet (i.e. nothing stops a justfile from including a
  /// file that is not a valid justfile), and that (currently) line numbers in error messages will
  /// reference lines in this concatenated String rather than probably-more-useful information
  /// about the original file an error came from.
  ///
  /// !include directives are allowed to appear in a justfile only before the first non-blank,
  /// non-comment line. This is because this processing step occurs before parsing, and we don't
  /// want to accidentally process the string "!include" when it happens to occur within a string
  /// or in another post-parsing syntactic context where it shouldn't be.
  fn process_includes(
    &self,
    original: String,
    current_justfile_path: &Path,
    seen_paths: HashSet<PathBuf>,

Review comment (suggestion):
Not sure about the name, how about:

```suggestion
  fn load_with_includes_recursive(&self, path: &Path, seen_paths: HashSet<PathBuf>) -> RunResult<String> {
```

Code after:

impl<'a> LinesWithEndings<'a> {
  fn new(input: &'a str) -> Self {
    Self { input }
  }
}

impl<'a> Iterator for LinesWithEndings<'a> {
  type Item = &'a str;

  fn next(&mut self) -> Option<&'a str> {
    if self.input.is_empty() {
      return None;
    }
    let split = self.input.find('\n').map_or(self.input.len(), |i| i + 1);
    let (line, rest) = self.input.split_at(split);
    self.input = rest;
    Some(line)
  }
}

pub(crate) struct Loader {
  arena: Arena<String>,
  unstable: bool,
}

impl Loader {
  pub(crate) fn new(unstable: bool) -> Self {
    Loader {
      arena: Arena::new(),
      unstable,
    }
  }

  pub(crate) fn load<'src>(&'src self, path: &Path) -> RunResult<&'src str> {
    let src = self.load_recursive(path, HashSet::new())?;
    Ok(self.arena.alloc(src))
  }

  fn load_file<'a>(path: &Path) -> RunResult<'a, String> {
    fs::read_to_string(path).map_err(|io_error| Error::Load {
      path: path.to_owned(),
      io_error,
    })
  }

  fn load_recursive(&self, file: &Path, seen: HashSet<PathBuf>) -> RunResult<String> {
    let src = Self::load_file(file)?;

    let mut output = String::new();