summaryrefslogtreecommitdiff
path: root/doc/coding-standard.tex
blob: dd38e1ba2720487ef5818d1d79f67f93112da486 (plain)
  1. \documentclass{article}
  2. \usepackage{metatron}
  3. \title{LedgerSMB Coding Standards}
  4. \author{The LedgerSMB Development Team}
  5. \begin{document}
  6. \maketitle
  7. \tableofcontents
  8. \section{Introduction}
  9. Consistent coding style contributes to readability of code. This contributes in
  10. turn to fewer bugs and easier debugging.
  11. While consultants which implement custom solutions for their customers are not
  12. required to follow these coding guidelines, doing so will likely contribute to
  13. efficiency of the project and, if desired, the likelihood of having the changes
  14. accepted in the main source tree.
  15. \section{File Organization and Whitespace}
  16. Files should be organized according to three principles: atomicity, performance,
  17. and readability. While there is no stated maximum file size, there is no reason to
  18. group large amounts of code together when only a small subset of that code will
  19. be executed on any given run of the program. Similarly, core API for a single
  20. data structure entity may be grouped together even if each run may touch only a
  21. small part of the code so that these functions can be maintained together in a
  22. logical way.
  23. Nested blocks of code should be indented with a single 4 spaces. Tabs are OK,
  24. but before release, a single commit will be made using perltidy -i=4.
  25. Lines longer than 79 characters are not handled well by many terminals and
  26. should generally be avoided. Continued lines should be indented by one more tab
  27. than either the line above or below (i.e. if the line above is indented by two
  28. tabs and the line below by three, indent the continued segment by four).
  29. Lines longer than 79 characters cause problems in some terminals and should be
  30. avoided.
  31. \section{Comments}
  32. In an ideal world, the code should be sufficiently readable to be
  33. entirely understandable without comments. Unfortunately, this sort of
  34. ideal is seldom attained. Comments should be used to annotate code
  35. and add information that is not already a part of code or programming
  36. logic itself.
  37. Comments may include arguments and return values of functions (for
  38. easy reference), a summary as to why a particular design choice was
  39. made, or a note to alert future programmers that a particular chunk of
  40. code deserves additional attention. Comments should not, however,
  41. merely indicate what the program is doing or how it does something.
  42. If such comments are required, that is a good indication that a block
  43. of code requires rewriting.
  44. Additionally it is a good idea to provide a brief description at the top of each
  45. file describing, in general terms, what its function is.
  46. \section{Function Organization}
  47. Functions should be atomic. While there is no maximum length to functions, long
  48. functions may be an indication that a function may be non-atomic.
  49. In general, when more than one line of code is being copied and
  50. pasted, it should instead be moved into its own function where it can
  51. be called by all entry points.
  52. \end{document}
