1 /**
2  * Authors: Tomoya Tanjo
3  * Copyright: © 2021 Tomoya Tanjo
4  * License: Apache-2.0
5  */
6 module salad.fetcher;
7 
8 ///
9 auto fetchText(string uri)
10 {
11     if (__ctfe)
12     {
13         import std.format : format;
14         assert(false, format!"%s is not supported at compile time."(__FUNCTION__));
15         //return import(uri);
16     }
17     else
18     {
19         return Fetcher.instance.fetchText(uri);
20     }
21 }
22 
23 ///
24 auto fetchNode(string uri)
25 {
26     import dyaml : Loader;
27     return Loader.fromString(fetchText(uri)).load;
28 }
29 
30 ///
31 auto scheme(string uri) @nogc nothrow pure @safe
32 {
33     import std.algorithm : findSplit;
34     if (auto split = uri.findSplit("://"))
35     {
36         return split[0];
37     }
38     else
39     {
40         return "";
41     }
42 }
43 
44 ///
45 auto pathWithAuthority(string uri) pure @safe
46 {
47     import std.algorithm : findSplit;
48     if (auto sp1 = uri.findSplit("://"))
49     {
50         auto rest = sp1[2];
51         if (auto sp2 = rest.findSplit("#"))
52         {
53             return sp2[0];
54         }
55         else
56         {
57             return rest;
58         }
59     }
60     else
61     {
62         throw new Exception("Not valid URI");
63     }
64 }
65 
66 unittest
67 {
68     assert("file:///foo/bar#buzz".pathWithAuthority == "/foo/bar");
69     assert("file:///foo/bar/buzz".pathWithAuthority == "/foo/bar/buzz");
70     assert("ssh://user@hostname:/fuga/hoge/piyo".pathWithAuthority == "user@hostname:/fuga/hoge/piyo");
71 }
72 
73 ///
74 auto fragment(string uri) @nogc nothrow pure @safe
75 {
76     import std.algorithm : findSplit;
77     if (auto split = uri.findSplit("#"))
78     {
79         return split[2];
80     }
81     else
82     {
83         return "";
84     }
85 }
86 
87 /++
88 A fetcher type that returns a string from absolute URI.
89 +/
90 alias TextFetcher = string delegate(string) @safe;
91 
92 ///
93 class Fetcher
94 {
95     ///
96     static typeof(this) instance()
97     {
98         static Fetcher instance_;
99 
100         if (instance_ is null)
101         {
102             instance_ = new Fetcher();
103         }
104         return instance_;
105     }
106 
107     ///
108     auto addSchemeFetcher(string scheme, TextFetcher fetcher)
109     {
110         schemeFetchers[scheme] = fetcher;
111     }
112 
113     ///
114     auto fetchText(string uri)
115     {
116         import salad.fetcher.exception : fetcherEnforce;
117         import std.format : format;
118 
119         auto scheme = uri.scheme;
120         auto fetcher = *fetcherEnforce(scheme in schemeFetchers,
121                                        format!"Scheme `%s` is not supported."(scheme));
122         return fetcher(uri);
123     }
124 private:
125     this()
126     {
127         schemeFetchers["file"] = (uri) {
128             import salad.fetcher.exception : fetcherEnforce;
129             import std.file : exists, readText;
130             import std.format : format;
131 
132             auto path = uri.pathWithAuthority;
133             fetcherEnforce(path.exists, format!"File not found: `%s`"(path));
134             return path.readText;
135         };
136         // schemeFetchers["http"] = schemeFetchers["https"] = (uri) {
137         //     return "";
138         // };
139     }
140     TextFetcher[string] schemeFetchers;
141 }