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 }