os_lib.pl -- Operating system interaction predicates.

This library collects a number of predicates useful for OS interactions. The emphasis here is on operations on files and directories rather than on calling OS commands. Unlike the system predicates of SWI/Yap here we adhere to the <lib>_ convention prefix that allows for more succinct predicate names. The assumption is that by using prefix "os", there will be a main argument that is an OS entity, so the predicate name does not have to explicitly refer to all the arguments. For instance

Highlights

polymorphic
4 ways to name OS objects
casting
and particulalry output variable casting via variable decoration
os_exists(Os, Opts)
works on files and dirs by default, and can be specialised via Opts
os_postfix(Psfx, Os, Posted)
os_postfix/3 add a bit on a OS name to produce a new one
os_ext(Ext, Stem, Os)
os_ext/3 is a renamed file_name_extension/3 with few extra bits
os_unique(Token, Os, Opts)
constructs unique filenames either based ondate (and possible time stamp) or on versioning
os_dir_stem_ext(Dir, Stem, Ext, Os)
os_dir_stem_ext/4 construct and de-construct OS names from/to its main parts
os_dir_stem_ext(Os, Opts)
construct
os_mill(File, Goal, Milled, Opts)
os_mill/4 allows construction of evolving pipelines
os_file(File)
os_file/1 backtrack over all files in current directory

In addition, the library is polymorphic in naming OS objects by supporting 4 different os term structures:

Currently the emphasis is on file name manipulations but command (eg copy_file) are likely to be included in new versions. Main reason why they are not yes, is that we use pack(by_unix).

Installation

To install

?- pack_install( os_lib ).

to load

?- use_module( library(os) ).

or

?- use_module( library(os_lib) ).

Opts

The library attempts to keep a consistent set of options that are on occasions funnelled through to either other interface or commonly used private predicates.

Common options:

dir(Dir = .)
directory for input and output
idir(Idir = .)
directory for input (overrides Dir)
odir(Odir = .)
directory for output (overrides Dir)
ext(Ext=)
extension for file
stem(Stem)
stem to be used for constructing os names
sub(Sub)
apply operation recursive to sub directories

Variable name conventions

var(Os)
An os entity

Casts

(os_cast/3,os_cast/2)

\ Os
casts to /-terms
+ Os
casts to atoms
&(Os)
casts to strings
@(Os)
casts to alias (input must be an alias to start with)

Nonmeclature

Predicates

The library predicates can be split to 4 groups.

  1. Predicates for manipulating and constructing OS entity names
  2. Commands
  3. Logical
  4. Helpers

Info

author
- nicos angelopoulos
version
- 0.0.1 2015/4/30
- 0.0.2 2015/4/30 added module documentation
- 0.0.3 2015/12/10 redone the typing and added better alias support, started custom errors
- 0.1.0 2016/2/24 first publisc release
- 0.6.0 2017/3/10 works with pack(lib)
- 1.0.0 2018/3/18
- 1.2.0 2018/8/5 added os_files/1,2 and os_dirs/1,2 (with options) and removed os_dir_files/2 and os_dir_dirs/2.
- 1.3 2018/10/1 cleaner error handling via throw, new opt dots(D), os_cast/3 arguments switch
- 1.4 2019/4/22 option sub(Sub); cp_rec.pl script; list of postfixes, etc
- 1.5 2019/4/22 os_path/2, fixes and new options to os_mill/4 ; os_exists/2 (return type) & os_sel/4
See also
- http://www.stoics.org.uk/~nicos/sware/os
- http://www.stoics.org.uk/~nicos/sware/os/html/os_lib.html
- doc/Releases.txt
To be done
- os_pwd/1 (working_directory + casting)
- use os_path/3 as a template to convert all lib predicates to castable outputs.
- there might a bit of dead code around
 os_dir_stem_ext(-Os, +Opts)
Create an os object (Os) from bits in Opts.

Opts

ext(Ext=)
extension for File
dir(Dir = .)
directory for File (if ODIr is not given)
dir_surpress_ext(Dre=true)
when true, do not use extension for Os
odir(ODir)
preferred option when constructing Os
stem(Stem)
stem for Os
type(Type=file)
or dir for directory (type only matters for handling extension)
?- os_dir_stem_ext( Os, [stem(file),ext(csv)] ).
Os = file.csv.

?- os_dir_stem_ext( Os, [stem(some_dir),ext(csv),type(dir)] ).
Os = some_dir.
author
- nicos angelopoulos
version
- 0.1 2016/6/24
- 0.2 2017/7/6, added type() and dir_surpress_ext()
See also
- module docs (how to cross-ref?)
 os_dir_stem_ext(+Dir, +Stem, +Ext, -File)
os_dir_stem_ext(-Dir, -Stem, -Ext, +File)
Construct and deconstruct filename File, from and to components: directory Dir, stem Stem and extension Ext.
 ?- os_dir_stem_ext( data/what, file, csv, File ).
 File = data/what/file.csv.
 
 ?- os_dir_stem_ext( 'data/what', file, csv, File ).
 File = 'data/what/file.csv'.
 
 ?- os_dir_stem_ext( library(os), file, csv, File ).
 File = library('os/file.csv').

 ?- os_dir_stem_ext( Dir, Stem, Ext, library('os/file.csv') ).
 Dir = '/usr/local/users/nicos/local/git/lib/swipl-7.3.16/library/os',
 Stem = file,
 Ext = csv.
 
 ?- os_dir_stem_ext( dir, stem, ext, /Os ).
 dir/stem.ext.

 ?- os_dir_stem_ext( "data/what", file, csv, File ).
 File = "data/what/file.csv".

 ?- os_dir_stem_ext( "data/what", file, csv, +File ).
 File = 'data/what/file.csv'.

Note: there is no quarantee the path to the file or the file itself exist.

Also File, might a relative or absolute reference depending on Dir's disposition.

author
- nicos angelopoulos
version
- 0.3 2016/2/7
See also
- was dir_stem_ext_file/4
 os_stem(-Stem, +File, +Opts)
os_stem(+Stem, -File, +Opts)
os_stem(-Stem, -File, +Opts)
 os_stem(-Stem, +Opts)
This predicate is meant to be used for generating the second argument (File). See os_path/3 if you looking for a more general relation between Stem, File and Dir. When Stem is a variable, Psfx should be given in Opts below. In addition, hen os_stem/2 is used, From should be given.

Opts

