Skip to content

Commit

Permalink
perf(package): only try to read files that exist (starship#3904)
Browse files Browse the repository at this point in the history
* perf(package): only try to read files that exist

Have refactored the package module to improve performance. Before this
change the module would try to open every single file that could contain
some package information until it found a valid version. This resulted
in a lot of unneeded disk IO. Have added a new fn, `read_file_from_pwd`
that uses the current context to check if that file already exists and
fast failing if it doesn't. From my local testing this speeds up the
package module from taking ~1ms to ~50µs in an empty directory.

* refactor: move read_file_from_pwd to context

* refactor(haskell): use read_files_from_pwd

* refactor(nodejs): use read_files_from_pwd
  • Loading branch information
andytom authored Apr 25, 2022
1 parent 4471e36 commit 2a650bf
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 27 deletions.
14 changes: 13 additions & 1 deletion src/context.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::config::{ModuleConfig, StarshipConfig};
use crate::configs::StarshipRootConfig;
use crate::module::Module;
use crate::utils::{create_command, exec_timeout, CommandOutput};
use crate::utils::{create_command, exec_timeout, read_file, CommandOutput};

use crate::modules;
use crate::utils::{self, home_dir};
Expand Down Expand Up @@ -342,6 +342,18 @@ impl<'a> Context<'a> {
.iter()
.find_map(|attempt| self.exec_cmd(attempt[0], &attempt[1..]))
}

/// Returns the string contents of a file from the current working directory
pub fn read_file_from_pwd(&self, file_name: &str) -> Option<String> {
if !self.try_begin_scan()?.set_files(&[file_name]).is_match() {
log::debug!(
"Not attempting to read {file_name} because, it was not found during scan."
);
return None;
}

read_file(self.current_dir.join(file_name)).ok()
}
}

#[derive(Debug)]
Expand Down
3 changes: 1 addition & 2 deletions src/modules/haskell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use super::{Context, Module, ModuleConfig};

use crate::configs::haskell::HaskellConfig;
use crate::formatter::StringFormatter;
use crate::utils;

/// Creates a module with the current Haskell version
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
Expand Down Expand Up @@ -64,7 +63,7 @@ fn get_snapshot(context: &Context) -> Option<String> {
if !is_stack_project(context) {
return None;
}
let file_contents = utils::read_file(context.current_dir.join("stack.yaml")).ok()?;
let file_contents = context.read_file_from_pwd("stack.yaml")?;
let yaml = yaml_rust::YamlLoader::load_from_str(&file_contents).ok()?;
let version = yaml.first()?["resolver"]
.as_str()
Expand Down
8 changes: 3 additions & 5 deletions src/modules/nodejs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ use super::{Context, Module, ModuleConfig};

use crate::configs::nodejs::NodejsConfig;
use crate::formatter::{StringFormatter, VersionFormatter};
use crate::utils;

use once_cell::sync::Lazy;
use regex::Regex;
use semver::Version;
use semver::VersionReq;
use serde_json as json;
use std::ops::Deref;
use std::path::Path;

/// Creates a module with the current Node.js version
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
Expand Down Expand Up @@ -45,7 +43,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
})
.map_style(|variable| match variable {
"style" => {
let engines_version = get_engines_version(&context.current_dir);
let engines_version = get_engines_version(context);
let in_engines_range =
check_engines_version(nodejs_version.deref().as_ref()?, engines_version);
if in_engines_range {
Expand Down Expand Up @@ -87,8 +85,8 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
Some(module)
}

fn get_engines_version(base_dir: &Path) -> Option<String> {
let json_str = utils::read_file(base_dir.join("package.json")).ok()?;
fn get_engines_version(context: &Context) -> Option<String> {
let json_str = context.read_file_from_pwd("package.json")?;
let package_json: json::Value = json::from_str(&json_str).ok()?;
let raw_version = package_json.get("engines")?.get("node")?.as_str()?;
Some(raw_version.to_string())
Expand Down
37 changes: 18 additions & 19 deletions src/modules/package.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use super::{Context, Module, ModuleConfig};
use crate::configs::package::PackageConfig;
use crate::formatter::{StringFormatter, VersionFormatter};
use crate::utils;

use ini::Ini;
use quick_xml::events::Event as QXEvent;
Expand Down Expand Up @@ -44,7 +43,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}

fn get_node_package_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(&context.current_dir.join("package.json")).ok()?;
let file_contents = context.read_file_from_pwd("package.json")?;
let package_json: json::Value = json::from_str(&file_contents).ok()?;

if !config.display_private
Expand All @@ -68,7 +67,7 @@ fn get_node_package_version(context: &Context, config: &PackageConfig) -> Option
}

fn get_poetry_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(&context.current_dir.join("pyproject.toml")).ok()?;
let file_contents = context.read_file_from_pwd("pyproject.toml")?;
let poetry_toml: toml::Value = toml::from_str(&file_contents).ok()?;
let raw_version = poetry_toml
.get("tool")?
Expand All @@ -80,7 +79,7 @@ fn get_poetry_version(context: &Context, config: &PackageConfig) -> Option<Strin
}

fn get_setup_cfg_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(context.current_dir.join("setup.cfg")).ok()?;
let file_contents = context.read_file_from_pwd("setup.cfg")?;
let ini = Ini::load_from_str(&file_contents).ok()?;
let raw_version = ini.get_from(Some("metadata"), "version")?;

Expand All @@ -92,48 +91,48 @@ fn get_setup_cfg_version(context: &Context, config: &PackageConfig) -> Option<St
}

fn get_gradle_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(context.current_dir.join("build.gradle")).ok()?;
let file_contents = context.read_file_from_pwd("build.gradle")?;
let re = Regex::new(r#"(?m)^version ['"](?P<version>[^'"]+)['"]$"#).unwrap();
let caps = re.captures(&file_contents)?;

format_version(&caps["version"], config.version_format)
}

fn get_composer_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(context.current_dir.join("composer.json")).ok()?;
let file_contents = context.read_file_from_pwd("composer.json")?;
let composer_json: json::Value = json::from_str(&file_contents).ok()?;
let raw_version = composer_json.get("version")?.as_str()?;

format_version(raw_version, config.version_format)
}

