diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..1dbcd1b --- /dev/null +++ b/meson.build @@ -0,0 +1,41 @@ +project('me.sergiotarxz.owlpath', 'vala') + +compiler = meson.get_compiler('c') +vapidir = meson.project_source_root() / 'vapi' + +is_windows = false +ws = '/' +if compiler.has_header('windows.h') + ws = '\\\\' + is_windows = true +endif + +config_h = configure_file ( + output : 'config.h', + configuration : { + 'WS': f'"@ws@"', + }, +) + +owlpath_deps = [ + dependency('glib-2.0'), + dependency('gio-2.0'), + meson.get_compiler('vala').find_library('config', dirs : vapidir), +] + +sources = [ + 'src/owlpath.vala', +] + +owlpath_lib = library('owlpath', + sources, + dependencies : owlpath_deps, + install : true, + install_dir: [true, true, true], +) + +executable('owlpath-test', + ['test/00-owlpath.test.vala'], + dependencies : owlpath_deps, + link_with: [owlpath_lib] +) diff --git a/src/owlpath.vala b/src/owlpath.vala new file mode 100644 index 0000000..9976924 --- /dev/null +++ b/src/owlpath.vala @@ -0,0 +1,156 @@ +namespace Owl { + public delegate bool VisitFileLambda (Owl.Path file); + public class Path { + static string ALPHABET_STRING = "abcdefghijklmnopqrstuvwxyz"; + GLib.File file; + bool is_tmp; + + private static string get_random_string () { + var random_generator = new GLib.Rand (); + string final_string = "valatmp-"; + for (uint64 i = 0; i < 12; i++) { + var random_number = random_generator.next_int () % ALPHABET_STRING.length; + final_string += ALPHABET_STRING[random_number].to_string (); + } + return final_string; + } + + public Path (GLib.File file) { + this.file = file; + } + + public Path.tempdir () throws GLib.Error { + this.is_tmp = true; + var file = GLib.File.new_for_path(GLib.Environment.get_tmp_dir () + Config.WS + get_random_string ()); + while (file.query_exists ()) { + file = GLib.File.new_for_path(GLib.Environment.get_tmp_dir () + Config.WS + get_random_string ()); + } + file.make_directory (); + this.file = file; + } + + ~Path () { + if (this.is_tmp) { + try { + this.@delete (); + } catch (GLib.Error error) { + GLib.critical (error.message); + } + } + } + + public void spew (string content) throws GLib.Error { + GLib.FileUtils.set_contents (this.get_path (), content); + } + + public string slurp () throws GLib.Error { + string output; + GLib.FileUtils.get_contents (this.get_path (), out output); + return output; + } + + public GLib.FileOutputStream replace () throws GLib.Error { + return this.file.replace (null, false, GLib.FileCreateFlags.PRIVATE); + } + + public GLib.FileOutputStream create () throws GLib.Error { + return this.file.create (GLib.FileCreateFlags.PRIVATE); + } + + public Owl.Path child (string relative_path) throws GLib.Error { + GLib.File child_file = this.file.resolve_relative_path (relative_path); + return new Owl.Path (child_file); + } + + public void mkpath () throws GLib.Error { + this.file.make_directory_with_parents (); + } + + public bool exists () { + return this.file.query_exists (); + } + + public Owl.Path parent () { + return new Owl.Path (this.file.get_parent ()); + } + + public void @delete () throws GLib.Error { + if (!this.is_dir ()) { + this.file.delete (); + return; + } + foreach (Owl.Path inner_file in this.children ()) { + inner_file.delete (); + } + this.file.delete (); + } + + public GLib.File get_file () { + return this.file; + } + + public string? get_path () { + return this.file.get_path (); + } + + public string basename () { + return this.file.get_basename (); + } + + public GLib.List children() { + var children = new GLib.List (); + if (!this.is_dir ()) { + GLib.critical ("You cannot list children if the target is not a directory.\n"); + return children; + } + try { + GLib.FileEnumerator enumerator = this.file.enumerate_children ( + "standard::*", + FileQueryInfoFlags.NONE); + GLib.FileInfo info; + while ((info = enumerator.next_file (null)) != null) { + GLib.File inner_file = file.resolve_relative_path (info.get_name ()); + children.append (new Owl.Path (inner_file)); + } + } catch (GLib.Error error) { + GLib.critical (error.message); + return children; + } + return children; + } + + public bool visit (bool recursive, Owl.VisitFileLambda lambda, bool visit_upper_dir = true) { + if (!this.is_dir ()) { + GLib.critical ("Unable to visit non directory\n"); + return false; + } + if (visit_upper_dir) { + bool continue_visit = lambda (this); + if (!continue_visit) { + return false; + } + } + foreach (Owl.Path inner_file in this.children ()) { + bool continue_visit = lambda (inner_file); + if (!continue_visit) { + return false; + } + if (recursive && inner_file.is_dir ()) { + continue_visit = inner_file.visit (true, lambda, false); + if (!continue_visit) { + return false; + } + } + } + return true; + } + + public bool is_dir () { + return this.query () == GLib.FileType.DIRECTORY; + } + + private GLib.FileType query () { + return this.file.query_file_type (GLib.FileQueryInfoFlags.NOFOLLOW_SYMLINKS); + } + } +} diff --git a/test/00-owlpath.test.vala b/test/00-owlpath.test.vala new file mode 100644 index 0000000..0b75f12 --- /dev/null +++ b/test/00-owlpath.test.vala @@ -0,0 +1,70 @@ +delegate void ContextLambda (); +const string S = Config.WS; +int main (string[] args) { + GLib.Test.init (ref args); + GLib.Test.add_func ("/test/owl/path-tempdir", () => { + Owl.Path file = null; + ContextLambda inner_context = () => { + Owl.Path tmp; + try { + tmp = new Owl.Path.tempdir (); + assert (tmp.exists ()); + file = new Owl.Path (GLib.File.new_for_path (tmp.get_path ())); + } catch (GLib.Error error) { + GLib.Test.fail_printf ("%s\n", error.message); + } + }; + inner_context (); + assert (!file.exists ()); + }); + GLib.Test.add_func ("/test/owl/path-mkpath", () => { + try { + var tmp = new Owl.Path.tempdir (); + var child = tmp.child (@"a$(S)b$(S)c"); + child.mkpath(); + assert (child.exists ()); + } catch (GLib.Error error) { + GLib.Test.fail_printf ("%s\n", error.message); + } + }); + GLib.Test.add_func ("/test/owl/path-spew-slurp", () => { + try { + var tmp = new Owl.Path.tempdir (); + var child = tmp.child (@"a$(S)b$(S)c"); + child.mkpath(); + var file_txt = child.child ("file.txt"); + string contents = "hola mundo\nadios hola\nadios mundo"; + file_txt.spew (contents); + assert (contents == file_txt.slurp ()); + } catch (GLib.Error error) { + GLib.Test.fail_printf ("%s\n", error.message); + } + }); + GLib.Test.add_func ("/test/owl/path-visit", () => { + try { + var hashmap = new GLib.HashTable (str_hash, str_equal); + var tmp = new Owl.Path.tempdir (); + var file1 = tmp.child(@"a$(S)b$(S)c$(S)/file1.txt"); + var file2 = tmp.child(@"a$(S)c$(S)b$(S)/file2.txt"); + file1.parent ().mkpath (); + file2.parent ().mkpath (); + file1.spew ("hola"); + file2.spew ("hola"); + tmp.visit (true, (file) => { + if (file.basename () == "a") { + hashmap.@set(file.basename (), true); + } + if (!file.is_dir ()) { + hashmap.@set(file.basename (), true); + } + return true; + }); + assert (hashmap.@get("file1.txt")); + assert (hashmap.@get("file2.txt")); + assert (hashmap.@get("a")); + } catch (GLib.Error error) { + GLib.Test.fail_printf ("%s\n", error.message); + } + }); + return GLib.Test.run (); +} diff --git a/vapi/config.vapi b/vapi/config.vapi new file mode 100644 index 0000000..16d2c94 --- /dev/null +++ b/vapi/config.vapi @@ -0,0 +1,4 @@ +[CCode (cheader_filename = "config.h", lower_case_cprefix = "")] +namespace Config { + public const string WS; +}