dir(Dir)
default dir only used if ODir is missing, or to return Dir if File is given
odir(ODir = )
output directory
ext(Ext = )
extension for File in relation to Stem
from(From)
get stem from From (also need to supply postfix())
use_from_dir(UseFromDir=true)
when both Stem and File are variables and From is given, setting this to false will use the parent of From as the destination directory (else, either Odir or Dir is used). The default is true except when either Dir or ODir are given in Opts
postfix(Psfx)
added to From,
?- os_stem( abc, File, dir(sub) ).
File = 'sub/abc'.

?- os_stem( abc, File, [dir(sub),ext(csv)] ).
File = 'sub/abc.csv'.

?- os_stem( Var, File, [from(abc/foo.bar),postfix(ps)] ).
Var = File, File = abc/foo_ps.

?- os_stem( Stem, File, [from(abc/foo.bar),postfix(ps),dir(doc)] ).
Stem = abc/foo_ps,
File = doc/foo_ps.

?- os_stem( Stem, File, [from(abc/foo.bar),postfix(ps),dir(doc),use_from_dir(true)] ).
Stem = File, File = abc/foo_ps.

?- os_stem( Stem, +(File), [from(abc/foo.bar),postfix(ps),dir(doc)] ).
Stem = abc/foo_ps,
File = 'doc/foo_ps'.

?- os_stem( "abc", File, dir(sub) ).
File = 'sub/abc'.

?- os_stem( "abc", File, dir("sub") ).
File = "sub/abc".

?- os_stem( abc, File, dir("sub") ).
 os_ext(?Ext, +File)
 os_ext(+Ext, +Stem, -File)
os_ext(-Ext, -Stem, +File)
 os_ext(?Ext, +New, +File, -NewFile)
os_ext(?Ext, ?Stem, ?File, +Opts)
Switch the position of the first two arguments of file_name_extension/3. Ext is the file extension of File. Provides an arity 2 version of file_name_extension/3. This is appropriate for going through include/3 to filter files of a kind in a list of filenames. In os_ext/3, Ext is not added if it is already the extension to Stem (new in 0.2). In os_ext/4, if the last argument is ground, it is taken to be Opts.

New is a replacement of Ext in File that produces NewFile. Contrary to file_name_extension/3, os_ext/3 allows dots in Ext.

  ?- file_name_extension( X, tar.gz, abc.tar.gz ).
  false.
  ?- os_ext( tar.gz, Stem, dir/abc.tar.gz ).
  Stem = dir/abc

  ?- include( os_ext(pl), ['what.pl',none], Pls ).
  Pls = ['what.pl'].

  ?- maplist( os_ext(xls,csv), [abc.xls,def.xls], New ).
  New = [abc.csv, def.csv].

  ?- os_ext( Ext, library(abc.txt) ).
  Ext = txt.

  ?- os_ext( csv, file.csv, File ).
  File = file.csv.

  ?- os_ext( csv, a.file.csv, File ).
 File = a.file.csv.

 ?- os_ext( X, S, a.file.csv ).
 X = csv,
 S = a.file.

 ?- os_ext( Old, new, afile.csv, Afile ).
 Old = csv,
 Afile = afile.new.

 ?- os_ext( Old, new, library(afile.csv), Afile ).
 Old = csv,
 Afile = library(afile.new).

 ?- os_ext( csv, abc/def, Os ).
 Os = abc/def.csv.
 
 ?- os_ext( csv, library(def), Os ).
 Os = library(def.csv).

 ?- os_ext( csv, /def, Os ).
 Os = /def.csv.
 
 ?- os_ext( csv, abc/def, Os ).
 Os = abc/def.csv.
 
 ?- os_ext( csv, "abc/def", Os ).
 Os = "abc/def.csv".
 
 ?- os_ext( csv, "abc/def", +Os ).
 Os = 'abc/def.csv'.
 
 ?- os_ext( Ext, Stem, "afile.csv" ).
 Ext = "csv",
 Stem = "afile".

 ?- os_ext( srt, 'a.file', 'a.file.srt' ).
 true.

?- os_ext( Old, srt, 'a.file', Afile ). Old = file, Afile = a.srt.

?- os_ext( txt, Csv, old.txt, New ). ERROR: os_ext/4: Ground argument expected at position: 2, (found: _7644) ?- os_ext( txt, Csv, New ). ERROR: os_ext/3: Ground arguments expected in some of the positions: [[1,2],3], but found:[txt,_8390,_8396] ==

author
- nicos angelopoulos
version
- 0.2 2015/05/18 changed behaviour to not adding Ext if it is already in Stem.
- 0.3 2016/02/05 added some os typing, and ground Ext can be multi doted
- 0.4 2016/01/04 various teething problems after public release
- 1.0 2018/10/11 allows 4th arg to be options. Errors updates, and trails.
 os_remove(+File)
 os_remove(+File, +OptS)
 os_rm(+File)
 os_rm(+File, +OptS)
File will be deleted if it exists. Extends built-in delete_file/1.
Version 1.2 added Options. Opts could be a single option term, see options_append/4.
Version 1.3 provides better messaging via throw/2.

Opts

err(Ex=exists)
controls, messaging and execution if file does not exist (via throw/2). values: error, test, exists; or use options on_exit(E) message(M)
debug(Dbg=false)
allows informational message printing for deleting and failed operation
?- os_remove( sk, true ).
Warning: os:os_rm/2: OS file: sk, does not exist
false.

?- @touch(sk).
true.

?- os_remove( sk, debug(true) ).
% Deleting existing file: sk
true.

?- os_remove( sk, err(test) ).
false.

?- os_remove( sk, err(error) ).
ERROR: os:os_rm/2: OS file: sk, does not exist

?- os_remove( sk, [on_exit(error),message(warning)] ).
Warning: os:os_rm/2: OS file: sk, does not exist

?- os_remove( sk, [on_exit(false),message(informational)] ).
% os:os_rm/2: OS file: sk, does not exist
false.

?- os_remove( sk, [on_exit(fail),message(warning)] ).
Warning: os:os_rm/2: OS file: sk, does not exist
false.
author
- Nicos Angelopoulos
version
- 0.1 2014/09/10
- 0.2 2018/10/1, use throw/2
 os_make_path(+Path)
 os_make_path(+Path, +Opts)
os_make_path(-Path, +Opts)
Mostly as make_directory_path/1, but with options. Also it can generate Path from its options.

Opts

