From b09a146aabcbfea4c5f1149763eb188a37eb66e0 Mon Sep 17 00:00:00 2001 From: Basile Burg Date: Fri, 16 Jan 2015 00:08:39 +0100 Subject: [PATCH] added cetodo, a tool which find TODO items in D sources --- cetodo/cetodo.coedit | 41 +++++++++ cetodo/cetodo.d | 192 +++++++++++++++++++++++++++++++++++++++++++ cetodo/readme.md | 7 ++ 3 files changed, 240 insertions(+) create mode 100644 cetodo/cetodo.coedit create mode 100644 cetodo/cetodo.d create mode 100644 cetodo/readme.md diff --git a/cetodo/cetodo.coedit b/cetodo/cetodo.coedit new file mode 100644 index 00000000..44e96e6c --- /dev/null +++ b/cetodo/cetodo.coedit @@ -0,0 +1,41 @@ +object CurrentProject: TCEProject + OptionsCollection = < + item + name = 'testwith_CPFS' + messagesOptions.additionalWarnings = True + outputOptions.boundsCheck = onAlways + outputOptions.unittest = True + pathsOptions.outputFilename = '..\cetodo.exe' + preBuildProcess.options = [] + preBuildProcess.showWindow = swoNone + postBuildProcess.options = [] + postBuildProcess.showWindow = swoNone + runOptions.options = [poUsePipes, poStderrToOutPut] + runOptions.parameters.Strings = ( + '' + '' + ) + runOptions.showWindow = swoHIDE + end + item + name = 'release' + outputOptions.inlining = True + outputOptions.boundsCheck = offAlways + outputOptions.optimizations = True + outputOptions.release = True + pathsOptions.outputFilename = '..\cetodo.exe' + preBuildProcess.options = [] + preBuildProcess.showWindow = swoNone + postBuildProcess.options = [] + postBuildProcess.showWindow = swoNone + runOptions.options = [] + runOptions.showWindow = swoNone + end> + Sources.Strings = ( + 'cetodo.d' + ) + ConfigurationIndex = 0 + LibraryAliases.Strings = ( + 'libdparse' + ) +end diff --git a/cetodo/cetodo.d b/cetodo/cetodo.d new file mode 100644 index 00000000..27758198 --- /dev/null +++ b/cetodo/cetodo.d @@ -0,0 +1,192 @@ +/******************************************************************************* +TODO source code analyzer for Coedit projects/files + +Format: // TODO [fields] : content + +- TODO: used to detect that the comment is a "TODO" comment. The keyword is not +case sensitive. + +- fields: an optional list of property with a format similar to the execution argument +of a program: --. +possible fields include: + - c: TODO category, e.g: -cserialization -cpersistence -cerrorhandling + - a: TODO assignee, e.g: -aMisterFreeze -aMadameMichou -aJhonSmith + - p: TODO priority, eg: -p8 -p0 + - s: TODO status, e.g -sPartiallyFixed, -sDone + +- content: the literal message, e.g: "set this property as const() to set it read only". + +full examples: + +// TODO: set this property as const() to set it read only. +// TODO-cfeature: save this property in the inifile. +// TODO-cannnotations-p8: annotates the member of the module as @safe and if possible nothrow. + +- widget to tool IPC: + +The widget call the tool with a list of file as argument and read the process +output on exit. the widget expects to find some TODO items in LFM format, according +to the classes declarations of TTodoItems (the collection container) and TTodoItem(the collection item) + +********************************************************************************/ +module cetodo; + +// std +import std.stdio, std.getopt, std.string; +import std.array, std.conv, std.traits; +import std.file, std.path, std.range; +// libdparse +import std.d.ast, std.d.lexer, std.d.parser; + + +private struct TodoItem +{ + private enum TodoField {file, line, text, category, assignee, priority, status} + private string[TodoField] fFields; + + /** + * Constructs a TODO item with its fields. + * Params: + * fname = the file where the item is located. mandatory. + * line = the line where the item is located. mandatory. + * text = the TODO content. mandatory. + */ + @safe public this(string fname, string line, string text, string cat = "", string ass = "", string prior = "", string status = "") + { + // line and fname must really be valid + if (!fname.exists) throw new Exception("TodoItem exception, the file name is invalid"); + try auto l = to!long(line); + catch(Exception e) throw new Exception("TodoItem exception, the line number is invalid"); + + // priority must be convertible to int + if (prior.length) try auto i = to!long(prior); + catch(Exception e) prior = ""; + + fFields[TodoField.file] = fname; + fFields[TodoField.line] = line; + fFields[TodoField.text] = text; + fFields[TodoField.category] = cat; + fFields[TodoField.assignee] = ass; + fFields[TodoField.priority] = prior; + fFields[TodoField.status] = status; + } + + /** + * The item writes itself as a TCollectionItem. + * Params: + * LfmString = the string containing the LFM script. + */ + @safe public void serialize(ref string LfmString) + { + LfmString ~= "\t\titem\n"; + foreach(member; EnumMembers!TodoField) + if (fFields[member].length) + LfmString ~= format("\t\t\t%s = '%s'\n", to!string(member), fFields[member]); + LfmString ~= "\t\tend\n"; + } +} + +private alias TodoItems = TodoItem * []; + +/** + * Application main procedure. + * Params: + * args = a list of files to analyze. + * Called each time a document is focused. Args is set using: + * - the symbolic string (current file is not in a project). + * - the symbolic string (current file is in a project). + */ +void main(string[] args) +{ + string[] files = args[1..$]; + string LfmString; + TodoItems todoItems; + + foreach(f; files) + { + if (!f.exists) continue; + + // loadand parse the file + auto src = cast(ubyte[]) read(f, size_t.max); + auto config = LexerConfig(f, StringBehavior.source); + StringCache cache = StringCache(StringCache.defaultBucketCount); + auto lexer = DLexer(src, config, &cache); + // analyze the tokens + foreach(tok; lexer) token2TodoItem(tok, f, todoItems); + } + // serialize the items using the pascal component streaming text format + foreach(todoItem; todoItems) todoItem.serialize(LfmString); + // the widget has the TODOs in the output + if (LfmString.length) writefln("object TTodoItems\n\titems = <\n%s>\nend", LfmString); +} + +/// Try to transforms a Token into a a TODO item +@safe private void token2TodoItem(const(Token) atok, string fname, ref TodoItems todoItems) +{ + if (atok.type != (tok!"comment")) + return; + if (atok.text.length < 3) + return; + if (atok.text[1] == '*' || atok.text[1] == '+' || atok.text[2] == '/') + return; + + auto text = atok.text[2..$]; + string identifier; + bool isTodoComment; + size_t pos; + + // "TODO" + while (true) { + if (pos == text.length) break; + if (identifier.strip.toUpper == "TODO") { + isTodoComment = true; + text = text[pos..$]; + break; + } + identifier ~= text[pos++]; + } + if (!isTodoComment) return; + + //fields : content + identifier = identifier.init; + auto fc = text.split(':'); + if (fc.length < 2) return; + auto raw_fields = fc[0]; + auto raw_content = fc[1..$]; + + // fields + string a,c,p,s; + foreach(field; raw_fields.split('-')) { + if (field.length < 2) continue; + switch (field[0]) { + default: break; + case 'a': case 'A': + a = field[1..$].strip; + break; + case 'c': case 'C': + c = field[1..$].strip; + break; + case 'p': case 'P': + p = field[1..$].strip; + break; + case 's': case 'S': + s = field[1..$].strip; + break; + } + } + + // content, must exists. + string content = raw_content.join.strip; + if (!content.length) return; + + // item + todoItems ~= new TodoItem(fname, to!string(atok.line), content, c, a, p, s); +} + +// samples for testing the program as a runnable module with + +// TODO-cINVALID_because_no_content: +// TODO: set this property as const() to set it read only. +// TODO-cfeature: save this property in the inifile. +// TODO-cannnotations-p8: annotates the member of the module as @safe and if possible nothrow. + diff --git a/cetodo/readme.md b/cetodo/readme.md new file mode 100644 index 00000000..6ef5e3a8 --- /dev/null +++ b/cetodo/readme.md @@ -0,0 +1,7 @@ +ceTodo +====== + +Tool designed to analyze the _TODO comments_ in D source files. +It's written in D using Coedit. To build it, [libdparse](https://github.com/Hackerpilot/libdparse) +must be setup in the [libman](https://github.com/BBasile/Coedit/wiki#library-manager-widget) +as described in this [tutorial(https://github.com/BBasile/Coedit/wiki#lets-build-a-static-library)]. \ No newline at end of file