diff --git a/src/clause_types.rs b/src/clause_types.rs index 89e4d00d..97aac77a 100644 --- a/src/clause_types.rs +++ b/src/clause_types.rs @@ -178,6 +178,8 @@ pub enum SystemClauseType { MakeDirectory, DeleteFile, WorkingDirectory, + PathCanonical, + FileModificationTime, DeleteAttribute, DeleteHeadAttribute, DynamicModuleResolution(usize), @@ -345,6 +347,8 @@ impl SystemClauseType { &SystemClauseType::MakeDirectory => clause_name!("$make_directory"), &SystemClauseType::DeleteFile => clause_name!("$delete_file"), &SystemClauseType::WorkingDirectory => clause_name!("$working_directory"), + &SystemClauseType::PathCanonical => clause_name!("$path_canonical"), + &SystemClauseType::FileModificationTime => clause_name!("$file_modification_time"), &SystemClauseType::REPL(REPLCodePtr::CompileBatch) => clause_name!("$compile_batch"), &SystemClauseType::REPL(REPLCodePtr::UseModule) => clause_name!("$use_module"), &SystemClauseType::REPL(REPLCodePtr::UseQualifiedModule) => { @@ -681,6 +685,8 @@ impl SystemClauseType { ("$make_directory", 1) => Some(SystemClauseType::MakeDirectory), ("$delete_file", 1) => Some(SystemClauseType::DeleteFile), ("$working_directory", 2) => Some(SystemClauseType::WorkingDirectory), + ("$path_canonical", 2) => Some(SystemClauseType::PathCanonical), + ("$file_modification_time", 2) => Some(SystemClauseType::FileModificationTime), ("$use_module", 1) => Some(SystemClauseType::REPL(REPLCodePtr::UseModule)), ("$use_module_from_file", 1) => Some(SystemClauseType::REPL(REPLCodePtr::UseModuleFromFile)), diff --git a/src/lib/files.pl b/src/lib/files.pl index 9e54bf33..adaa1e94 100644 --- a/src/lib/files.pl +++ b/src/lib/files.pl @@ -52,10 +52,13 @@ directory_exists/1, delete_file/1, make_directory/1, - working_directory/2]). + working_directory/2, + path_canonical/2, + file_modification_time/2]). :- use_module(library(error)). :- use_module(library(lists)). +:- use_module(library(charsio)). list_of_chars(Cs) :- must_be(list, Cs), @@ -87,7 +90,45 @@ delete_file(File) :- list_of_chars(File), '$delete_file'(File). +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Dir0 is the current working directory, and the working directory + is changed to Dir. + + Use working_directory(Ds, Ds) to determine the current working directory, + and leave it as is. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + working_directory(Dir0, Dir) :- can_be(list, Dir0), can_be(list, Dir), '$working_directory'(Dir0, Dir). + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + True iff Cs is the canonical, absolute path of Ps. + + All intermediate components are normalized, and all symbolic links + are resolved. + + The predicate fails in the following situations, though not + necessarily *only* in these cases: + + 1. Ps is a path that does not exist. + 2. A non-final component in Ps is not a directory. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +path_canonical(Ps, Cs) :- + must_be(list, Ps), + maplist(must_be(character), Ps), + can_be(list, Cs), + '$path_canonical'(Ps, Cs). + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + T is the modification time of File. + T is a time stamp, suitable for use in format_time//2 in library(time). + + For two time stamps A and B, if A precedes B, then A @< B holds. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +file_modification_time(File, T) :- + '$file_modification_time'(File, T0), + read_term_from_chars(T0, T). diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 77ef1366..9e969c7b 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -918,6 +918,47 @@ impl MachineState { return Ok(()); } } + &SystemClauseType::PathCanonical => { + let path = self.heap_pstr_iter(self[temp_v!(1)]).to_string(); + + match fs::canonicalize(path) { + Ok(canonical) => { + let cs = + match canonical.to_str() { + Some(s) => { s } + _ => { + let stub = MachineError::functor_stub(clause_name!("path_canonical"), 2); + let err = MachineError::representation_error(RepFlag::Character); + let err = self.error_form(err, stub); + + return Err(err); + } + }; + let chars = self.heap.put_complete_string(cs); + self.unify(self[temp_v!(2)], chars); + } + _ => { + self.fail = true; + return Ok(()); + } + } + } + &SystemClauseType::FileModificationTime => { + let file = self.heap_pstr_iter(self[temp_v!(1)]).to_string(); + + if let Ok(md) = fs::metadata(file) { + if let Ok(time) = md.modified() { + let chars = self.systemtime_to_timestamp(time); + self.unify(self[temp_v!(2)], chars); + } else { + self.fail = true; + return Ok(()); + } + } else { + self.fail = true; + return Ok(()); + } + } &SystemClauseType::AtEndOfExpansion => { if self.cp == LocalCodePtr::TopLevel(0, 0) { self.at_end_of_expansion = true; @@ -3202,18 +3243,7 @@ impl MachineState { self.unify(a1, addr); } &SystemClauseType::CurrentTime => { - let system_time = SystemTime::now(); - let datetime: DateTime = system_time.into(); - - let mut fstr = "[".to_string(); - let specifiers = vec!["d","m","Y","y","H","M","S","b","B","a","A","w","u","U","W","j","D","x","v"]; - for spec in specifiers { - fstr.push_str(&format!("'{}'=\"%{}\", ", spec, spec).to_string()); - } - fstr.push_str("finis]."); - let str = { let s = datetime.format(&fstr).to_string(); - self.heap.put_complete_string(&s) - }; + let str = self.systemtime_to_timestamp(SystemTime::now()); self.unify(self[temp_v!(1)], str); } &SystemClauseType::OpDeclaration => { @@ -5693,6 +5723,23 @@ impl MachineState { return_from_clause!(self.last_call, self) } + pub(super) + fn systemtime_to_timestamp( + &mut self, + system_time: SystemTime + ) -> Addr { + let datetime: DateTime = system_time.into(); + + let mut fstr = "[".to_string(); + let specifiers = vec!["Y","m","d","H","M","S","y","b","B","a","A","w","u","U","W","j","D","x","v"]; + for spec in specifiers { + fstr.push_str(&format!("'{}'=\"%{}\", ", spec, spec).to_string()); + } + fstr.push_str("finis]."); + let s = datetime.format(&fstr).to_string(); + self.heap.put_complete_string(&s) + } + pub(super) fn string_encoding_bytes( &mut self,