1 module pbcompiler;
2 // compiler for .proto protocol buffer definition files that generates D code
3 //
4 import dprotobuf.pbroot;
5 import dprotobuf.pbextension;
6 import dprotobuf.pbmessage;
7 import dprotobuf.pbgeneral;
8 import dprotobuf.generator.d1lang;
9 import dprotobuf.generator.dlang;
10 
11 import std.conv;
12 import std.file;
13 import std.path;
14 import std.stdio;
15 import std.string;
16 
17 version(D_Version2) {
18 	import std.getopt;
19 	import std.algorithm;
20 	import std.range;
21 } else
22 	import dprotobuf.d1support;
23 
24 // our tree of roots to play with, so that we can apply multiple extensions to a given document
25 PBRoot[string] docroots;
26 
27 enum Language {
28 	D1,
29 	D2,
30 }
31 
32 int main(string[] args) {
33 	version(D_Version2) {
34 		Language lang = Language.D2;
35 		getopt(args, config.passThrough,
36 		       "lang", &lang
37 		      );
38 	} else
39 		Language lang = Language.D1;
40 
41 	// rip off the first arg, because that's the name of the program
42 	args = args[1..$];
43 
44 	if (!args.length) throw new Exception("No proto files supplied on the command line!");
45 
46 	foreach (arg;args) {
47 		readRoot(arg);
48 	}
49 	applyExtensions();
50 	writeRoots(lang);
51 	return 0;
52 }
53 
54 // returns package name
55 string readRoot(string filename) {
56 	string contents = cast(string)read(filename);
57 	auto root = PBRoot(contents);
58 	string fname = root.Package;
59 	if (!fname.length) {
60 		if (filename.length>6 && filename[$-6..$].icmp(".proto") == 0) {
61 			fname = filename[0..$-6];
62 		} else {
63 			fname = filename;
64 		}
65 	}
66 	root.Package = fname;
67 	foreach(ref imp;root.imports) {
68 		imp = readRoot(imp);
69 	}
70 	// store this for later use under its package name
71 	docroots[root.Package] = root;
72 	return root.Package;
73 }
74 
75 // we run through the whole list looking for extensions and applying them
76 void applyExtensions() {
77 	foreach(root;docroots) {
78 		// make sure something can only extend what it has access to (including where it was defined)
79 		writefln("Probing root %s for extensions",root.Package);
80 		PBExtension[]extlist = getExtensions(root);
81 		foreach(ext;extlist) {
82 			// check the current node first (just in case)
83 			if (root.Package.applyExtension(ext)) continue;
84 			foreach(imp;root.imports) {
85 				// break out of the import loop to jump to the next extension
86 				if (imp.applyExtension(ext)) break;
87 			}
88 		}
89 	}
90 }
91 
92 // attempt to apply an individual extension to a node identifier
93 // returns 1 if applied
94 int applyExtension(string imp,PBExtension ext) {
95 	writefln("Attempting to apply extension %s",ext.name);
96 	string tmp = ext.name;
97 	bool impflag = false;
98 	tmp.skipOver(imp);
99 	// now look for a message that matches the section within the current import
100 	PBMessage*dst = imp.findMessage(tmp);
101 	if (dst is null) {
102 		writefln("Found no destination to apply extension to %s in import \"%s\"",ext.name,imp);
103 		if (impflag) throw new Exception("Found an import path match \""~imp~"\", but unable to apply extension \""~ext.name~"\'");
104 		return 0;
105 	}
106 	// we have something we might want to apply it to! this is exciting!
107 	// make sure it's within the allowed extensions
108 	*dst = insertExtension(*dst, ext);
109 
110 	// it looks like we have a match!
111 	writefln("Applying extensions to %s",dst.name);
112 	return 1;
113 }
114 
115 // this function digs through a given root to see if it has the message described by the dotstring
116 PBMessage*findMessage(string impstr,string message) {
117 	PBRoot root = docroots[impstr];
118 	return searchMessages(root, ParserData(message));
119 }
120 
121 // this is where all files are written, no real processing is done here
122 void writeRoots(Language lang) {
123 	foreach(root;docroots) {
124 		string tmp;
125 		tmp ~= addComments(root.comments).finalize();
126 		tmp ~= "module "~root.Package~";\n";
127 		// write out imports
128 		foreach(imp;root.imports) {
129 			tmp ~= "import "~imp~";\n";
130 		}
131 		switch(lang) {
132 			case Language.D1:
133 				tmp ~= langD1(root);
134 				break;
135 			case Language.D2:
136 				tmp ~= langD(root);
137 				break;
138 			default:
139 				assert(false);
140 		}
141 		string fname = root.Package.tr(".","/")~".d";
142 		version(D_Version2) string dname = fname.dirName();
143 		else string dname = fname.getDirName();
144 		// check to see if we need to create the directory
145 		if (dname.length && !dname.exists()) {
146 			dname.mkdirRecurse();
147 		}
148 		std.file.write(fname,tmp);
149 	}
150 }
151 
152 void mkdirRecurse(in string  pathname)
153 {
154 	version(D_Version2) string left = dirName(pathname);
155 	else string left = getDirName(pathname);
156 	exists(left) || mkdirRecurse(left);
157 	mkdir(pathname);
158 }