1 /**
2  * Copyright: Copyright Jason White, 2016
3  * License:   MIT
4  * Authors:   Jason White
5  *
6  * Description:
7  * Parses rules.
8  */
9 module button.rule;
10 
11 import std.range.primitives : isInputRange, ElementType;
12 
13 import button.command;
14 import button.resource;
15 import button.task;
16 import button.edge;
17 
18 struct Rule
19 {
20     /**
21      * The sets of inputs and outputs that this task is dependent on.
22      */
23     Resource[] inputs, outputs;
24 
25     /**
26      * The command to execute.
27      */
28     Task task;
29 }
30 
31 struct Rules
32 {
33     import std.json : JSONValue;
34 
35     private
36     {
37         JSONValue[] rules;
38 
39         // Current rule taken from the stream.
40         Rule rule;
41 
42         bool _empty;
43     }
44 
45     this(JSONValue rules)
46     {
47         this.rules = rules.array();
48 
49         // Prime the cannon
50         popFront();
51     }
52 
53     void popFront()
54     {
55         import std.range : empty, popFront, front;
56         import std.algorithm : map;
57         import std.array : array;
58         import std.json : JSONException;
59         import std.path : buildNormalizedPath;
60         import std.exception : assumeUnique;
61 
62         if (rules.empty)
63         {
64             _empty = true;
65             return;
66         }
67 
68         auto jsonRule = rules.front;
69 
70         auto inputs = jsonRule["inputs"].array()
71             .map!(x => Resource(buildNormalizedPath(x.str())))
72             .array();
73 
74         auto outputs = jsonRule["outputs"].array()
75             .map!(x => Resource(buildNormalizedPath(x.str())))
76             .array();
77 
78         auto commands = jsonRule["task"].array()
79             .map!(x => Command(x.array().map!(y => y.str()).array().idup))
80             .array
81             .assumeUnique;
82 
83         string cwd = "";
84 
85         // Optional
86         try
87             cwd = jsonRule["cwd"].str();
88         catch(JSONException e) {}
89 
90         string display;
91         try
92             display = jsonRule["display"].str();
93         catch(JSONException e) {}
94 
95         rule = Rule(inputs, outputs, Task(commands, cwd, display));
96 
97         rules.popFront();
98     }
99 
100     inout(Rule) front() inout
101     {
102         return rule;
103     }
104 
105     bool empty() const pure nothrow
106     {
107         return _empty;
108     }
109 }
110 
111 /**
112  * Convenience function for constructing a Rules range.
113  */
114 @property Rules parseRules(R)(R json)
115     if (isInputRange!R)
116 {
117     import std.json : parseJSON;
118     return Rules(parseJSON(json));
119 }
120 
121 unittest
122 {
123     import std.algorithm : equal;
124 
125     immutable json = q{
126         [
127             {
128                 "inputs": ["foo.c", "baz.h"],
129                 "task": [["gcc", "-c", "foo.c", "-o", "foo.o"]],
130                 "display": "cc foo.c",
131                 "outputs": ["foo.o"]
132             },
133             {
134                 "inputs": ["bar.c", "baz.h"],
135                 "task": [["gcc", "-c", "bar.c", "-o", "bar.o"]],
136                 "outputs": ["bar.o"]
137             },
138             {
139                 "inputs": ["foo.o", "bar.o"],
140                 "task": [["gcc", "foo.o", "bar.o", "-o", "foobar"]],
141                 "outputs": ["foobar"]
142             }
143         ]
144     };
145 
146     immutable Rule[] rules = [
147         {
148             inputs: [Resource("foo.c"), Resource("baz.h")],
149             task: Task([Command(["gcc", "-c", "foo.c", "-o", "foo.o"])]),
150             outputs: [Resource("foo.o")]
151         },
152         {
153             inputs: [Resource("bar.c"), Resource("baz.h")],
154             task: Task([Command(["gcc", "-c", "bar.c", "-o", "bar.o"])]),
155             outputs: [Resource("bar.o")]
156         },
157         {
158             inputs: [Resource("foo.o"), Resource("bar.o")],
159             task: Task([Command(["gcc", "foo.o", "bar.o", "-o", "foobar"])]),
160             outputs: [Resource("foobar")]
161         }
162     ];
163 
164     assert(parseRules(json).equal(rules));
165 }
166 
167 unittest
168 {
169     import std.algorithm : equal;
170 
171     immutable json = q{
172         [
173             {
174                 "inputs": ["./test/../foo.c", "./baz.h"],
175                 "task": [["ls", "foo.c", "baz.h"]],
176                 "outputs": ["this/../path/../is/../normalized"]
177             }
178         ]
179     };
180 
181     immutable Rule[] rules = [
182         {
183             inputs: [Resource("foo.c"), Resource("baz.h")],
184             task: Task([Command(["ls", "foo.c", "baz.h"])]),
185             outputs: [Resource("normalized")]
186         },
187     ];
188 
189     assert(parseRules(json).equal(rules));
190 }