t;;
  • }
  • if (exists $config{svnpath}) {
  • # code depends on the path not having extraneous slashes
  • $config{svnpath}=~tr#/#/#s;
  • $config{svnpath}=~s/\/$//;
  • $config{svnpath}=~s/^\///;
  • }
  • if (defined $config{svn_wrapper} && length $config{svn_wrapper}) {
  • push @{$config{wrappers}}, {
  • wrapper => $config{svn_wrapper},
  • wrappermode => (defined $config{svn_wrappermode} ? $config{svn_wrappermode} : "04755"),
  • };
  • }
  • } #}}}
  • sub getsetup () { #{{{
  • return
  • plugin => {
  • safe => 0, # rcs plugin
  • rebuild => undef,
  • },
  • svnrepo => {
  • type => "string",
  • example => "/svn/wiki",
  • description => "subversion repository location",
  • safe => 0, # path
  • rebuild => 0,
  • },
  • svnpath => {
  • type => "string",
  • example => "trunk",
  • description => "path inside repository where the wiki is located",
  • safe => 0, # paranoia
  • rebuild => 0,
  • },
  • svn_wrapper => {
  • type => "string",
  • example => "/svn/wikirepo/hooks/post-commit",
  • description => "svn post-commit hook to generate",
  • safe => 0, # file
  • rebuild => 0,
  • },
  • svn_wrappermode => {
  • type => "string",
  • example => '04755',
  • description => "mode for svn_wrapper (can safely be made suid)",
  • safe => 0,
  • rebuild => 0,
  • },
  • historyurl => {
  • type => "string",
  • example => "http://svn.example.org/trunk/[[file]]",
  • description => "viewvc url to show file history ([[file]] substituted)",
  • safe => 1,
  • rebuild => 1,
  • },
  • diffurl => {
  • type => "string",
  • example => "http://svn.example.org/trunk/[[file]]?root=wiki&r1=[[r1]]&r2=[[r2]]",
  • description => "viewvc url to show a diff ([[file]], [[r1]], and [[r2]] substituted)",
  • safe => 1,
  • rebuild => 1,
  • },
  • } #}}}
  • # svn needs LC_CTYPE set to a UTF-8 locale, so try to find one. Any will do.
  • sub find_lc_ctype() {
  • my $current = setlocale(LC_CTYPE());
  • return $current if $current =~ m/UTF-?8$/i;
  • # Make some obvious attempts to avoid calling `locale -a`
  • foreach my $locale ("$current.UTF-8", "en_US.UTF-8", "en_GB.UTF-8") {
  • return $locale if setlocale(LC_CTYPE(), $locale);
  • }
  • # Try to get all available locales and pick the first UTF-8 one found.
  • if (my @locale = grep(/UTF-?8$/i, `locale -a`)) {
  • chomp @locale;
  • return $locale[0] if setlocale(LC_CTYPE(), $locale[0]);
  • }
  • # fallback to the current locale
  • return $current;
  • } # }}}
  • $ENV{LC_CTYPE} = $ENV{LC_CTYPE} || find_lc_ctype();
  • sub svn_info ($$) { #{{{
  • my $field=shift;
  • my $file=shift;
  • my $info=`LANG=C svn info $file`;
  • my ($ret)=$info=~/^$field: (.*)$/m;
  • return $ret;
  • } #}}}
  • sub rcs_update () { #{{{
  • if (-d "$config{srcdir}/.svn") {
  • if (system("svn", "update", "--quiet", $config{srcdir}) != 0) {
  • warn("svn update failed\n");
  • }
  • }
  • } #}}}
  • sub rcs_prepedit ($) { #{{{
  • # Prepares to edit a file under revision control. Returns a token
  • # that must be passed into rcs_commit when the file is ready
  • # for committing.
  • # The file is relative to the srcdir.
  • my $file=shift;
  • if (-d "$config{srcdir}/.svn") {
  • # For subversion, return the revision of the file when
  • # editing begins.
  • my $rev=svn_info("Revision", "$config{srcdir}/$file");
  • return defined $rev ? $rev : "";
  • }
  • } #}}}
  • sub rcs_commit ($$$;$$) { #{{{
  • # Tries to commit the page; returns undef on _success_ and
  • # a version of the page with the rcs's conflict markers on failure.
  • # The file is relative to the srcdir.
  • my $file=shift;
  • my $message=shift;
  • my $rcstoken=shift;
  • my $user=shift;
  • my $ipaddr=shift;
  • if (defined $user) {
  • $message="web commit by $user".(length $message ? ": $message" : "");
  • }
  • elsif (defined $ipaddr) {
  • $message="web commit from $ipaddr".(length $message ? ": $message" : "");
  • }
  • if (-d "$config{srcdir}/.svn") {
  • # Check to see if the page has been changed by someone
  • # else since rcs_prepedit was called.
  • my ($oldrev)=$rcstoken=~/^([0-9]+)$/; # untaint
  • my $rev=svn_info("Revision", "$config{srcdir}/$file");
  • if (defined $rev && defined $oldrev && $rev != $oldrev) {
  • # Merge their changes into the file that we've
  • # changed.
  • if (system("svn", "merge", "--quiet", "-r$oldrev:$rev",
  • "$config{srcdir}/$file", "$config{srcdir}/$file") != 0) {
  • warn("svn merge -r$oldrev:$rev failed\n");
  • }
  • }
  • if (system("svn", "commit", "--quiet",
  • "--encoding", "UTF-8", "-m",
  • IkiWiki::possibly_foolish_untaint($message),
  • $config{srcdir}) != 0) {
  • my $conflict=readfile("$config{srcdir}/$file");
  • if (system("svn", "revert", "--quiet", "$config{srcdir}/$file") != 0) {
  • warn("svn revert failed\n");
  • }
  • return $conflict;
  • }
  • }
  • return undef # success
  • } #}}}
  • sub rcs_commit_staged ($$$) {
  • # Commits all staged changes. Changes can be staged using rcs_add,
  • # rcs_remove, and rcs_rename.
  • my ($message, $user, $ipaddr)=@_;
  • if (defined $user) {
  • $message="web commit by $user".(length $message ? ": $message" : "");
  • }
  • elsif (defined $ipaddr) {
  • $message="web commit from $ipaddr".(length $message ? ": $message" : "");
  • }
  • if (system("svn", "commit", "--quiet",
  • "--encoding", "UTF-8", "-m",
  • IkiWiki::possibly_foolish_untaint($message),
  • $config{srcdir}) != 0) {
  • warn("svn commit failed\n");
  • return 1; # failure
  • }
  • return undef # success
  • }
  • sub rcs_add ($) { #{{{
  • # filename is relative to the root of the srcdir
  • my $file=shift;
  • if (-d "$config{srcdir}/.svn") {
  • my $parent=IkiWiki::dirname($file);
  • while (! -d "$config{srcdir}/$parent/.svn") {
  • $file=$parent;
  • $parent=IkiWiki::dirname($file);
  • }
  • if (system("svn", "add", "--quiet", "$config{srcdir}/$file") != 0) {
  • warn("svn add failed\n");
  • }
  • }
  • } #}}}
  • sub rcs_remove ($) { #{{{
  • # filename is relative to the root of the srcdir
  • my $file=shift;
  • if (-d "$config{srcdir}/.svn") {
  • if (system("svn", "rm", "--force", "--quiet", "$config{srcdir}/$file") != 0) {
  • warn("svn rm failed\n");
  • }
  • }
  • } #}}}
  • sub rcs_rename ($$) { #{{{
  • # filenames relative to the root of the srcdir
  • my ($src, $dest)=@_;
  • if (-d "$config{srcdir}/.svn") {
  • # Add parent directory for $dest
  • my $parent=dirname($dest);
  • if (! -d "$config{srcdir}/$parent/.svn") {
  • while (! -d "$config{srcdir}/$parent/.svn") {
  • $parent=dirname($dest);
  • }
  • if (system("svn", "add", "--quiet", "$config{srcdir}/$parent") != 0) {
  • warn("svn add $parent failed\n");
  • }
  • }
  • if (system("svn", "mv", "--force", "--quiet",
  • "$config{srcdir}/$src", "$config{srcdir}/$dest") != 0) {
  • warn("svn rename failed\n");
  • }
  • }
  • } #}}}
  • sub rcs_recentchanges ($) { #{{{
  • my $num=shift;
  • my @ret;
  • return unless -d "$config{srcdir}/.svn";
  • eval q{
  • use Date::Parse;
  • use XML::SAX;
  • use XML::Simple;
  • };
  • error($@) if $@;
  • # avoid using XML::SAX::PurePerl, it's buggy with UTF-8 data