afresh(Afresh=false)
when Afresh==true, predicate removes path before creating it afresh
debug_exists(DbgExists=false)
The predicate only reports when directory did not exist by default. Turn this to true to print a debug message when the directory did exist and Dbg is true
debug(Dbg=false)
as per options_append/4 when true it prints a debugging message. The predicate does not listen to debug(os_make_path), instead it uses options_debug/3.
dir(Dir)
operate on Dir if Path is a variable and Odir is not given
odir(Odir)
operate on Odir if Path is a variable (preceeds over Dir).
descend(Desc=false)
whether to move to newly created directory
make_path(MkPath=true)
do not go on with operation if MkPath is false
parent(Par)
returns current directory (useful when Move == true)
?- os_make_path( '/tmp/what', debug(true)  ).
% Creating path: '/tmp/what'
true.

?- os_make_path( '/tmp/what', debug(false)  ).
true.

?- os_make_path( '/tmp/what', debug(true)  ).
true.

?- os_make_path( '/tmp/what', debug_exists(true)  ).
true.

?- os_make_path( '/tmp/what', [debug_exists(true),debug(true)]  ).
% Path existed: '/tmp/what'
true.

?- os_make_path( '/tmp/what1', debug(false)  ).
true.

?- os_make_path( '/tmp/what2' ).
true.

?- os_make_path( "/tmp/what4" ).
true.

?- os_make_path( /tmp/what5 ).
true.

?- os_make_path( library(tmp) ).

?- os_make_path( Path, odir('/tmp/what3') ).
Path = '/tmp/what3'.

?- ls('/tmp/what3').
true.

?- os_make_path( Path, true ).

ERROR: Domain error: `Path, first argument in os_path_make/2 ground, or options containing dir/1 or odir/2' expected, found `'Opts'=['$restore'(os_make_path,debug,false),true,debug(false),afresh(false),debug_exists(false)]'

?- os_make_path( '/tmp/new1', [make_path(false),debug(true)] ).
% Skipping creation of path: '/tmp/new1'
true.
See also
- options_append/4 (pack(options))
- options_debug/3 (pack(options))
 os_mill(+FromOs, +Goal, ?Milled, +Opts)
Generate or recreate Milled from FromOs by calling Goal. If Milled is not given its name is generated by applying os_postfix/3 on FromOs with SepPsf as the postfix. Milled is usually a file, but it can also be a directory. By default, Goal is trusted to generate the correct format. If FromOs is of the form @(Callable), then call(Callable,Obj) is called to establish the location of OsObj which is used in place of FromOs.

As of 0.3, when Type=dir and OutsTo is a filename, Milled is created as to house the file. Goal, should be aware of this- as it might be attempting to create it too.

The Goal is called as call( Goal, RelFromOs, Milled, Co ).

Opts

The predicate uses os_dir_stem_ext/2 to construct OS, so its options can be used in addition to the above.

The default postfix (P) is taken to be the predicate name of Goal, minus a possible 'file_' prefix.

?- assert( (true(A,B,C) :- write(args(A,B,C)), nl) ).

?- os_mill( abc.txt, true, Outf, [] ).
args(abc.txt,abc_true.txt,[])
ERROR: os:os_mill/4: OS milled: abc_true.txt  was not created (source was: abc.txt)

?- os_mill( abc.txt, true, Outf, not_created(fail) ).
args(abc.txt,abc_true.txt,[])
false.

?- use_module(library(debug)).
true.

?- [user].
|: go_from_here_to_there( Here, There, HTopts ) :-
|:     debug( testo, 'Here: ~w', [Here] ),
|:     debug( testo, 'There: ~w', [There] ),
|:     debug( testo, 'TheOpts: ~w', [HTopts] ).
|: ^D% user://1 compiled 0.01 sec, 1 clauses
true.

?- debug(testo).
true.

?- Milts = [outputs_to('debug_outs.txt'),type(dir),debug(true)],
    os_mill( here, go_from_here_to_there, ex_os_milled, Milts ),
    write( milled(ex_os_milled) ), nl.
% Creating non-existing mill entity: ex_os_milled, from: here
% Calling os_mill: user:go_from_here_to_there/0
% Opened:'ex_os_milled/debug_outs.txt', at:<stream>(0x558dbb1d98c0)
% Changing channels to: io_streams(user_input,<stream>(0x558dbb1d98c0),<stream>(0x558dbb1d98c0))
% Closing: <stream>(0x558dbb1d98c0)
% Run output at: 'ex_os_milled/debug_outs.txt'
milled(ex_os_milled)
Milts = [outputs_to('debug_outs.txt'), type(dir), debug(true)].

?- shell( 'cat ex_os_milled/debug_outs.txt').
% Calling: call(user:go_from_here_to_there,here,ex_os_milled,[])
% Here: here
% There: ex_os_milled
% TheOpts: []
% Caught: exit
% Reverting streams to: io_streams(<stream>(0x7f4311820780),<stream>(0x7f4311820880),<stream>(0x7f4311820980))
true.

?-
author
- nicos angelopoulos
version
- 0.1 2014/10/15
- 0.2 2016/06/28
- 0.3 2020/09/16, added outputs_to() & outputs_as_tty(), with example
To be done
- double check non atomic File and Milled
 os_un_zip(+File, ?Stem, +Opts)
 os_un_zip(+File)
 os_un_zip(+File, ?Stem)
Does a simple and safe job of gunzipping files. If uncompressed file exists the predicate skips via default, but other behaviours can be defined via Opts.

Opts

debug(Dbg=false)
see options_append/3
keep(Keep=false)
if true, pass Keep option to
on_exists(OnExists=skip)
skip: skips the un_zipping (with debug message), error: throws error quiet: skip with no debug message, redo: run the un_zip regardless
?- tell( ex.txt ), maplist( writeln, [1,2,3,4,5] ), told, shell( 'gzip ex.txt' ), ls.

?- os_un_zip( ex.txt.gz, Stem1, keep(true) ).
?- os_un_zip( ex.txt.gz, Stem2, on_exists(error) ).

Second time should be an error as the gunzipped file already exists.

?- tell( ex2.txt ), maplist( writeln, [1,2,3,4,5] ), told, shell( 'gzip ex2.txt' ), ls.
?- os_un_zip( ex2.txt.gz, Stem, [keep(true),debug(true)] ).
% Sending: gunzip(-k,-d,ex2.txt.gz)
Stem = ex2.txt.
% Stem: ex.txt exists, so skipping un_zipping of file: ex.txt.gz
Stem = ex.txt.

