1 /*
2 	 -------------------------------------------------------------------
3 
4 	 Copyright (C) 2014, Edwin van Leeuwen
5 
6 	 This file is part of todod todo list manager.
7 
8 	 Todod is free software; you can redistribute it and/or modify
9 	 it under the terms of the GNU General Public License as published by
10 	 the Free Software Foundation; either version 3 of the License, or
11 	 (at your option) any later version.
12 
13 	 Todod is distributed in the hope that it will be useful,
14 	 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 	 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 	 GNU General Public License for more details.
17 
18 	 You should have received a copy of the GNU General Public License
19 	 along with Todod. If not, see <http://www.gnu.org/licenses/>.
20 
21 	 -------------------------------------------------------------------
22 	 */
23 
24 /// Manage dependencies between Todos (identified by their uuid)
25 module todod.dependency;
26 
27 import std.algorithm;
28 import std.json;
29 import std.uuid;
30 
31 import todod.storage;
32 
33 version( unittest ) {
34 	import std.stdio;
35 }
36 
37 /// Contains one link between child which depends on parent
38 struct Link {
39 	@disable this();
40 
41 	UUID _parent; /// uuid of the parent
42 	UUID _child; /// uuid of the child
43 
44 	/**
45 		Set the link with @child depending on @parent
46 		*/
47 	this( UUID parent, UUID child ) {
48 		_parent = parent;
49 		_child = child;
50 	}
51 
52 	unittest {
53 		auto prnt = randomUUID;
54 		auto chld = randomUUID;
55 		auto lnk = Link( prnt, chld );
56 		assert( lnk._parent == prnt );
57 		assert( lnk._child == chld );
58 	}
59 }
60 
61 JSONValue toJSON( Link link ) {
62 	JSONValue[string] jsonLink;
63 	jsonLink["parent"] = link._parent.toString;
64 	jsonLink["child"] = link._child.toString;
65 	return JSONValue( jsonLink );	
66 }
67 
68 Link toLink( const JSONValue json ) {
69 	Link link = Link( UUID(), UUID() );
70 	const JSONValue[string] jsonAA = json.object;
71 	if ("parent" in jsonAA)
72 		link._parent = UUID( jsonAA["parent"].str );
73 	if ("child" in jsonAA)
74 		link._child = UUID( jsonAA["child"].str );
75 	return link;
76 }
77 
78 unittest {
79 	Link orig = Link( randomUUID, randomUUID );
80 	auto json = toJSON( orig );
81 	Link link = toLink( json );
82 	assert( link._parent == orig._parent );
83 	assert( link._child == orig._child );
84 }
85 
86 alias Dependencies = Link[];
87 
88 /// Is given uuid a child of anyone
89 bool isAChild( in Dependencies deps, in UUID child ) {
90 	return canFind!( (a,b) => a._child == b )( deps, child );
91 }
92 
93 unittest {
94 	Dependencies deps;
95 	auto child = randomUUID;
96 	deps ~= Link( randomUUID, child );
97 	deps ~= Link( randomUUID, randomUUID );
98 	deps ~= Link( randomUUID, randomUUID );
99 	assert( deps.isAChild( child ) );
100 	assert( !deps.isAChild( randomUUID ) );
101 	auto child2 = randomUUID;
102 	deps ~= Link( randomUUID, child2 );
103 	deps ~= Link( randomUUID, randomUUID );
104 	assert( deps.isAChild( child ) );
105 	assert( deps.isAChild( child2 ) );
106 	assert( !deps.isAChild( randomUUID ) );
107 }
108 
109 /// Remove given uuid completely from the dependencies
110 Dependencies removeUUID( ref Dependencies deps, UUID theUUID ) {
111 	Dependencies result;
112 	foreach( lnk; deps ) // Ideally use remove!pred, but for some reason that did
113 											 // not work properly
114 		if ( lnk._child != theUUID && lnk._parent != theUUID )
115 			result ~= lnk;
116 	return result; 
117 }
118 
119 unittest {
120 	Dependencies deps;
121 	auto child = randomUUID;
122 	auto parent = randomUUID;
123 	deps ~= Link( randomUUID, child );
124 	deps ~= Link( parent, randomUUID );
125 	deps ~= Link( randomUUID, randomUUID );
126 	assert( deps.length == 3 );
127 	deps = removeUUID( deps, child );
128 	assert( deps.length == 2 );
129 	assert( removeUUID( deps, parent ).length == 1 );
130 }
131 
132 /// Group the parents in the dependencies by child.
133 UUID[][UUID] groupByChild( in Dependencies deps ) pure nothrow {
134 	UUID[][UUID] groups;
135 	foreach( link; deps )
136 		groups[link._child] ~= link._parent;
137 	return groups;
138 }
139 
140 unittest {
141 	Dependencies deps;
142 	auto child = randomUUID;
143 	deps ~= Link( randomUUID, child );
144 	deps ~= Link( randomUUID, child );
145 	deps ~= Link( randomUUID, randomUUID );
146 	auto groups = groupByChild( deps );
147 	assert( child in groups );
148 	assert( groups[child].length == 2 );
149 }
150 
151 JSONValue toJSON(T)( in T[] range ) {
152 	JSONValue[] json;
153 	foreach (e; range) 
154 		json ~= toJSON( e );
155 	return JSONValue( json );
156 }
157 
158 Dependencies toDependencies( JSONValue json ) {
159 	Dependencies deps;
160 	foreach ( js; json.array )
161 		deps ~= toLink(js);
162 	return deps;
163 }
164 
165 unittest {
166 	Dependencies deps;
167 	auto child = randomUUID;
168 	auto parent = randomUUID;
169 	deps ~= Link( randomUUID, randomUUID );
170 	deps ~= Link( parent, child );
171 	deps ~= Link( randomUUID, randomUUID );
172 	auto json = toJSON( deps );
173 	auto dps = toDependencies( json );
174 	assert( dps.length == 3 );
175 	assert( dps[1]._parent == parent );
176 	assert( dps[1]._child == child );
177 }
178 
179 Dependencies loadDependencies( GitRepo gr ) {
180 	Dependencies deps;
181 	auto dependenciesFileName = "dependencies.json";
182 	auto content = readFile( gr.workPath, dependenciesFileName );
183 	if (content != "")
184 		deps = toDependencies( parseJSON( content ) );
185 	return deps;
186 }
187 
188 void writeDependencies( Dependencies deps, GitRepo gr ) {
189 	auto dependenciesFileName = "dependencies.json";
190 	writeToFile( gr.workPath, dependenciesFileName, toJSON( deps ).toPrettyString );
191 	commitChanges( gr, dependenciesFileName, "Updating dependencies file" );
192 }