fn get_julia_project_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(context.current_dir.join("Project.toml")).ok()?;
let file_contents = context.read_file_from_pwd("Project.toml")?;
let project_toml: toml::Value = toml::from_str(&file_contents).ok()?;
let raw_version = project_toml.get("version")?.as_str()?;

format_version(raw_version, config.version_format)
}

fn get_helm_package_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(context.current_dir.join("Chart.yaml")).ok()?;
let file_contents = context.read_file_from_pwd("Chart.yaml")?;
let yaml = yaml_rust::YamlLoader::load_from_str(&file_contents).ok()?;
let version = yaml.first()?["version"].as_str()?;

format_version(version, config.version_format)
}

fn get_mix_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(context.current_dir.join("mix.exs")).ok()?;
let file_contents = context.read_file_from_pwd("mix.exs")?;
let re = Regex::new(r#"(?m)version: "(?P<version>[^"]+)""#).unwrap();
let caps = re.captures(&file_contents)?;

format_version(&caps["version"], config.version_format)
}

fn get_maven_version(context: &Context, config: &PackageConfig) -> Option<String> {
let pom_file = utils::read_file(context.current_dir.join("pom.xml")).ok()?;
let mut reader = QXReader::from_str(&pom_file);
let file_contents = context.read_file_from_pwd("pom.xml")?;
let mut reader = QXReader::from_str(&file_contents);
reader.trim_text(true);

let mut buf = vec![];
Expand Down Expand Up @@ -171,8 +170,8 @@ fn get_maven_version(context: &Context, config: &PackageConfig) -> Option<String
}

fn get_meson_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(context.current_dir.join("meson.build"))
.ok()?
let file_contents = context
.read_file_from_pwd("meson.build")?
.split_ascii_whitespace()
.collect::<String>();

Expand All @@ -183,29 +182,29 @@ fn get_meson_version(context: &Context, config: &PackageConfig) -> Option<String
}

fn get_vmod_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(context.current_dir.join("v.mod")).ok()?;
let file_contents = context.read_file_from_pwd("v.mod")?;
let re = Regex::new(r"(?m)^\s*version\s*:\s*'(?P<version>[^']+)'").unwrap();
let caps = re.captures(&file_contents)?;
format_version(&caps["version"], config.version_format)
}

fn get_vpkg_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(context.current_dir.join("vpkg.json")).ok()?;
let file_contents = context.read_file_from_pwd("vpkg.json")?;
let vpkg_json: json::Value = json::from_str(&file_contents).ok()?;
let raw_version = vpkg_json.get("version")?.as_str()?;

format_version(raw_version, config.version_format)
}

fn get_sbt_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(context.current_dir.join("build.sbt")).ok()?;
let file_contents = context.read_file_from_pwd("build.sbt")?;
let re = Regex::new(r"(?m)^(.*/)*\s*version\s*:=\s*.(?P<version>[\d\.]+)").unwrap();
let caps = re.captures(&file_contents)?;
format_version(&caps["version"], config.version_format)
}

fn get_cargo_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(&context.current_dir.join("Cargo.toml")).ok()?;
let file_contents = context.read_file_from_pwd("Cargo.toml")?;

let cargo_toml: toml::Value = toml::from_str(&file_contents).ok()?;
let raw_version = cargo_toml.get("package")?.get("version")?.as_str()?;
Expand All @@ -231,7 +230,7 @@ fn get_nimble_version(context: &Context, config: &PackageConfig) -> Option<Strin
}

fn get_shard_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(&context.current_dir.join("shard.yml")).ok()?;
let file_contents = context.read_file_from_pwd("shard.yml")?;

let data = yaml_rust::YamlLoader::load_from_str(&file_contents).ok()?;
let raw_version = data.first()?["version"].as_str()?;
Expand All @@ -240,7 +239,7 @@ fn get_shard_version(context: &Context, config: &PackageConfig) -> Option<String
}

fn get_dart_pub_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(&context.current_dir.join("pubspec.yaml")).ok()?;
let file_contents = context.read_file_from_pwd("pubspec.yaml")?;

let data = yaml_rust::YamlLoader::load_from_str(&file_contents).ok()?;
let raw_version = data.first()?["version"].as_str()?;
Expand Down

0 comments on commit 2a650bf

Please sign in to comment.