Second time the debug message is different.

author
- nicos angelopoulos
version
- 0.1 2014/7/2
- 0.2 2016/6/20, changed from gunzip/2 to os_un_zip/2, added options
To be done
- deal with .tgz files (do not need this yet)
- use different engines ? with auto-recognision from extensions ?
 os_parts(+Parts, -Stem)
os_parts(-Parts, +Stem)
 os_parts(?Parts, ?Stem, +Opts)
Construct and deconstruct stems of files from/to their constituents.

Opts

separator(Sep=Sep)
defaults to flag filename_separator by preference
sep(Sep=Sep)
shortened separator()
?- os_parts( Parts, abc_def ).
Parts = [abc, def].

?- os_parts( Parts, 'abc_def-xyz', sep(-) ).
Parts = [abc_def, xyz].
 os_path(+Dir, +File, -Path)
os_path(-Dir, -File, +Path)
 os_path(-Parts, +Path)
os_path(+Parts, -Path)
Mostly a polymorphic directory_file_path/3 nickname. Also '' is the default rather than '/' when dealing with absolute paths. The predicate understands all the input types known to os_name/2. Path takes its type from Dir and vice versa- when mode is reversed. If you require Path to be of specific type regardless of what Dir is decorate -Path as explained is os_name/2.
?- os_path( D, F,  '/abc/def/' ).
D = '/abc',
F = def.

?- os_path( D, F,  "/abc/def/" ).
D = "/abc",
F = "def".

?- os_path( D, F,  /abc/def/ ).
ERROR: Syntax error: Unbalanced operator
ERROR: os_path( D, F,  /abc/def/
ERROR: ** here **
ERROR:  ) .

?- os_path( D, F,  /abc/def ).
D = /abc,
F = def.

?- os_path( abc/def, ghi.txt, Path ).
Path = abc/def/ghi.txt.

?- os_path( /abc/def, ghi.txt, Path ).
Path = /abc/def/ghi.txt.

?- os_path( '', abc, Abc ).
Abc = abc.

?- directory_file_path( '', abc, Abc ).
   Abc = '/abc'.

?- os_path( Dir, Base, '/abc' ).
Dir = '',
Base = abc.

?- directory_file_path( Dir, Base, '/abc' ).
Dir =  (/),
Base = abc.


?- os_path( hmrn(library(what)), abc, Abc ).
ERROR: pack(os): Nested aliases are not supported yet.
Found: hmrn(library(what)) at position: 1 for predicate: os_path/3

?- file_search_path( hmrn, Hmrn ).
Hmrn = '/home/nicos/ac/14mg/cohorts/hmrn'.

?- os_path( hmrn(what), if, Abc ).
Abc = hmrn('what/if').

?- os_path( hmrn(what/if), not, Abc ).
Abc = hmrn(what/if/not).

?- os_path( hmrn(what/if), not, +Abc ).
Abc = '/home/nicos/ac/14mg/cohorts/hmrn/what/if/not'.

?- os_path( hmrn(what/if), not, \Abc ).
Abc = /home/nicos/ac/'14mg'/hmrn/what/if/not.

?- os_path( hmrn(what/if), not, @Abc ).
Abc = hmrn(what/if/not).

?- os_path( "what/if", foo.txt, Abc ).
Abc = "what/if/foo.txt".

?- os_path( Parts, 'a/b/c.txt' ), os_path( Parts, Rel ).
Parts = [a, b, c.txt],
Rel = 'a/b/c.txt'.

?- os_path( Parts, '/a/b/c.txt' ), os_path( Parts, Rel ).
Parts = ['', a, b, c.txt],
Rel = '/a/b/c.txt'.
author
- nicos angelopoulos
version
- 0.4 2020/9/14
 os_postfix(-Postfix, +Posted)
 os_postfix(+Postfix, +Fname, -Posted)
os_postfix(+Postfix, -Fname, +Posted)
 os_postfix(+Postfix, ?Fname, ?Posted, +Opts)
os_postfix(+Postfix, +Opts, ?Fname, ?Posted)
Append a Postfix atom (or list of postfix atoms) to a filename without touching its file type extension. Also works for removing a postfix. If Postfix is compound of arity/1 is taken to be an aliased path, in which case the innermost path is extended.

Second argument is allowed to be the options (recognised as such when input is a list) so that it can be used in meta-calls.

Opts

ext(Ext)
if Ext is ground is assumed to be the strippable part of Fname. When Ext is a variable the found extension is bound to it. This option is for testing and returning, see with_ext() for setting alternative extensions.
ignore_post(Posts=[])
(separated) parts to ignore at end (see ex. below)
with_ext(WithExt)
replace extension of Fname with WithExt, if ground (you can pick up the old one using an unbound varible in ext(Ext) above)
postfix(Psfx)
alternative way of defining Postfix, only used when Postfix is a variable
replace(Rplc=false)
replace relevant part of filename instead of adding new postfix
separator(Sep=Sep)
expansion of sep(Sep) - canonical is sep(Sep)
sep(Sep=_)
shortened separator() separator for stem-file parts (see os_sep/2)
 ?- os_postfix( abc, library(x.txt), T ).
 T = library(x_abc.txt).
 ?- os_postfix( abc, library(x.txt), T, [separator(-)] ).
 T = library('x-abc.txt').
 ?- os_postfix( abc, x.txt, T, sep(.) ).
 T = x.abc.txt.
 ?- os_postfix( v1, graph_layout.csv, T, [ignore_post(layout)] ).
 T = graph_v1_layout.csv.
 ?- os_postfix( v1, graph_lay_out.csv, T, [ignore_post(layout)] ).
 T = graph_lay_out_v1.csv.
 ?- os_postfix( v1, graph_lay_out.csv, T, ignore_post([lay,out]) ).
 T = graph_v1_lay_out.csv.
 ?- os_postfix( v1, graph_lay_out.csv, T, [ignore_post([out]),replace(true)] ).
 T = graph_v1_out.csv
 ?- maplist( os_postfix(v1,[sep(-)]),[a.csv,b.csv], AB ).
 AB = ['a-v1.csv', 'b-v1.csv'].
 ?- os_postfix( _, library(x.txt), T, postfix(abc) ).
 T = library(x_abc.txt).
 ?- os_postfix( _, "x.txt", T, postfix(abc) ).
 T = "x_abc.txt".
 ?- os_postfix( Psf, abc_def.txt ).
 Psf = def.
 ?- os_postfix( bit, by.csv, ByBit, with_ext(txt) ).
 ByBit = by_bit.txt.
 ?- os_postfix( [by,bit], bit.csv, ByBit, with_ext(txt) ).
 ByBit = bit_by_bit.txt.
