Skip to content

Commit

Permalink
Merge pull request #244 from stepchowfun/windows-support
Browse files Browse the repository at this point in the history
Fix Windows support
  • Loading branch information
stepchowfun authored Oct 16, 2021
2 parents fd0463e + 216e9e4 commit feea8be
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 45 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.0.4] - 2021-10-16

### Fixed
- Fixed a bug that prevented Typical from working on Windows.

## [0.0.3] - 2021-10-14

### Changed
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "typical"
version = "0.0.3"
version = "0.0.4"
authors = ["Stephan Boyer <[email protected]>"]
edition = "2018"
description = "Algebraic data types for data interchange."
Expand Down
1 change: 0 additions & 1 deletion src/generate_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1597,7 +1597,6 @@ mod tests {
// This test doesn't work on Windows, for some reason.
#[allow(clippy::too_many_lines)]
#[test]
#[cfg_attr(target_os = "windows", ignore)]
fn generate_example() {
let schemas = load_schemas(Path::new("integration_tests/types/main.t")).unwrap();
validate(&schemas).unwrap();
Expand Down
2 changes: 0 additions & 2 deletions src/generate_typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,7 @@ mod tests {
};

// This test doesn't work on Windows, for some reason.
#[allow(clippy::too_many_lines)]
#[test]
#[cfg_attr(target_os = "windows", ignore)]
fn generate_example() {
let schemas = load_schemas(Path::new("integration_tests/types/main.t")).unwrap();
validate(&schemas).unwrap();
Expand Down
111 changes: 71 additions & 40 deletions src/schema_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ use {
tokenizer::tokenize,
},
std::{
borrow::ToOwned,
collections::{BTreeMap, HashSet},
fs::read_to_string,
io::{self, ErrorKind},
path::PathBuf,
path::{Component, Path},
},
Expand Down Expand Up @@ -46,13 +48,43 @@ pub fn load_schemas(
// Any errors will end up here.
let mut errors = vec![];

// Canonicalize the path. This ensures the path doesn't contain `..` or `.`.
let canonical_schema_path = match schema_path.canonicalize() {
Ok(canonical_schema_path) => canonical_schema_path,
// The base directory for the schema's dependencies is the directory containing the schema.
let base_path = if let Some(base_path) = schema_path.parent() {
base_path
} else {
errors.push(throw::<Error>(
&format!(
"{} is not a file.",
schema_path.to_string_lossy().code_str(),
),
None,
None,
None,
));

return Err(errors);
};

// Canonicalize the path to the base directory. This will be used to calculate namespaces below.
// Note that even with this we still need `base_path` from above, because canonicalization on
// Windows adds a `\\?\` prefix to the path, which changes the meaning of `..` and thus prevents
// us from joining it with other paths containing `..`. Note also that we can't simply
// compute this by canonicalizing `base_path`, since `base_path` might have zero components,
// which is considered invalid for canonicalization. So, instead, we canonicalize `schma_path`
// and take the parent of the result.
let canonical_base_path = match schema_path
.canonicalize()
.and_then(|canonical_schema_path| {
canonical_schema_path
.parent()
.map(ToOwned::to_owned)
.ok_or_else(|| io::Error::from(ErrorKind::Other))
}) {
Ok(canonical_base_path) => canonical_base_path,
Err(error) => {
errors.push(throw(
&format!(
"Unable to load {}.",
"{} is not a file.",
schema_path.to_string_lossy().code_str(),
),
None,
Expand All @@ -64,11 +96,10 @@ pub fn load_schemas(
}
};

// Compute the base directory for the schema's dependencies. Any canonical path which starts
// with this base directory can be safely converted into a namespace
// [tag:canonical_based_paths_are_namespaces].
let base_path = if let Some(base_path) = canonical_schema_path.parent() {
base_path
// Relative to the base directory, the path to the schema is the name of the schema file
// [tag:based_schema_path_is_file_name].
let based_schema_path = if let Some(based_schema_path) = schema_path.file_name() {
AsRef::<Path>::as_ref(based_schema_path)
} else {
errors.push(throw::<Error>(
&format!(
Expand All @@ -83,20 +114,19 @@ pub fn load_schemas(
return Err(errors);
};

// Strip the base path from the schema path, i.e., compute the schema file name. The `unwrap`
// is safe because we know `base_path` is the parent of `canonical_schema_path`.
let based_schema_path = canonical_schema_path.strip_prefix(base_path).unwrap();
// Compute the namespace of the schema. This is safe due to
// [ref:based_schema_path_is_file_name].
let schema_namespace = path_to_namespace(based_schema_path);

// Initialize the "frontier" with the given path. Paths in the frontier are relative to
// `base_path` [tag:frontier_paths_based]. The path-to-namespace conversion is safe due to
// [ref:canonical_based_paths_are_namespaces].
// `base_path` [tag:frontier_paths_based].
let mut schemas_to_load = vec![(
path_to_namespace(based_schema_path),
schema_namespace.clone(),
based_schema_path.to_owned(),
None as Option<(PathBuf, String)>,
)];
let mut visited_paths = HashSet::new();
visited_paths.insert(based_schema_path.to_owned());
let mut visited_namespaces = HashSet::new();
visited_namespaces.insert(schema_namespace);

// Perform a depth-first traversal of the transitive dependencies.
while let Some((namespace, path, origin)) = schemas_to_load.pop() {
Expand Down Expand Up @@ -160,7 +190,7 @@ pub fn load_schemas(
errors.push(throw(
&format!(
"Unable to load {}.",
import.path.to_string_lossy().code_str(),
non_canonical_import_path.to_string_lossy().code_str(),
),
Some(&path),
Some(&origin_listing),
Expand All @@ -171,35 +201,37 @@ pub fn load_schemas(
}
};

// Strip the base path from the schema path.
let based_import_path =
if let Ok(based_import_path) = canonical_import_path.strip_prefix(base_path) {
based_import_path.to_owned()
} else {
errors.push(throw::<Error>(
&format!(
"{} is not a descendant of {}, which is the base directory for this \
run.",
canonical_import_path.to_string_lossy().code_str(),
base_path.to_string_lossy().code_str(),
),
Some(&path),
Some(&origin_listing),
None,
));
// Strip the base path from the schema path. Since this is computed from two canonical
// paths, its guaranteed to contain only normal components
// [tag:based_import_path_only_has_normal_components].
let based_import_path = if let Ok(based_import_path) =
canonical_import_path.strip_prefix(&canonical_base_path)
{
based_import_path.to_owned()
} else {
errors.push(throw::<Error>(
&format!(
"{} is not a descendant of {}, which is the base directory for this run.",
canonical_import_path.to_string_lossy().code_str(),
canonical_base_path.to_string_lossy().code_str(),
),
Some(&path),
Some(&origin_listing),
None,
));

continue;
};
continue;
};

// Populate the namespace of the import [tag:namespace_populated]. The
// path-to-namespace conversion is safe due to
// [ref:canonical_based_paths_are_namespaces].
// [ref:based_import_path_only_has_normal_components].
let import_namespace = path_to_namespace(&based_import_path);
import.namespace = Some(import_namespace.clone());

// Visit this import if it hasn't been visited already.
if !visited_paths.contains(&based_import_path) {
visited_paths.insert(based_import_path.clone());
if !visited_namespaces.contains(&import_namespace) {
visited_namespaces.insert(import_namespace.clone());
schemas_to_load.push((
import_namespace,
based_import_path,
Expand Down Expand Up @@ -283,7 +315,6 @@ mod tests {

// This test doesn't work on Windows, for some reason.
#[test]
#[cfg_attr(target_os = "windows", ignore)]
fn load_schemas_example() {
load_schemas(Path::new("integration_tests/types/main.t")).unwrap();
}
Expand Down

0 comments on commit feea8be

Please sign in to comment.