diff --git a/wpiformat/test/test_commentformat.py b/wpiformat/test/test_commentformat.py new file mode 100644 index 00000000..26c18bc7 --- /dev/null +++ b/wpiformat/test/test_commentformat.py @@ -0,0 +1,296 @@ +import os + +from test.tasktest import * +from wpiformat.commentformat import CommentFormat + + +def test_commentformat(): + test = TaskTest(CommentFormat()) + + # Empty comment + test.add_input("./Test.h", + "/**" + os.linesep + \ + " */" + os.linesep) + test.add_latest_input_as_output(True) + + # Adds space before asterisks + test.add_input("./Test.h", + "/**" + os.linesep + \ + "*" + os.linesep + \ + "*/" + os.linesep) + test.add_output( + "/**" + os.linesep + \ + " */" + os.linesep, True, True) + + # Put /** on separate line + test.add_input("./Test.h", "/** asdf */" + os.linesep) + test.add_output( + "/**" + os.linesep + \ + " * Asdf." + os.linesep + \ + " */" + os.linesep, True, True) + + # Paragraphs but no tags + test.add_input("./Accelerometer.cpp", + "/**" + os.linesep + \ + " * Get the x-axis acceleration" + os.linesep + \ + " *" + os.linesep + \ + " * This is a floating point value in units of 1 g-force" + os.linesep + \ + " */" + os.linesep) + test.add_output( + "/**" + os.linesep + \ + " * Get the x-axis acceleration." + os.linesep + \ + " *" + os.linesep + \ + " * This is a floating point value in units of 1 g-force." + os.linesep + \ + " */" + os.linesep, True, True) + + # Paragraphs but no tags + test.add_input("./Accelerometer.cpp", + "/**" + os.linesep + \ + " * Convert a 12-bit raw acceleration value into a scaled double in units of" + os.linesep + \ + " * 1 g-force, taking into account the accelerometer range." + os.linesep + \ + " */" + os.linesep) + test.add_output( + "/**" + os.linesep + \ + " * Convert a 12-bit raw acceleration value into a scaled double in units of 1" + os.linesep + \ + " * g-force, taking into account the accelerometer range." + os.linesep + \ + " */" + os.linesep, True, True) + + # @param tag but with blank line before it and no description + test.add_input("./AnalogInput.cpp", + "/**" + os.linesep + \ + " *" + os.linesep + \ + " * @param analogPortHandle Handle to the analog port." + os.linesep + \ + " */" + os.linesep) + test.add_output( + "/**" + os.linesep + \ + " * @param analogPortHandle Handle to the analog port." + os.linesep + \ + " */" + os.linesep, True, True) + + # Paragraph with @param and @return tags + test.add_input("./AnalogAccumulator.cpp", + "/**" + os.linesep + \ + " * Is the channel attached to an accumulator." + os.linesep + \ + " *" + os.linesep + \ + " * @param analogPortHandle Handle to the analog port." + os.linesep + \ + " * @return The analog channel is attached to an accumulator." + os.linesep + \ + " */" + os.linesep) + test.add_latest_input_as_output(True) + + # Paragraph and @return with no empty line between them + test.add_input("./AnalogTrigger.cpp", + "/**" + os.linesep + \ + " * Return the InWindow output of the analog trigger." + os.linesep + \ + " *" + os.linesep + \ + " * True if the analog input is between the upper and lower limits." + os.linesep + \ + " * @return The InWindow output of the analog trigger." + os.linesep + \ + " */" + os.linesep) + test.add_output( + "/**" + os.linesep + \ + " * Return the InWindow output of the analog trigger." + os.linesep + \ + " *" + os.linesep + \ + " * True if the analog input is between the upper and lower limits." + os.linesep + \ + " *" + os.linesep + \ + " * @return The InWindow output of the analog trigger." + os.linesep + \ + " */" + os.linesep, True, True) + + # List ("-" bullets) + test.add_input("./DigitalInternal.h", + "/**" + os.linesep + \ + " * The default PWM period is in ms." + os.linesep + \ + " *" + os.linesep + \ + " * - 20ms periods (50 Hz) are the \"safest\" setting in that this works for all" + os.linesep + \ + " * devices" + os.linesep + \ + " * - 20ms periods seem to be desirable for Vex Motors" + os.linesep + \ + " * - 20ms periods are the specified period for HS-322HD servos, but work" + os.linesep + \ + " * reliably down to 10.0 ms; starting at about 8.5ms, the servo sometimes hums" + os.linesep + \ + " * and get hot; by 5.0ms the hum is nearly continuous" + os.linesep + \ + " * - 10ms periods work well for Victor 884" + os.linesep + \ + " * - 5ms periods allows higher update rates for Luminary Micro Jaguar speed" + os.linesep + \ + " * controllers. Due to the shipping firmware on the Jaguar, we can't run the" + os.linesep + \ + " * update period less than 5.05 ms." + os.linesep + \ + " *" + os.linesep + \ + " * kDefaultPwmPeriod is the 1x period (5.05 ms). In hardware, the period" + os.linesep + \ + " * scaling is implemented as an output squelch to get longer periods for old" + os.linesep + \ + " * devices." + os.linesep + \ + " */" + os.linesep) + test.add_latest_input_as_output(True) + + # List ("+" bullets) + test.add_input("./DigitalInternal.h", + "/**" + os.linesep + \ + " * The default PWM period is in ms." + os.linesep + \ + " *" + os.linesep + \ + " * + 20ms periods (50 Hz) are the \"safest\" setting in that this works for all" + os.linesep + \ + " * devices" + os.linesep + \ + " * + 20ms periods seem to be desirable for Vex Motors" + os.linesep + \ + " * + 20ms periods are the specified period for HS-322HD servos, but work" + os.linesep + \ + " * reliably down to 10.0 ms; starting at about 8.5ms, the servo sometimes hums" + os.linesep + \ + " * and get hot; by 5.0ms the hum is nearly continuous" + os.linesep + \ + " * + 10ms periods work well for Victor 884" + os.linesep + \ + " * + 5ms periods allows higher update rates for Luminary Micro Jaguar speed" + os.linesep + \ + " * controllers. Due to the shipping firmware on the Jaguar, we can't run the" + os.linesep + \ + " * update period less than 5.05 ms." + os.linesep + \ + " *" + os.linesep + \ + " * kDefaultPwmPeriod is the 1x period (5.05 ms). In hardware, the period" + os.linesep + \ + " * scaling is implemented as an output squelch to get longer periods for old" + os.linesep + \ + " * devices." + os.linesep + \ + " */" + os.linesep) + test.add_latest_input_as_output(True) + + # List ("*" bullets) + test.add_input("./DigitalInternal.h", + "/**" + os.linesep + \ + " * The default PWM period is in ms." + os.linesep + \ + " *" + os.linesep + \ + " * * 20ms periods (50 Hz) are the \"safest\" setting in that this works for all" + os.linesep + \ + " * devices" + os.linesep + \ + " * * 20ms periods seem to be desirable for Vex Motors" + os.linesep + \ + " * * 20ms periods are the specified period for HS-322HD servos, but work" + os.linesep + \ + " * reliably down to 10.0 ms; starting at about 8.5ms, the servo sometimes hums" + os.linesep + \ + " * and get hot; by 5.0ms the hum is nearly continuous" + os.linesep + \ + " * * 10ms periods work well for Victor 884" + os.linesep + \ + " * * 5ms periods allows higher update rates for Luminary Micro Jaguar speed" + os.linesep + \ + " * controllers. Due to the shipping firmware on the Jaguar, we can't run the" + os.linesep + \ + " * update period less than 5.05 ms." + os.linesep + \ + " *" + os.linesep + \ + " * kDefaultPwmPeriod is the 1x period (5.05 ms). In hardware, the period" + os.linesep + \ + " * scaling is implemented as an output squelch to get longer periods for old" + os.linesep + \ + " * devices." + os.linesep + \ + " */" + os.linesep) + test.add_latest_input_as_output(True) + + # List (numbered items) + test.add_input("./DigitalInternal.h", + "/**" + os.linesep + \ + " * The default PWM period is in ms." + os.linesep + \ + " *" + os.linesep + \ + " * 1. 20ms periods (50 Hz) are the \"safest\" setting in that this works for all" + os.linesep + \ + " * devices" + os.linesep + \ + " * 2. 20ms periods seem to be desirable for Vex Motors" + os.linesep + \ + " * 3. 20ms periods are the specified period for HS-322HD servos, but work" + os.linesep + \ + " * reliably down to 10.0 ms; starting at about 8.5ms, the servo sometimes hums" + os.linesep + \ + " * and get hot; by 5.0ms the hum is nearly continuous" + os.linesep + \ + " * 4. 10ms periods work well for Victor 884" + os.linesep + \ + " * 5. 5ms periods allows higher update rates for Luminary Micro Jaguar speed" + os.linesep + \ + " * controllers. Due to the shipping firmware on the Jaguar, we can't run the" + os.linesep + \ + " * update period less than 5.05 ms." + os.linesep + \ + " *" + os.linesep + \ + " * kDefaultPwmPeriod is the 1x period (5.05 ms). In hardware, the period" + os.linesep + \ + " * scaling is implemented as an output squelch to get longer periods for old" + os.linesep + \ + " * devices." + os.linesep + \ + " */" + os.linesep) + test.add_latest_input_as_output(True) + + # C++: paragraphs with @param tags + test.add_input("./PIDController.cpp", + " /**" + os.linesep + \ + " * Allocate a PID object with the given constants for P, I, D." + os.linesep + \ + " *" + os.linesep + \ + " * More summary." + os.linesep + \ + " * Even more summary." + os.linesep + \ + " *" + os.linesep + \ + " * @param Kp the proportional coefficient" + os.linesep + \ + " * @param Ki the integral coefficient" + os.linesep + \ + " * @param Kd the derivative coefficient" + os.linesep + \ + " * @param source The PIDSource object that is used to get values" + os.linesep + \ + " * @param output The PIDOutput object that is set to the output value" + os.linesep + \ + " * @param period the loop time for doing calculations. This particularly" + os.linesep + \ + " * effects calculations of the integral and differental terms." + os.linesep + \ + " * The default is 50ms." + os.linesep + \ + " */" + os.linesep + \ + " PIDController::PIDController(double Kp, double Ki, double Kd, PIDSource* source," + os.linesep + \ + " PIDOutput* output, double period)" + os.linesep) + test.add_output( + " /**" + os.linesep + \ + " * Allocate a PID object with the given constants for P, I, D." + os.linesep + \ + " *" + os.linesep + \ + " * More summary. Even more summary." + os.linesep + \ + " *" + os.linesep + \ + " * @param Kp The proportional coefficient." + os.linesep + \ + " * @param Ki The integral coefficient." + os.linesep + \ + " * @param Kd The derivative coefficient." + os.linesep + \ + " * @param source The PIDSource object that is used to get values." + os.linesep + \ + " * @param output The PIDOutput object that is set to the output value." + os.linesep + \ + " * @param period The loop time for doing calculations. This particularly" + os.linesep + \ + " * effects calculations of the integral and differental terms." + os.linesep + \ + " * The default is 50ms." + os.linesep + \ + " */" + os.linesep + \ + " PIDController::PIDController(double Kp, double Ki, double Kd, PIDSource* source," + os.linesep + \ + " PIDOutput* output, double period)" + os.linesep, True, True) + + # Java: paragraphs with @param tags idempotence + test.add_input("./PIDController.java", + " /**" + os.linesep + \ + " * Allocate a PID object with the given constants for P, I, D." + os.linesep + \ + " *" + os.linesep + \ + " * More summary." + os.linesep + \ + " * Even more summary." + os.linesep + \ + " *" + os.linesep + \ + " * @param Kp the proportional coefficient" + os.linesep + \ + " * @param Ki the integral coefficient" + os.linesep + \ + " * @param Kd the derivative coefficient" + os.linesep + \ + " * @param source The PIDSource object that is used to get values" + os.linesep + \ + " * @param output The PIDOutput object that is set to the output value" + os.linesep + \ + " * @param period the loop time for doing calculations. This particularly" + os.linesep + \ + " * effects calculations of the integral and differental terms." + os.linesep + \ + " * The default is 50ms." + os.linesep + \ + " */" + os.linesep + \ + " PIDController(double Kp, double Ki, double Kd, PIDSource source, PIDOutput output, double period)" + os.linesep) + test.add_output( + " /**" + os.linesep + \ + " * Allocate a PID object with the given constants for P, I, D." + os.linesep + \ + " *" + os.linesep + \ + " *
More summary. Even more summary." + os.linesep + \ + " *" + os.linesep + \ + " * @param Kp The proportional coefficient." + os.linesep + \ + " * @param Ki The integral coefficient." + os.linesep + \ + " * @param Kd The derivative coefficient." + os.linesep + \ + " * @param source The PIDSource object that is used to get values." + os.linesep + \ + " * @param output The PIDOutput object that is set to the output value." + os.linesep + \ + " * @param period The loop time for doing calculations. This particularly effects calculations of" + os.linesep + \ + " * the integral and differental terms. The default is 50ms." + os.linesep + \ + " */" + os.linesep + \ + " PIDController(double Kp, double Ki, double Kd, PIDSource source, PIDOutput output, double period)" + os.linesep, True, True) + + test.add_input("./PIDController.java", + " /**" + os.linesep + \ + " * Allocate a PID object with the given constants for P, I, D." + os.linesep + \ + " *" + os.linesep + \ + " *
More summary. Even more summary." + os.linesep + \
+ " *" + os.linesep + \
+ " * @param Kp The proportional coefficient." + os.linesep + \
+ " * @param Ki The integral coefficient." + os.linesep + \
+ " * @param Kd The derivative coefficient." + os.linesep + \
+ " * @param source The PIDSource object that is used to get values." + os.linesep + \
+ " * @param output The PIDOutput object that is set to the output value." + os.linesep + \
+ " * @param period The loop time for doing calculations. This particularly effects calculations of" + os.linesep + \
+ " * the integral and differental terms. The default is 50ms." + os.linesep + \
+ " */" + os.linesep + \
+ " PIDController(double Kp, double Ki, double Kd, PIDSource source, PIDOutput output, double period)" + os.linesep)
+ test.add_latest_input_as_output(True)
+
+ # Java: Don't count "{@" as tag (only "@" at beginning of line)
+ test.add_input("./Test.java",
+ "/**" + os.linesep + \
+ " * This is a {@link test} description." + os.linesep + \
+ " *" + os.linesep + \
+ " * @param test Test parameter." + os.linesep + \
+ " */" + os.linesep)
+ test.add_latest_input_as_output(True)
+
+ # Java: Make sure {@link ...} is wrapped atomically
+ test.add_input("./Test.java",
+ "/**" + os.linesep + \
+ " * This is a sentence. This is a really, really, really, really long sentence with a Javadoc {@link test} in it." + os.linesep + \
+ " *" + os.linesep + \
+ " * @param test Test parameter." + os.linesep + \
+ " */" + os.linesep)
+ test.add_output(
+ "/**" + os.linesep + \
+ " * This is a sentence. This is a really, really, really, really long sentence with a Javadoc" + os.linesep + \
+ " * {@link test} in it." + os.linesep + \
+ " *" + os.linesep + \
+ " * @param test Test parameter." + os.linesep + \
+ " */" + os.linesep, True, True)
+
+ test.run(OutputType.FILE)
diff --git a/wpiformat/wpiformat/__init__.py b/wpiformat/wpiformat/__init__.py
index 74811a8f..0feda2dc 100644
--- a/wpiformat/wpiformat/__init__.py
+++ b/wpiformat/wpiformat/__init__.py
@@ -10,6 +10,7 @@
from wpiformat.bracecomment import BraceComment
from wpiformat.cidentlist import CIdentList
from wpiformat.clangformat import ClangFormat
+from wpiformat.commentformat import CommentFormat
from wpiformat.config import Config
from wpiformat.includeguard import IncludeGuard
from wpiformat.includeorder import IncludeOrder
@@ -343,6 +344,7 @@ def main():
task_pipeline = [
BraceComment(),
CIdentList(),
+ CommentFormat(),
IncludeGuard(),
LicenseUpdate(),
JavaClass(),
diff --git a/wpiformat/wpiformat/commentformat.py b/wpiformat/wpiformat/commentformat.py
new file mode 100644
index 00000000..7dab4373
--- /dev/null
+++ b/wpiformat/wpiformat/commentformat.py
@@ -0,0 +1,200 @@
+"""This task formats Doxygen and Javadoc comments.
+
+Comments are rewrapped to 80 characters for C++ and 100 for Java. The @param
+tag has one space followed by the parameter name, at least one one space, then
+the description. All @param descriptions start on the same column.
+
+The first letter of paragraphs and tag descriptions is capitalized and a "." is
+appended if one is not already. Descriptions past 80 (or 100) characters are
+wrapped to the next line at the same starting column.
+
+The indentation of lists is left alone. Bulleted lists can use "-", "+", or "*"
+while numbered lists use numbers followed by ".".
+"""
+
+import regex
+import sys
+
+from wpiformat.task import Task
+
+
+class CommentFormat(Task):
+
+ def should_process_file(self, config_file, name):
+ return config_file.is_c_file(name) or config_file.is_cpp_file(name) or \
+ name.endswith(".java")
+
+ def textwrap(self, lines, column_limit):
+ """Wraps lines to the provided column limit and returns a list of lines.
+
+ Keyword Arguments:
+ lines -- string to wrap
+ column_limit -- maximum number of characters per line
+ """
+ output = []
+ output_str = ""
+ pos = 0
+ rgx = regex.compile(r"{@link(?>.*?})|\S+")
+ for match in rgx.finditer(lines):
+ if len(output_str) + len(" ") + len(match.group()) > column_limit:
+ output.append(output_str)
+ output_str = match.group()
+ else:
+ if output_str:
+ output_str += " "
+ output_str += match.group()
+ pos = match.end()
+ if output_str:
+ output.append(output_str)
+ return output
+
+ def run_pipeline(self, config_file, name, lines):
+ linesep = Task.get_linesep(lines)
+
+ if name.endswith(".java"):
+ column_limit = 100
+ else:
+ column_limit = 80
+
+ output = ""
+
+ # Construct regex for Doxygen comment
+ indent = r"(?P "):
+ # Add paragraph tag before new paragraph
+ output += " "
+
+ # Strip newlines and extra spaces between words from paragraph
+ contents = " ".join(match.group().split())
+
+ # Capitalize first letter of paragraph and wrap paragraph
+ contents = self.textwrap(
+ contents[:1].upper() + contents[1:],
+ column_limit - len(" * ") - len(spaces))
+
+ # Write out paragraphs
+ for i, line in enumerate(contents):
+ if i == 0:
+ output += line
+ else:
+ output += spaces + " * " + line
+ # Put period at end of paragraph
+ if i == len(contents) - 1 and output[-1] != ".":
+ output += "."
+ output += linesep
+
+ comment_pos += match.end()
+
+ # Parse tags
+ tag_list = []
+ max_arglength = 0
+ for match in tag_rgx.finditer(comment[comment_pos:]):
+ contents = " ".join(match.group("description").split())
+ if match.group("tag_name") == "param":
+ tag_list.append((match.group("tag_name"),
+ match.group("arg_name"), contents))
+
+ # Only param tags are lined up and thus count toward the
+ # maximum amount indented
+ max_arglength = max(max_arglength,
+ len(match.group("arg_name")))
+ else:
+ tag_list.append((match.group("tag_name"), "",
+ match.group("arg_name") + " " + contents))
+
+ # Insert empty line before tags if there was a description before
+ if tag_list and comment_pos > 0:
+ output += spaces + " *" + linesep
+
+ for tag in tag_list:
+ # Only line up param tags
+ if tag[0] == "param":
+ tagline = spaces + " * @" + tag[0] + " " + tag[1]
+ tagline += " " * (max_arglength - len(tag[1]) + 1)
+ else:
+ tagline = spaces + " * @" + tag[0] + " "
+
+ # Capitalize first letter of description and wrap description
+ contents = self.textwrap(
+ tag[2][:1].upper() + tag[2][1:],
+ column_limit - len(tagline) - len(spaces))
+
+ # Write out tags
+ output += tagline
+ for i, line in enumerate(contents):
+ if i == 0:
+ output += line
+ else:
+ output += spaces + " * " + " " * (
+ len(tagline) - len(spaces) - len(" * ")) + line
+ # Put period at end of description
+ if i == len(contents) - 1 and output[-1] != ".":
+ output += "."
+ output += linesep
+
+ # Append closing part of comment
+ output += spaces + " */"
+ pos = comment_match.end()
+
+ # Append leftover lines in file
+ if pos < len(lines):
+ output += lines[pos:]
+
+ if output != lines:
+ return (output, True, True)
+ else:
+ return (lines, False, True)