author
- nicos angelopoulos
version
- 0.2 2014/7/8 changed order of 1&2 make it more suitable to meta calls
- 0.3 2014/7/28 added aliased paths and example
- 0.4 2014/12/2 added options (separator/1,ignore_post/1,replace/1)
 os_repoint(+Link, +Target)
Repoint an existing Link to a new Target. Pred fails if Link exists but not a link. Pred is debug(os_repoint) aware.
?- debug( os_repoint ).
?- shell( 'touch atzoumbalos' ).
?- shell( 'ln -s atzoumbalos shortos' ).
?- shell( 'touch atzoukos ).
?- os_repoint( shortos, atzoukos ).
% Warning, repointing link did not exist. Creating: shortos
% Linked to: '/home/nicos/pl/packs/private/os/atzoukos'
?- os_repoint( shortolos, atzoumbalos ).
% Repointing existing link: shortolos
% Old target was: '/home/nicos/pl/packs/private/os/atzoukos'
% Linked to: '/home/nicos/pl/packs/private/os/atzoumbalos'
?- os_repoint( danglink, atzou ).
% Warning, repointing link did not exist. Creating: danglink
% Linked to: '/home/nicos/pl/packs/private/os/atzou'
?- exists_file( danglink ).
false.
?- os_exists( danglink ).
false.
?- os_exists( danglink, type(flink) ).
false.
?- os_exists( danglink, type(link) ).
true.
author
- nicos angelopoulos
version
- 0.1 2014/7/23
See also
- was repoint_link/2
To be done
- extend interface to control link_file/3 3rd argument?
 os_slashify(-Path, +Slashed)
os_slashify(+Path, -Slashed)
Convert between Slashify and ensure de-slashified versions of os entry names. The predicate does not check the arguments map to os entries it works purely on the atomic or string representation. Path may be represented by an os slash-term structure or alias compound (see os_term/2) but Slashed is always either atomic (in most cases) or string in the case where Path is string. You can force casting as per os_cast/2.
0.2@2011/10/28, '' now goes to '', not to '/'

   0.3@2013/10/06, now also goes -Path +Slashed

   0.4@2014/10/06, changed predicate name from slashify/2
?- os_slashify( a, A ).
A = 'a/'.

?- os_slashify( "a", A ).
A = "a/".

?- os_slashify( A, 'a/' ).
A = a.

?- os_slashify( A, 'a' ).
A = a.

?- os_slashify( library(csv), Sla ).
Sla = '/home/nicos/pl/lib/src/csv/'.

?- os_slashify( /tmp/abc, &(Sla) ).
Sla = "/tmp/abc/".

?- os_slashify( /tmp/abc, Atom ).
Atom = '/tmp/abc/'.

?- os_slashify( /tmp/abc, \(Term) ).
Term = /tmp/abc.
author
- nicos angelopoulos
version
- 0.5 2016/2/23
 os_term(+Atom, -Term)
os_term(-Atom, +Term)
Bi-directional convertion between atom and slash-term representations of Os entries. Can also be used to ensure the type.
 ?- os_term( Atom, './abc/edf.g' ).
 Atom = './abc/edf.g/'.
 
 ?- os_term( Atom, '.'/abc/edf.g ).
 Atom = './abc/edf.g/'.

 ?- os_term( 'abc/edf.g', Term ), Term = B / C.
 Term = abc/edf.g,
 B = abc,
 C = edf.g.
 
 ?- os_term( Abc, /abc/def.txt ).
 Abc = '/abc/def.txt'.
 
 ?- os_term( Abc, abc/def.txt ).
 Abc = 'abc/def.txt'.

 % Can be used to ensure a dir is in atom form:
 ?- os_term( Atom, 'abc/def' ).
 Atom = 'abc/def'.
 
 ?- os_term( '/abc/edf.g', Term ), Term = /A/B .
 ? Term = /abc/edf.g,
 A = abc,
 B = edf.g.

 ?- os_term( './abc/edf.g', Term ).
 Term = ('.')/abc/edf.g.

v0.2 added

author
- nicos angelopoulos
version
- 0.2 2014/9/16,
 os_tmp_dir(-Tmp)
Creates a uniquely named directory.

Contrary to system tmp_file_stream/3 % this predicate does not remove the directory at halt. The directory is placed in /tmp/ so it wouldn't surive a reboot.

author
- nicos angelopoulos
version
- 0.1 2014/4/2 (some time well before)
 os_name(+Os, -NameType)
NameType is the type of name for Os. The possible types are atom, slash, string and alias. The alias type only succeeds if Os is a compound of arity one, and its functor matches a current known alias. When Os is a variable, Type is atom.

When Os matches +(_), \(_) , &(_) or @(_) then the corresponding type is atom, slash string and alias, respectively. In this case, then first argument is not inspected.

Types

 ?- os_name(_,Type).
 Type = atom.
 ?- os_name(abc,Type).
 Type = atom.
 ?- os_name('abc/def',Type).
 Type = atom.
 ?- os_name("abc/def",Type).
 Type = string.
 ?- os_name(abc/def,Type).
 Type = slash.
 ?- os_name(+(_),Type).
 Type = atom.
 ?- os_name(\(_),Type).
 Type = slash.
 ?- os_name(&(_),Type).
 Type = string.
 
author
- nicos angelopoulos
version
- 0.1 2014/9/18, this used to return compound
- 0.2 2015/12/10, now we dissect compound to slash and alias. added errors.
 os_unique(+TokenS, -Os)
 os_unique(+TokenS, -Os, +Opts)
Create a unique file or directory named Os by using date elements and a Token or list of Tokens. Token can be a list in which case the Separator option will also apply within the token parts.

The predicate does not only provide the name of Os, it also, by default, creates it.

Opts:

by(By=date([ye, mo, da, [ho, mi], [se]]))
how to group: either by date with the default taking YeMoDa first, then adds HoMi and on the thrid attempt adds Seconds. For alterative ways to do dates give the respective date/3 term (date/0 is shorthand for default date pattern). For building unique via version give version(Pfx,Compon,Type,Whc) (version/0 is short for version(v,'',1,int,1))
create(Create=true)
by default the file is created
ext(Ext=csv)
extension to add to OsEntry iff type is file
max_length(Dp=ye, Ye=2)
max length of date Id (ye,mo,da) if using By=date/n or integer if using By=version/n (Nth component), and an integer
min_length(Id=, Len)
min length of date Id (ye,mo,da) if using By=date/n or integer when By=verions/n (Nth component), and an integer
place_token(Plc=before)
or after, where to place the token in relation to the date
sep_parts(Psep=_)
how to conjoin Token and unique part (was token_sep)
sep_sub(Ssep = .)
inter date component separator (was date_sep)
sep_token(Sep = -)
separator to be used in bonding TokenS into Token
type(Type=dir)
should the unique entry be a _dir_ectory or a file.

Id and DP can be a free variables in which case they match everything.

When using dates for By and call this twice within a single second there is all the chance it will fail.

?- os_unique( res, Dname ).
Dname = 'res-14.05.22'.

?- os_unique( res, Dname, [] ).
Dname = 'res-14.05.22.10.40'.

?- os_unique( res, Dname, [] ).
Dname = 'res-14.05.22.10.40.46'.

?- os_unique( res, Dname, [] ).
Dname = 'res-14.05.22.10.40.57'.

?- os_unique( res, Dname, [min_length(_,3)] ).
Dname = 'res-014.005.022'.

?- os_unique( res, Dname, [token_sep('+'),sep_sub(':'),place_token(after),type(file)] ).
Fname = '14:05:22+res.csv'.

?- os_unique( res, Dname, [token_sep('+'),sep_sub(':'),place_token(after),type(file)] ).
Dname = '14:05:22:11:03+res.csv'.

?- os_unique( tkn, &(Bname), [type(file),ext(tsv)] ).
Bname = "tkn-16.02.23.tsv".

?- os_unique( tkn, &(Bname), [type(file),ext(tsv)] ).
Bname = "tkn-16.02.23.16.15.tsv".

?- os_unique( res, Dname, [ext(png),by(version),create(false),type(file)] ).
Dname = 'res-v01.png'.

?- os_unique( res, Dname, [ext(png),by(version),create(false),type(file)] ).
Dname = 'res-v01.png'.

?- os_unique( res, Dname, [ext(png),by(version),create(false),type(file)] ).
Dname = 'res-v01.png'.

?- os_unique( res, Dname, by(version) ).
Dname = 'res-v01'.

?- os_unique( res, Dname, by(version) ).
Dname = 'res-v02'.

?- os_unique( res, Dname, by(version) ).
Dname = 'res-v03'.

?- os_unique( res, Dname, [by(version),create(false)] ).
Dname = 'res-v04'.

?- os_unique( res, Dname, [by(version),create(false)] ).
Dname = 'res-v04'.

Used to be unique_entry_by_date/n, then unique_by_date/n.

author
- nicos angelopoulos
version
- 0.3 2016/2/23
- 0.4 2016/9/2 changed name from os_unique_by_date, now it also does versioning
 os_base(+Os, -Bname)
Like file_base_name/2 but also works for all formats accepted by os_lib
?- os_base( abc/foo.txt, Base ).
Base = foo.txt.

?- os_base( Var, Base ).
ERROR: pack(os): Ground argument expected at position: 1 for predicate: os_base/2, but, _G1156 was found

?- os_base( abc(foo.bar), Base ).
ERROR: pack(os): OS entity: abc(foo.bar), looks like aliased but alias does not exist.

?- os_base( library(foo.bar), Base ).
Base = foo.bar.

?- os_base( "abc/foo.txt", Base ).
Base = "foo.txt".

?- os_base( "abc/foo.txt", +Base ).
Base = foo.txt.
 os_cast(+Os, -Termplate)
 os_cast(+Def, +Os, -Termplate)
Cast Termplate's variable part to the Os entity as defined by Template's grounded part. If Template is a variable, Def is used as the type for casting Os onto the Template variable (via os_type_entity/3). In os_cast/2 version, Def defaults to atom.
?- os_cast( abc/edf.txt, +Var ).
Var = 'abc/edf.txt'.

?- os_cast( abc/edf.txt, \Var ).
Var = abc/edf.txt.

?- os_cast( abc/edf.txt, @Var ).
Var = abc/edf.txt.

?- os_cast( abc/edf.txt, &(Var) ).
Var = "abc/edf.txt".

?- os_cast( abc/edf.txt, Var ).
Var = 'abc/edf.txt'.

?- os_cast( atom, abc/edf.txt, Var ).
Var = 'abc/edf.txt'.

Termplates

+(V) converts to atom (atom)

\(V) converts to /-term (slash)

@(V) leave Os as is, assumes it is an alias-term
it is harder to convert arbitrary terms to aliased ones, (alias)

&(V) converts to string (string)

See also
- os_type_entity/3 for how the types are converted
To be done
- /(V) was -(V)
version
- 0.2 2018/10/1 swapped 1 & 2 args in /3 version
 os_abs(+Os, -Abs)
 os_abs(+Os, -Abs, +Opts)
Short for absolute_file_name/2 but also when Os is '' it is not interpreted as '.'. Os can a / starting slash Os term (os_name/2). Note that absolute_file_name/2 deals correctly with all other os_name/2 types.
 os_file(?File)
 os_file(?File, +Opts)
True iff File is a file or a link to an existing file, in the current directory.
Can be used to enumerate all files. The order is via sort/2.

Opts

dir(Dir = .)
directory in which to find File
dots(Dots=false)
set to true if dot starting files are required
note that '.' and '..' are never returned
solutions(Sol=single)
or findall for returning a list of all solutions
stem(Stem=rel)
what stem to add to returned files, rel: relative (default else), abs: absolute, false: no, stem
sub(Sub=false)
find files within sub directories when true
 ?- cd(pack('os_lib/examples/testo')).

 ?- os_file(File).
 File = file1 ;

 ?- os_file( & File ).
 File = "file1".
 
 ?- os_file(File, sub(true)).
 File = 'dir1/file2' ;
 File = 'dir1/link2' ;
 File = file1.
 
 ?- os_file(File, dots(true)).
 File = '.dotty1' ;
 File = file1.
 
:- absolute_file_name( pack(os_lib), OsDir ), working_directory( Old, OsDir ).
 OsDir = '/usr/local/users/nicos/local/git/lib/swipl-7.7.19/pack/os_lib',
 Old = '/home/nicos/.unison/canonical/sware/nicos/git/github/stoics.infra/'.
 
 File = pack.pl ;
 false.
 
 ?- os_file( File, solutions(findall) ).
 File = [pack.pl].
 
 ?- os_file( File, [solutions(findall),sub(true)] ).
 File = ['doc/Releases.txt', 'doc/html/h1-bg.png', 'doc/html/h2-bg.png', 'doc/html/multi-bg.png', 'doc/html/os.html', 'doc/html/pldoc.css', 'doc/html/priv-bg.png', 'doc/html/pub-bg.png', 'examples/testo/dir1/file2'|...].
 
 ?- os_file( File, [solutions(single),sub(true)] ).
 File = 'doc/Releases.txt' ;
 File = 'doc/html/h1-bg.png' ;
 File = 'doc/html/h2-bg.png' ;
 File = 'doc/html/multi-bg.png'...
author
- nicos angelopoulos
version
- 0.1 2016/1/31, this version without ref to lib(os_sub)
- 0.2 2018/7/23, added options, dir(Dir) and sub(true)
- 0.3 2018/10/1, added option dots(Dots)
- 0.4 2018/11/4, added option solutions(Sol)
 os_files(-Files)
 os_files(-Files, +Opts)
Collects all files for which os_file(File) or os_file(File,Opts) succeed.
Opts are passed to os_file/2.
 ?- absolute_file_name( pack(os_lib/src), Abs ), os_files( Files, dir(Abs) ).
 Abs = '/usr/local/users/nicos/local/git/lib/swipl-7.7.18/pack/os_lib/src',
 Files = ['/usr/local/users/nicos/local/git/lib/swipl-7.7.18/pack/os_lib/src/os_make_path.pl', '/usr/local/users/nicos/local/git/lib/swipl-7.7.18/pack/os_lib/src/os_term.pl', '/usr/local/users/nicos/local/git/lib/swipl-7.7.18/pack/os_lib/src/os_dir_stem_ext.pl', ... ]
 
author
- nicos angelopoulos
version
- 0.1 2016/1/31, this version without ref to lib(os_sub)
- 0.2 2018/8/05, added options, dir(Dir) and sub(true), removed os_dir_files/2
See also
- os_file/2
 os_dir(?OsDir)
 os_dir(?OsDir, +Opts)
True iff OsDir is a directory or a link to an existing directory, in the current directory.
Directories '.' and '..' are not returned. Can be used to enumerate all directories.

Opts

dir(Dir = .)
directory in which to find OsDir.
dots(Dots=false)
set to true if dot starting dirs are required
note that '.' and '..' are never returned
solutions(Sol=single)
or findall for returning a list for solutions
stem(Stem=false)
what stem to add to returned files, rel: relative (default), abs: absolute, false: none note, the default
sub(Sub=false)
find OsDir within sub directories when true
 
 ?-  cd( pack(os_lib) ).
 true.
 
 ?- ls.
 % doc/      pack.pl   prolog/   src/
 true.
 
 ?- os_dir(Dir), write( Dir ), nl, fail.
 doc
 prolog
 src
 false.
 
 ?- os_dir(& Dir).
 Dir = "doc" ;
 Dir = "prolog" ;
 Dir = "src" ;
 false.
 
 ?- os_dir(Os,sub(true)), write(Os), nl, fail.
 doc
 doc/html
 prolog
 src
 src/lib
 false.
 
 ?- os_dir(Os,[stem(abs),sub(true)]), write(Os), nl, fail.
 /usr/local/users/nicos/local/git/lib/swipl-7.7.18/pack/os_lib/doc/doc
 /usr/local/users/nicos/local/git/lib/swipl-7.7.18/pack/os_lib/doc/html/html
 /usr/local/users/nicos/local/git/lib/swipl-7.7.18/pack/os_lib/prolog/prolog
 /usr/local/users/nicos/local/git/lib/swipl-7.7.18/pack/os_lib/src/src
 /usr/local/users/nicos/local/git/lib/swipl-7.7.18/pack/os_lib/src/lib/lib
 false.

 ?- cd(pack('os_lib/examples/testo')).
 ?- os_dir( Dir ).
 Dir = dir1 ;
 false.
 
 ?- os_dir( Dir, dots(true) ).
 Dir = '.dodi1'
 Unknown action: ' (h for help)
 Action? ;
 Dir = dir1 ;
 false.

 ?- absolute_file_name( pack(os_lib), OsDir ), working_directory( _, OsDir ).
 OsDir = '.../lib/swipl-7.7.19/pack/os_lib',
 
 ?- ls.
 % doc/        examples/   pack.pl     prolog/     src/
 true.
 
 ?- os_dir( Dir ).
 Dir = doc ;
 Dir = examples ;
 Dir = prolog ;
 Dir = src ;
 false.
 
 ?- os_dir( Dir, solutions(findall) ).
 Dir = [doc, examples, prolog, src].
author
- nicos angelopoulos
version
- 0.2 2016/1/31, this version without ref to lib(os_sub)
- 0.3 2018/8/05, added options and harmonized with os_file/2
- 0.3 2018/10/1, added option dots(D)
- 0.4 2018/11/4, added option solutions(D)
 os_dirs(-Dirs)
 os_dirs(-Dirs, +Opts)
Find all directories for which os_dir(Dir) succeeds.
Opts are passed to os_dir/2.
 ?- cd( pack(os_lib) ).
 ?- os_dirs( Dirs ).
    Dirs = [doc,prolog, src, doc].
author
- nicos angelopoulos
version
- 0.2 2016/1/31, this version without ref to lib(os_sub)
- 0.3 2018/8/05, removed os_dir_dirs/2, now these are simply a findall on os_dir/1,2.
See also
- os_dirs/2
 os_exists(+Os)
 os_exists(+Os, +Opts)
True if Os exists as an object in the filestore. When mode is requested, the predicate goes to the source and tries to effect operations of the appropriate mode to establish permissions. The only deviation is permission execute on files. By default the predicate uses access_file(Os,execute). See option WinsFileExec.

The predicate tries to stay compatible with system predicates, but it does introduces two new file types: flink and dlink, for file point link or file, and directory pointing link or directory.

Opts

err(Err=test)
test for report and fail, fail for failing, error for throwing, true for success
(see options: err(E), on_exit(O) and message(M) in throw/2)
not(Not=false)
reverse polarity, if true require Os not to exist
type(Type)
in addition to Os existing, require file type-ness (dir,link,file,flink,dlink,any). Can be used to return the type, when input is a variable. Type = base(BaseType) streamline type to either file or dir (see os_type_base/2).
mode(Mode=exists)
one of exist, read, write and append
wins_file_exec(WinsFileExec=sys)
alternatively, use fail for failure and error for error
?- os_exists( pack(os_lib/src) ).
true.

?- os_exists( pack(os_lib/src), type(link) ).
false.

?- set_prolog_flag( allow_dot_in_atom, true ).
?- os_exists( pack(os_lib/prolog/os.pl), type(file) ).
true.

?- cd( pack('os_lib/examples/testo') ).

?- os_exists(file1).
true.

?- os_exists( "file1" ).
true

?- os_exists( file2 ).
false.

?- os_exists( file2, err(error) ).
ERROR: os:os_exists/2: OS entity: file2, does not exist

?- os_exists( file2, err(exists) ).
Warning: os:os_exists/2: OS entity: file2, does not exist
false.

?- os_exists( file2, [on_exit(fail),message(warning)] ).
Warning: os:os_exists/2: OS entity: file2, does not exist
false.

?- os_exists( file2, [on_exit(error),message(informational)] ), writeln(later).
% os:os_exists/2: OS entity: file2, does not exist

?- os_exists( file2, not(true) ).
true.

?- os_exists( file1, [not(true),err(error)] ).
ERROR: os:os_exists/2: OS entity: file1, already exists

?- os_exists( file1, type(dir) ).
false.

?- os_exists( file1, [type(dir),err(error)] ).
ERROR: os:os_exists/2: OS entity: file1, not of requested type: dir, but has type: file

?- os_exists( file1, type(flink) ).
true.

?- os_exists( file1, type(link) ).
false.

?- os_exists( dir1/link2, type(link) ).
true.

?- os_exists( dir1/link2, type(base(Base)) ).
Base = file.
 os_sep(-Sep)
 os_sep(-Sep, Opts)
Read the default or Opts provided separator.

Opts

separator(Sep=Sep)
expansion of sep(Sep) - canonical is sep(Sep)
sep(Sep=_)
shortened separator(). Sep is the separator for stem-file parts
?- os_sep( Sep ).
Sep = '_'.

?- os_sep( Sep, true ).
Sep = '_'.

?- os_sep( Sep, separator(x) ).
Sep = x.

?- os_sep( Sep, [separator(x),sep(y)] ).
Sep = y.
 os_sel(+Oses, +PatternS, -Sel)
 os_sel(+Oses, +PatternS, -Sel, +Opts)
Select a number of entries from Oses according to PatternS. Oses can be one of the following tokens: os_files, os_dirs or os_all or a list of Os objects. Tokens are expanded to the respective Os entries within the current directory. PatternS can be a list of

PatternS, a list of, or one of:

ext(Ext)
Oses with extension Ext
postfix(Psf)
is a postfix of the stem of Os
prefix(Pfx)
is a prefix of the stem of Os
sub(SubAtom)
sub_atom/5 succeeds on the stem of the Os

Opts

dir(Dir = .)
directory at which the Oses will be sought
stem(Stem=rel)
whether to return relative or absolute location
sub(Sub=false)
should sub dirs be recursed (passed to os_files/2 and os_dirs/2)
% mkdir /tmp/os_sel; cd /tmp/os_sel; touch a.pl b.txt c.pl abc.txt; mkdir abc_sub
?- os_sel( os_files, ext(pl), Sel, true ).
Sel = [a.pl, c.pl].

?- os_sel( os_files, ext(txt), Sel, true ).
Sel = [b.txt, abc.txt].

?- os_sel( os_all, sub(abc), Sel, true ).
Sel = [abc.txt, abc_sub].

?- working_directory( Old, '..' ), os_sel( os_dirs, os_, Sel, true ), working_directory( _, Old ).
Old = '/tmp/os_sel/',
Sel = [os_sel].

?- os_sel( os_files, ext(txt), Files, stem(abs) ).
Files = ['/homes/nicos/email.txt'].
author
- nicos angelopoulos
version
- 0.1 2016/ 8/24
- 0.2 2016/10/18 changed naked atoms to sub(Sub), added prefix and postfix
- 0.3 2017/2/8 allow list of patterns
- 0.4 2019/3/26 option sub()
- 0.5 2020/9/14 option stem()
 os_mv(+From, +To)
Move file from From to To. Behaves as unix mv, If To is a directory, From is moved into it (keeping From's basename).
% mkdir testo_D; touch testo_D/testo1; touch at_root
?- os_mv( testo_D/testo1, testo_D/example1 ).
true
?- os_mv( at_root, testo_D ).

?- ls( testo_D ).
% at_root    example1
true.

?-
% halt

άμπελος;src/os% rm testo_D/example1; rm testo_D/at_root
άμπελος;src/os% rmdir testo_D/;
author
- nicos angelopoulos
version
- 0.1 2016/7/
To be done
- debugging
 os_cp(+From, +To)
Copy file from From to To. Behaves as unix cp, If To is a directory, From is moved into it (keeping From's basename).
% mkdir testo_D; touch testo_D/testo1; touch at_root
?- os_cp( testo_D/testo1, testo_D/example1 ).
true
?- os_cp( at_root, testo_D ).

?- ls( testo_D ).
% at_root    example1   testo1
true.

?-
% halt

άμπελος;src/os% rm testo_D/testo1; rm testo_D/example1; rm testo_D/at_root; rm at_root
άμπελος;src/os% rmdir testo_D
author
- nicos angelopoulos
version
- 0.1 2016/8/25
To be done
- debugging
 os_ln_s(From, To)
Symbolic link file From to location To.
 os_type_base(?Type, ?Base)
Map detailed Type to Base type.

file,flink,link,any map to file and dir,dlink,link,any to dir. Predicate is deterministic and in the -,- mode it generates file for both any,link.


?- os_type_base( flink, Base ).
Base = file.

?- os_type_base( file, Base ).
Base = file.

?- cd( pack('os_lib/examples/testo') ).
?- os_exists( dir1/link2, type(base(Base)) ).
Base = file.
author
- nicos angelopoulos
version
- 0:1 2020/09/17
See also
- os_exists/2
 os_version(-Version, -Date)
Current version and release date for the library.
?- os_version( V, D ) :-
    V = 1:5:0,
    D = date(2020,9,18)