1 /** ThePath - easy way to work with paths and files
2   *
3   * Yet another attempt to implement high-level object-oriented interface
4   * to manage path and files in D.
5   * Inspired by [Python's pathlib](https://docs.python.org/3/library/pathlib.html)
6   * and [D port of pathlib](https://code.dlang.org/packages/pathlib) but
7   * implementing it in different way.
8   *
9   **/
10 module thepath;
11 
12 public import thepath.path: Path;
13 public import thepath.utils: createTempDirectory, createTempPath;
14 public import thepath.exception: PathException;
15 
16 /// Example to find configuration of current project
17 unittest {
18     import dshould;
19 
20     Path root = createTempPath();
21     scope(exit) root.remove();
22 
23     // Save current directory
24     auto const cdir = Path.current;
25     scope(exit) cdir.chdir;
26 
27     // Create directory structure
28     root.join("my-project", "some-dir", "some-sub-dir").mkdir(true);
29     root.join("my-project", "utils", "some-utility").mkdir(true);
30     root.join("my-project", "tools", "tool42", "s31").mkdir(true);
31 
32     // Create some project config file
33     root.join("my-project", "my-conf.conf").writeFile("name = My Project");
34 
35     // Let's change current working directory to test root
36     root.chdir;
37 
38     // Let's try to find project config, and expect that no config found,
39     // because our current working directory is not inside project
40     Path.current.searchFileUp("my-conf.conf").isNull.should.be(true);
41 
42     // Let's change directory to our project directory,
43     // and try to find our config
44     root.chdir("my-project");
45 
46     // Ensure that current directory now is my-project
47     version(OSX) {
48         Path.current.should.equal(
49             root.join("my-project").realPath);
50     } else {
51         Path.current.should.equal(root.join("my-project"));
52     }
53 
54     // Ensure that we can find path to config
55     auto config1 = Path.current.searchFileUp("my-conf.conf");
56     config1.isNull.should.be(false);
57     config1.get.readFileText.should.equal("name = My Project");
58 
59     // Let's change directory to 'some-sub-dir' inside our project,
60     // and try to find our config again
61     root.chdir("my-project", "some-dir", "some-sub-dir");
62 
63     // Ensure that current directory now is my-project/some-dir/some-sub-dir
64     version(OSX) {
65         Path.current.should.equal(
66             root.join("my-project", "some-dir", "some-sub-dir").realPath);
67     } else {
68         Path.current.should.equal(
69             root.join("my-project", "some-dir", "some-sub-dir"));
70     }
71 
72     // Ensure that we can find path to config even if we someshere deep inside
73     // our project tree
74     auto config2 = Path.current.searchFileUp("my-conf.conf");
75     config2.isNull.should.be(false);
76     config2.get.readFileText.should.equal("name = My Project");
77 }
78 
79 
80 /// Example of using nullable paths as function parameters
81 unittest {
82     import dshould;
83 
84     import std.typecons: Nullable, nullable;
85 
86     /* simple function, that will join 'test.conf' to provided path
87      * if provided path is not null, and return null path is provided path
88      * is null
89      */
90     Nullable!Path test_path_fn(in Nullable!Path p) {
91         if (p.isNull)
92             return Nullable!Path.init;
93         return p.get.join("test.conf").nullable;
94     }
95 
96     // Pass value to nullable param
97     auto const p1 = test_path_fn(Path("hello").nullable);
98     p1.isNull.should.be(false);
99     p1.get.segments.should.equal(["hello", "test.conf"]);
100 
101     // Pass null to nullable param
102     auto const p2 = test_path_fn(Nullable!Path.init);
103     p2.isNull.should.be(true);
104 }
105 
106 
107 /// Example of using paths in structs
108 unittest {
109     import dshould;
110 
111     struct PStruct {
112         string name;
113         Path path;
114 
115         bool check() const {
116             return path.exists;
117         }
118     }
119 
120     PStruct p;
121 
122     p.name = "test";
123 
124     // Attempt to run operation on uninitialized path will throw error
125     import core.exception: AssertError;
126     p.check.should.throwA!AssertError;
127 
128     // Let's initialize path and check it again
129     p.path = Path("some-unexisting-path-to-magic-file");
130     p.check.should.be(false);
131 }