1 /**
2  * Parsing mime/globs and mime/globs2 files.
3  * Authors:
4  *  $(LINK2 https://github.com/FreeSlave, Roman Chistokhodov)
5  * License:
6  *  $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
7  * Copyright:
8  *  Roman Chistokhodov, 2015-2016
9  */
10 
11 module mime.files.globs;
12 
13 public import mime.files.common;
14 
15 private {
16     import mime.common : defaultGlobWeight, isNoGlobs;
17 
18     import std.algorithm;
19     import std.conv : parse;
20     import std.exception;
21     import std.range;
22     import std.traits;
23     import std.typecons;
24 }
25 
26 ///Represents one line in globs or globs2 file.
27 alias Tuple!(uint, "weight", string, "mimeType", string, "pattern", bool, "caseSensitive") GlobLine;
28 
29 /**
30  * Parse mime/globs file by line ignoring empty lines and comments.
31  * Returns:
32  *  Range of $(D GlobLine) tuples.
33  * Throws:
34  *  $(D mime.files.common.MimeFileException) on parsing error.
35  */
36 auto globsFileReader(Range)(Range byLine) if(isInputRange!Range && is(ElementType!Range : string))
37 {
38     return byLine.filter!(lineFilter).map!(function(string line) {
39         auto splitted = line.splitter(':');
40         string mimeType, pattern;
41 
42         if (!splitted.empty) {
43             mimeType = splitted.front;
44             splitted.popFront();
45             if (!splitted.empty) {
46                 pattern = splitted.front;
47                 splitted.popFront();
48             } else {
49                 throw new MimeFileException("Malformed globs file: mime type and pattern must be presented", line);
50             }
51         }
52 
53         if (!mimeType.length || !pattern.length) {
54             throw new MimeFileException("Malformed globs file: the line has wrong format", line);
55         }
56 
57         return GlobLine(isNoGlobs(pattern) ? 0 : defaultGlobWeight, mimeType, pattern, false);
58     });
59 }
60 
61 ///
62 unittest
63 {
64     string[] lines = ["#comment", "text/x-c++src:*.cpp", "text/x-csrc:*.c"];
65     auto expected = [GlobLine(defaultGlobWeight, "text/x-c++src", "*.cpp", false), GlobLine(defaultGlobWeight, "text/x-csrc", "*.c", false)];
66     assert(equal(globsFileReader(lines), expected));
67     assert(equal(globsFileReader(["text/plain:__NOGLOBS__"]), [GlobLine(0, "text/plain", "__NOGLOBS__", false)]));
68 
69     assertThrown!MimeFileException(globsFileReader(["#comment", "text/plain:*.txt", "nocolon"]).array, "must throw");
70 }
71 
72 /**
73  * Parse mime/globs2 file by line ignoring empty lines and comments.
74  * Returns:
75  *  Range of $(D GlobLine) tuples.
76  * Throws:
77  *  $(D mime.files.common.MimeFileException) on parsing error.
78  */
79 auto globs2FileReader(Range)(Range byLine) if(isInputRange!Range && is(ElementType!Range : string))
80 {
81     return byLine.filter!(lineFilter).map!(function(string line) {
82         auto splitted = line.splitter(':');
83         string weightStr, mimeType, pattern, optionsStr;
84 
85         if (!splitted.empty) {
86             weightStr = splitted.front;
87             splitted.popFront();
88             if (!splitted.empty) {
89                 mimeType = splitted.front;
90                 splitted.popFront();
91                 if (!splitted.empty) {
92                     pattern = splitted.front;
93                     splitted.popFront();
94                     if (!splitted.empty) {
95                         optionsStr = splitted.front;
96                         splitted.popFront();
97                     }
98                 }
99             }
100         }
101 
102         if (!weightStr.length || !mimeType.length || !pattern.length) {
103             throw new MimeFileException("Malformed globs2 file: the line has wrong format", line);
104         }
105 
106         uint weight;
107         try {
108             weight = parse!uint(weightStr);
109         } catch(Exception e) {
110             throw new MimeFileException(e.msg, line, e.file, e.line, e.next);
111         }
112 
113         auto flags = optionsStr.splitter(','); //The fourth field contains a list of comma-separated flags
114         bool cs = !flags.empty && flags.front == "cs";
115         return GlobLine(weight, mimeType, pattern, cs);
116     });
117 }
118 
119 ///
120 unittest
121 {
122     string[] lines = [
123         "#comment",
124         "50:text/x-c++src:*.cpp",
125         "60:text/x-c++src:*.C:cs",
126         "50:text/x-csrc:*.c:cs"
127     ];
128 
129     auto expected = [GlobLine(50, "text/x-c++src", "*.cpp", false), GlobLine(60, "text/x-c++src", "*.C", true), GlobLine(50, "text/x-csrc", "*.c", true)];
130     assert(equal(globs2FileReader(lines), expected));
131 
132     assertThrown!MimeFileException(globs2FileReader(["notanumber:text/plain:*.txt"]).array, "must throw");
133 
134     MimeFileException mfe;
135     try {
136         globs2FileReader(["notanumber:text/nopattern"]).array;
137     } catch(MimeFileException e) {
138         mfe = e;
139         assert(mfe.lineString == "notanumber:text/nopattern");
140     }
141     assert(mfe, "must throw");
142 }