Skip to content

Commit fa001db

Browse files
Support relative script and source commands (#293)
## Usage and product changes We support using relative paths for the `--script` command (relative to the current directory), as well as relative paths for the REPL `source` command. When `source` is invoked _from_ a script, the sourced file is relativised to the script, rather than the current working directory. ## Implementation * Store the console's working directory, and the script directory if a script is active (scripts can't be nested, which enables this!) * Relativize to either the script directory or the working directory as when running with `--script` mode and `>> source <path> ` commands
1 parent 42949ab commit fa001db

File tree

4 files changed

+61
-22
lines changed

4 files changed

+61
-22
lines changed

src/cli.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ pub struct Args {
1616

1717
/// Executes all console commands directly from the script(s) in the order of each specified script.
1818
/// Exits if any script errors at any point.
19-
/// Files must follow the convention of terminating queries with an empty newline
19+
/// Files must follow the convention of terminating queries with an empty newline.
20+
/// File path can be absolute or relative to the current directory
2021
#[arg(long, value_name = "path to script file")]
2122
pub script: Vec<String>,
2223

src/main.rs

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
*/
66

77
use std::{
8+
env,
89
env::temp_dir,
910
error::Error,
1011
fmt::{Debug, Display, Formatter},
1112
fs::File,
1213
io,
1314
io::BufRead,
14-
path::Path,
15+
path::{Path, PathBuf},
1516
process::exit,
1617
rc::Rc,
1718
sync::Arc,
@@ -56,22 +57,38 @@ const DIAGNOSTICS_REPORTING_URI: &'static str =
5657
"https://7f0ccb67b03abfccbacd7369d1f4ac6b@o4506315929812992.ingest.sentry.io/4506355433537536";
5758

5859
struct ConsoleContext {
60+
invocation_dir: PathBuf,
5961
repl_stack: Vec<Rc<Repl<ConsoleContext>>>,
6062
background_runtime: BackgroundRuntime,
6163
driver: Arc<TypeDBDriver>,
6264
transaction: Option<(Transaction, bool)>,
65+
script_dir: Option<String>,
6366
}
6467

65-
impl ReplContext for ConsoleContext {
66-
fn current_repl(&self) -> &Repl<Self> {
67-
self.repl_stack.last().unwrap()
68+
impl ConsoleContext {
69+
fn convert_path(&self, path: &str) -> PathBuf {
70+
let path = Path::new(path);
71+
if !path.is_absolute() {
72+
match self.script_dir.as_ref() {
73+
None => self.invocation_dir.join(path),
74+
Some(dir) => PathBuf::from(dir).join(path),
75+
}
76+
} else {
77+
path.to_path_buf()
78+
}
6879
}
6980

7081
fn has_changes(&self) -> bool {
7182
self.transaction.as_ref().is_some_and(|(_, has_writes)| *has_writes)
7283
}
7384
}
7485

86+
impl ReplContext for ConsoleContext {
87+
fn current_repl(&self) -> &Repl<Self> {
88+
self.repl_stack.last().unwrap()
89+
}
90+
}
91+
7592
fn main() {
7693
let mut args = Args::parse();
7794
if args.version {
@@ -111,8 +128,15 @@ fn main() {
111128
};
112129

113130
let repl = entry_repl(driver.clone(), runtime.clone());
114-
let mut context =
115-
ConsoleContext { repl_stack: vec![Rc::new(repl)], background_runtime: runtime, transaction: None, driver };
131+
let invocation_dir = PathBuf::from(env::current_dir().unwrap());
132+
let mut context = ConsoleContext {
133+
invocation_dir,
134+
repl_stack: vec![Rc::new(repl)],
135+
background_runtime: runtime,
136+
transaction: None,
137+
script_dir: None,
138+
driver,
139+
};
116140

117141
if !args.command.is_empty() && !args.script.is_empty() {
118142
println!("Error: Cannot specify both commands and files");
@@ -128,31 +152,38 @@ fn main() {
128152

129153
fn execute_scripts(context: &mut ConsoleContext, files: &[String]) {
130154
for file_path in files {
155+
let path = context.convert_path(file_path);
131156
if let Ok(file) = File::open(&file_path) {
132-
execute_script(context, &file_path, io::BufReader::new(file).lines())
157+
execute_script(context, path, io::BufReader::new(file).lines())
133158
} else {
134-
println!("Error opening file: {}", file_path);
159+
println!("Error opening file: {}", path.to_string_lossy());
135160
exit(1);
136161
}
137162
}
138163
}
139164

140-
fn execute_script(context: &mut ConsoleContext, file: &str, inputs: impl Iterator<Item = Result<String, io::Error>>) {
165+
fn execute_script(
166+
context: &mut ConsoleContext,
167+
file_path: PathBuf,
168+
inputs: impl Iterator<Item = Result<String, io::Error>>,
169+
) {
141170
let mut combined_input = String::new();
171+
context.script_dir = Some(file_path.parent().unwrap().to_string_lossy().to_string());
142172
for (index, input) in inputs.enumerate() {
143173
match input {
144174
Ok(line) => {
145175
combined_input.push('\n');
146176
combined_input.push_str(&line);
147177
}
148178
Err(_) => {
149-
println!("### Error reading file '{}' line: {}", file, index + 1);
179+
println!("### Error reading file '{}' line: {}", file_path.to_string_lossy(), index + 1);
150180
return;
151181
}
152182
}
153183
}
154184
// we could choose to implement this as line-by-line instead of as an interactive-compatible script
155185
let _ = execute_commands(context, &combined_input, false, true);
186+
context.script_dir = None;
156187
}
157188

158189
fn execute_command_list(context: &mut ConsoleContext, commands: &[String]) {
@@ -342,7 +373,7 @@ fn transaction_repl(database: &str, transaction_type: TransactionType) -> Repl<C
342373
))
343374
.add(CommandLeaf::new_with_input(
344375
"source",
345-
"Execute a file containing a sequence of TypeQL queries. Queries may be split over multiple lines using backslash ('\\')",
376+
"Synchronously execute a file containing a sequence of TypeQL queries with full validation. Queries can be explicitly ended with 'end;' if required. Path may be absolute or relative to the invoking script (if there is one) otherwise relative to the current working directory.",
346377
CommandInput::new("file", get_word, None, Some(Box::new(file_completer))),
347378
transaction_source,
348379
))

src/operations.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
66

7-
use std::{error::Error, fs::read_to_string, path::Path, process::exit, rc::Rc};
7+
use std::{
8+
error::Error,
9+
fs::read_to_string,
10+
path::{Path, PathBuf},
11+
process::exit,
12+
rc::Rc,
13+
};
814

915
use futures::stream::StreamExt;
1016
use typedb_driver::{
@@ -246,13 +252,13 @@ pub(crate) fn transaction_rollback(context: &mut ConsoleContext, _input: &[Strin
246252

247253
pub(crate) fn transaction_source(context: &mut ConsoleContext, input: &[String]) -> CommandResult {
248254
let file_str = &input[0];
249-
let path = Path::new(file_str);
255+
let path = context.convert_path(file_str);
250256
if !path.exists() {
251-
return Err(Box::new(ReplError { message: format!("File not found: {}", file_str) }) as Box<dyn Error + Send>);
257+
return Err(Box::new(ReplError { message: format!("File not found: {}", path.to_string_lossy()) })
258+
as Box<dyn Error + Send>);
252259
} else if path.is_dir() {
253-
return Err(
254-
Box::new(ReplError { message: format!("Path must be a file: {}", file_str) }) as Box<dyn Error + Send>
255-
);
260+
return Err(Box::new(ReplError { message: format!("Path must be a file: {}", path.to_string_lossy()) })
261+
as Box<dyn Error + Send>);
256262
}
257263

258264
let contents = read_to_string(path).map_err(|err| {
@@ -267,7 +273,7 @@ pub(crate) fn transaction_source(context: &mut ConsoleContext, input: &[String])
267273
input = &input[next_query_index..];
268274
continue;
269275
}
270-
match execute_query(context, query.to_owned(), true) {
276+
match execute_query(context, query.to_owned(), false) {
271277
Err(err) => {
272278
return Err(Box::new(ReplError {
273279
message: format!(
@@ -284,6 +290,9 @@ pub(crate) fn transaction_source(context: &mut ConsoleContext, input: &[String])
284290
query_count += 1;
285291
}
286292
}
293+
if query_count % 1_000 == 0 {
294+
println!("In progress: executed {} queries.", query_count);
295+
}
287296
}
288297
if !input.trim().is_empty() {
289298
match execute_query(context, input.to_owned(), false) {
@@ -301,7 +310,7 @@ pub(crate) fn transaction_source(context: &mut ConsoleContext, input: &[String])
301310
Ok(_) => query_count += 1,
302311
}
303312
}
304-
println!("Successfully executed {} queries.", query_count);
313+
println!("Finished executing {} queries.", query_count);
305314
Ok(())
306315
}
307316

src/repl/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ pub(crate) mod line_reader;
1818

1919
pub(crate) trait ReplContext: Sized {
2020
fn current_repl(&self) -> &Repl<Self>;
21-
22-
fn has_changes(&self) -> bool;
2321
}
2422

2523
pub(crate) struct Repl<Context> {

0 commit comments

Comments
 (0)