Skip to content

Commit feea8be

Browse files
authored
Merge pull request #244 from stepchowfun/windows-support
Fix Windows support
2 parents fd0463e + 216e9e4 commit feea8be

File tree

6 files changed

+78
-45
lines changed

6 files changed

+78
-45
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.0.4] - 2021-10-16
9+
10+
### Fixed
11+
- Fixed a bug that prevented Typical from working on Windows.
12+
813
## [0.0.3] - 2021-10-14
914

1015
### Changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "typical"
3-
version = "0.0.3"
3+
version = "0.0.4"
44
authors = ["Stephan Boyer <[email protected]>"]
55
edition = "2018"
66
description = "Algebraic data types for data interchange."

src/generate_rust.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1597,7 +1597,6 @@ mod tests {
15971597
// This test doesn't work on Windows, for some reason.
15981598
#[allow(clippy::too_many_lines)]
15991599
#[test]
1600-
#[cfg_attr(target_os = "windows", ignore)]
16011600
fn generate_example() {
16021601
let schemas = load_schemas(Path::new("integration_tests/types/main.t")).unwrap();
16031602
validate(&schemas).unwrap();

src/generate_typescript.rs

-2
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,7 @@ mod tests {
182182
};
183183

184184
// This test doesn't work on Windows, for some reason.
185-
#[allow(clippy::too_many_lines)]
186185
#[test]
187-
#[cfg_attr(target_os = "windows", ignore)]
188186
fn generate_example() {
189187
let schemas = load_schemas(Path::new("integration_tests/types/main.t")).unwrap();
190188
validate(&schemas).unwrap();

src/schema_loader.rs

+71-40
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ use {
77
tokenizer::tokenize,
88
},
99
std::{
10+
borrow::ToOwned,
1011
collections::{BTreeMap, HashSet},
1112
fs::read_to_string,
13+
io::{self, ErrorKind},
1214
path::PathBuf,
1315
path::{Component, Path},
1416
},
@@ -46,13 +48,43 @@ pub fn load_schemas(
4648
// Any errors will end up here.
4749
let mut errors = vec![];
4850

49-
// Canonicalize the path. This ensures the path doesn't contain `..` or `.`.
50-
let canonical_schema_path = match schema_path.canonicalize() {
51-
Ok(canonical_schema_path) => canonical_schema_path,
51+
// The base directory for the schema's dependencies is the directory containing the schema.
52+
let base_path = if let Some(base_path) = schema_path.parent() {
53+
base_path
54+
} else {
55+
errors.push(throw::<Error>(
56+
&format!(
57+
"{} is not a file.",
58+
schema_path.to_string_lossy().code_str(),
59+
),
60+
None,
61+
None,
62+
None,
63+
));
64+
65+
return Err(errors);
66+
};
67+
68+
// Canonicalize the path to the base directory. This will be used to calculate namespaces below.
69+
// Note that even with this we still need `base_path` from above, because canonicalization on
70+
// Windows adds a `\\?\` prefix to the path, which changes the meaning of `..` and thus prevents
71+
// us from joining it with other paths containing `..`. Note also that we can't simply
72+
// compute this by canonicalizing `base_path`, since `base_path` might have zero components,
73+
// which is considered invalid for canonicalization. So, instead, we canonicalize `schma_path`
74+
// and take the parent of the result.
75+
let canonical_base_path = match schema_path
76+
.canonicalize()
77+
.and_then(|canonical_schema_path| {
78+
canonical_schema_path
79+
.parent()
80+
.map(ToOwned::to_owned)
81+
.ok_or_else(|| io::Error::from(ErrorKind::Other))
82+
}) {
83+
Ok(canonical_base_path) => canonical_base_path,
5284
Err(error) => {
5385
errors.push(throw(
5486
&format!(
55-
"Unable to load {}.",
87+
"{} is not a file.",
5688
schema_path.to_string_lossy().code_str(),
5789
),
5890
None,
@@ -64,11 +96,10 @@ pub fn load_schemas(
6496
}
6597
};
6698

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

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

90121
// Initialize the "frontier" with the given path. Paths in the frontier are relative to
91-
// `base_path` [tag:frontier_paths_based]. The path-to-namespace conversion is safe due to
92-
// [ref:canonical_based_paths_are_namespaces].
122+
// `base_path` [tag:frontier_paths_based].
93123
let mut schemas_to_load = vec![(
94-
path_to_namespace(based_schema_path),
124+
schema_namespace.clone(),
95125
based_schema_path.to_owned(),
96126
None as Option<(PathBuf, String)>,
97127
)];
98-
let mut visited_paths = HashSet::new();
99-
visited_paths.insert(based_schema_path.to_owned());
128+
let mut visited_namespaces = HashSet::new();
129+
visited_namespaces.insert(schema_namespace);
100130

101131
// Perform a depth-first traversal of the transitive dependencies.
102132
while let Some((namespace, path, origin)) = schemas_to_load.pop() {
@@ -160,7 +190,7 @@ pub fn load_schemas(
160190
errors.push(throw(
161191
&format!(
162192
"Unable to load {}.",
163-
import.path.to_string_lossy().code_str(),
193+
non_canonical_import_path.to_string_lossy().code_str(),
164194
),
165195
Some(&path),
166196
Some(&origin_listing),
@@ -171,35 +201,37 @@ pub fn load_schemas(
171201
}
172202
};
173203

174-
// Strip the base path from the schema path.
175-
let based_import_path =
176-
if let Ok(based_import_path) = canonical_import_path.strip_prefix(base_path) {
177-
based_import_path.to_owned()
178-
} else {
179-
errors.push(throw::<Error>(
180-
&format!(
181-
"{} is not a descendant of {}, which is the base directory for this \
182-
run.",
183-
canonical_import_path.to_string_lossy().code_str(),
184-
base_path.to_string_lossy().code_str(),
185-
),
186-
Some(&path),
187-
Some(&origin_listing),
188-
None,
189-
));
204+
// Strip the base path from the schema path. Since this is computed from two canonical
205+
// paths, its guaranteed to contain only normal components
206+
// [tag:based_import_path_only_has_normal_components].
207+
let based_import_path = if let Ok(based_import_path) =
208+
canonical_import_path.strip_prefix(&canonical_base_path)
209+
{
210+
based_import_path.to_owned()
211+
} else {
212+
errors.push(throw::<Error>(
213+
&format!(
214+
"{} is not a descendant of {}, which is the base directory for this run.",
215+
canonical_import_path.to_string_lossy().code_str(),
216+
canonical_base_path.to_string_lossy().code_str(),
217+
),
218+
Some(&path),
219+
Some(&origin_listing),
220+
None,
221+
));
190222

191-
continue;
192-
};
223+
continue;
224+
};
193225

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

200232
// Visit this import if it hasn't been visited already.
201-
if !visited_paths.contains(&based_import_path) {
202-
visited_paths.insert(based_import_path.clone());
233+
if !visited_namespaces.contains(&import_namespace) {
234+
visited_namespaces.insert(import_namespace.clone());
203235
schemas_to_load.push((
204236
import_namespace,
205237
based_import_path,
@@ -283,7 +315,6 @@ mod tests {
283315

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

0 commit comments

Comments
 (0)