Difference between revisions of "Policies/Kdepim Coding Style"

Jump to: navigation, search
(Why is coding Style usefull?)
(Replaced content with "{{Moved To Community}}")
 
(78 intermediate revisions by one other user not shown)
Line 1: Line 1:
== Purpose of this document ==
+
{{Moved To Community}}
 
+
This document describes the recommended coding style for kdepim and akonadi. Nobody is
+
forced to use this style, but to have consistent formatting of the source code
+
files it is strongly recommended to make use of it.
+
 
+
''In short: Kdepim and akonadi coding style follows the''
+
[http://techbase.kde.org/Policies/Kdelibs_Coding_Style Kdelibs coding style].
+
 
+
== Why is coding Style usefull? ==
+
 
+
Let us make a comparision with real life.
+
To make an addition, one can do:
+
 
+
{{Output| 1=123
+
+      456
+
  ==========
+
    =      579
+
}}
+
 
+
But we have learned at the primary to write:
+
 
+
{{Output| 1=Addition
+
  123
+
+456
+
====
+
=579
+
}}
+
 
+
Which is much more readable, easy to control (or debug).
+
 
+
This is Coding Style: not necessary but very usefull, and pretty to read.
+
 
+
== What do we need? ==
+
 
+
'''We need at least:'''
+
*a '''specification''' (a set of rules) for the coding style of the sources
+
*some tools to '''check the sources''' against the specification
+
*some tools to '''change the sources'''
+
 
+
[http://astyle.sourceforge.net/ astyle] is a pretty tool to make such changes. But astyle doesn't implement (yet) all the specification rules.
+
 
+
You can find below some awk-scripts which help us to check all the rules.
+
 
+
You can find below some awk-scripts which help us to make most of the changes.
+
The last part must be done manually.
+
 
+
== The specification rules of coding style for kdepim and akonadi ==
+
 
+
*Identation with four spaces, don't use any <TAB>s
+
*Trim the lines
+
*Only single empty lines
+
*The first line, the last line(s) may not be empty
+
*Only one statement per line
+
*Variable declaration
+
*Only one declaration per line
+
*Use a space after each keyword, but not after a cast
+
*Use a space after the name of the class
+
*include directive
+
*Place '''*''' and '''&''' near the variable
+
*Use '''namespace foo {''' in the same line
+
*Each member initialization of a method in a separate line
+
*Surround all operators with spaces
+
*'''switch''' rules
+
*'''if''', '''for''', '''while''' and similar macros rules
+
*'''typedef''' statement over more lines
+
*Don't use '''&,''' without a variable
+
*Don't use untyped '''enum'''
+
*Don't use '''enum''' with empty member
+
*No ''';''' after some macros
+
*No "one line" '''if''' '''for''' '''while''' statement
+
*No code after '''{'''
+
*No code before '''}'' (but else)
+
*No header and body code in the same line, even empty body
+
*No space between some keywords
+
*No space around the index of an array
+
*No space around an expression surrounded with braces
+
*No space before ''':''' in a case statement
+
*No space before ''';''' at the end of statement
+
 
+
== Migration ==
+
 
+
As discussed at the KDEPIM meeting, Berlin, 3 March 2013, all the files of KDEPIM will
+
be reviewed to follow the coding style.  This will be done over a long time,
+
directory after directory, for each of the
+
rules defined above.  For each rule, one can find one or two script(s).
+
 
+
The main part of the changes can be done with astyle:
+
http://astyle.sourceforge.net/
+
 
+
The results can be seen [[here]].
+
 
+
== Two scripts '''to check all the rules''' and '''to make the all the changes''' ==
+
 
+
Most of the rules can be check with the scripts below.
+
For some of the rules, we don't have a script to change the sources. It is better to make first a check for such a rule, make the modification(s) to suscript the rule(s) before using the change-script(s).
+
 
+
If one wants all-in-one scripts, please use:
+
*All-Check.sh
+
*Change-All.sh
+
 
+
Download the scripts: [[Media:ALL-Coding-Style.tar.gz]]
+
 
+
== The scripts '''to check''' and '''to make the changes''' ==
+
 
+
The first script is '''to check''' a single file or a complete directory for all .h and
+
.cpp files.
+
 
+
If present, the second script '''makes the changes''' for a single file or a complete
+
directory for all .h and .cpp files.  For some complicated situations, the
+
script makes no change.
+
 
+
One can use the scripts for own work.It is recommanded to use them in this order.
+
 
+
=== Don't test all directories ===
+
 
+
If a '''.no_coding_style''' file is present on a directory, the test will not be done.
+
 
+
If a '''.no_recursion''' file is present on a directory, we do not explore the subdirectory(ies)
+
 
+
=== Identation with four spaces, don't use any <TAB>s ===
+
 
+
*coding-style-check-Tabs.sh
+
*The changes are well done with
+
{{Output| 1=astyle --indent=spaces
+
}}
+
 
+
Download the scripts: [[Media:Tabs.tar.gz]]
+
 
+
The output of the '''check''' script is:
+
{{Output|1=check the file ktnefparser.cpp
+
308: Tab at 16:  stream_ >> i;              // i <- attribute type & name
+
311: Tab at 16:  stream_ >> i;              // i <- data length
+
326: Tab at 22:  case attATTACHMENT:        // try to get attachment info
+
367: Tab at 16:  stream_ >> u;      // u <- checksum
+
}}
+
 
+
This shows:
+
* the name of the file which is under test.
+
* the line number, the position found and the line itself.
+
 
+
=== Trim the lines ===
+
 
+
*coding-style-check-Trim.sh
+
*The changes are well done with:
+
{{Output| 1=astyle --indent=spaces
+
}}
+
 
+
Download the scripts: [[Media:Trim.tar.gz]]
+
 
+
The output of the '''check''' script is:
+
{{Output|1=check the file trim.cpp
+
51: Space(s) at end of line (28):  QVariant m_matchData;
+
}}
+
 
+
This shows:
+
* the name of the file which is under test.
+
* the line number, the position found and the line itself.
+
 
+
=== Only single empty lines ===
+
Refer to http://techbase.kde.org/Policies/Kdelibs_Coding_Style#Whitespace
+
 
+
*coding-style-check-Twice.sh
+
*coding-style-change-Twice.sh
+
* astyle cannot (yet) do it
+
 
+
Download the scripts: [[Media:Twice.tar.gz]]
+
 
+
The output of the '''check''' script is:
+
{{Output|1=check the file enclosure.cpp
+
25: next empty line found
+
26: next empty line found
+
30: next empty line found
+
}}
+
   
+
This shows:
+
* the name of the file which is under test.
+
* the line numbers.
+
 
+
The '''change''' script:
+
*removes all the next empty line(s).
+
 
+
=== First line, last line(s) may not be empty ===
+
 
+
Some of the sources have a first empty lines, some have one or more empty last line(s).
+
*coding-style-check-First-Last.sh
+
*coding-style-change-First-Last.sh
+
*astyle cannot (yet) do it
+
 
+
Download the scripts: [[Media:First.tar.gz]]
+
 
+
The output of the '''check''' script is:
+
{{Output|1=check the file trim.cpp
+
The first line is empty
+
The last line is empty}}
+
   
+
The '''change''' script:
+
*removes the first line if empty, all the last empty line(s).
+
 
+
=== Only one statement per line ===
+
 
+
We don't provide (yet) any check for this rule.
+
 
+
=== Variable declaration ===
+
 
+
We follow the kdelibs rule: [[http://techbase.kde.org/Policies/Kdelibs_Coding_Style#Variable_declaration]]
+
We don't provide (yet) any check for this rule.
+
 
+
=== Only one declaration per line ===
+
 
+
We follow the kdelibs rule: [[http://techbase.kde.org/Policies/Kdelibs_Coding_Style#Variable_declaration]]
+
We don't provide (yet) any check for this rule.
+
 
+
=== Use one space after each keyword, but not after a cast ===
+
 
+
Refer to http://techbase.kde.org/Policies/Kdelibs_Coding_Style#Whitespace
+
 
+
For most of the keywords, it is not necessary to make a test. Because the sources have been already compiled. For example this code never appear in a compiled source:
+
{{Output|1=intmyVariableAa;
+
floatmyVariableAb;}}
+
 
+
Some of the keywords are alone in the statement, such as '''break''' and '''continue'''. No test is necessary.
+
 
+
The only tests we have to do are the ones where a keyword is (or can be) followed
+
by a sign '''( { [ :'''
+
 
+
These are:
+
'''alignas decltype alignof noexcept typeid asm static_assert switch if catch while for sizeof new Q_FOREACH do try enum union Q_FOREVER bool char char16_t char32_t double float int long wchar_t signed unsigned short'''
+
 
+
For only '''one''' keyword:
+
*coding-style-check-SpaceAfterKeyword.sh
+
*coding-style-change-SpaceAfterKeyword.sh
+
 
+
 
+
For '''all''' keywords above:
+
*coding-style-check-SpaceAfter.sh
+
*coding-style-change-SpaceAfter.sh
+
 
+
 
+
=== Use a space after the name of the class ===
+
 
+
We prefer having a space before the keyword public at the definition of a new class:
+
{{Output|1=class DbException : public Akonadi::Exception
+
{
+
  ...
+
};}}
+
 
+
*coding-style-check-Public.sh
+
*coding-style-change-Public.sh
+
*astyle cannot (yet) do it
+
 
+
Download the scripts: [[Media:Public.tar.gz]]
+
 
+
Download the scripts: [[Media:SpaceAfter.tar.gz]]
+
 
+
The output of the '''check''' script is:
+
{{Output|1=check the file contactstreemodel.cpp
+
98:  if( at 10:          if(contact.realName().isEmpty()) {
+
99:  if( at 12:            if(contact.preferredEmail().isEmpty()) {
+
}}
+
 
+
The '''change''' script:
+
*puts a space after the keyword.
+
 
+
=== #include directive ===
+
 
+
Refer to http://techbase.kde.org/Policies/Kdelibs_Coding_Style#Qt_Includes
+
 
+
We prefer no space at the beginning of the directive. Some (not many) files need to be corrected to unify to all the other files.
+
 
+
{{Output|1=// some files use this
+
# include <A/b>
+
 
+
// we prefer, to unify the coding style
+
#include <A/b>}}
+
 
+
*coding-style-check-Space-Include.sh
+
 
+
Download the scripts: [[Media:Space-Include.tar.gz]]
+
 
+
=== Place '''*''' and '''&''' near the variable ===
+
 
+
The declaration S *D; declares D as a pointer to the type determined by decl-specifier-seq S.
+
 
+
The most compilers do not make any difference for such lines of code:
+
 
+
{{Output|1=int *a;
+
int* b;
+
int * c
+
}}
+
 
+
We prefer the first one, without a space beetwen the star and the name of the variable:
+
 
+
{{Output|1=int *a;
+
}}
+
 
+
The same rule may be use for:
+
 
+
{{Output|1=myFunction(int &a, int& b, int & c)
+
{
+
    // some lines
+
}
+
}}
+
 
+
We prefer:
+
{{Output|1=myFunction(int &a, int &b, int &c)
+
}}
+
 
+
The awk-script checks also the occurences of:
+
* '''&,'''
+
* '''& >'''
+
* '''* >'''
+
* '''( )''' and '''(  )''' ''empty function call''
+
 
+
* '''enum {''' ''untyped enum''
+
 
+
Not all the ouputs are real errors. Some codings might be correct.
+
 
+
*coding-style-check-NO-Space.sh
+
*using astyle to make the changes:
+
{{Output| 1=astyle --reference=name --align-pointer=name
+
}}
+
 
+
 
+
Some lines with "type & name..." must be manually corrected.
+
 
+
Download the scripts: [[Media:NO-Space.tar.gz]]
+
 
+
The script gives informations about the found line(s).
+
 
+
=== Use '''namespace foo {''' in the same line ===
+
 
+
We don't provide (yet) any check for this rule.
+
 
+
=== Each member initialization of a method in separate line ===
+
 
+
This example shows the indentation we prefer:
+
 
+
{{Output|1=class myClass {
+
    // some lines
+
public:
+
    myClass(int r, int b, int i, int j)
+
        : r(0)
+
        , b(i)
+
        , i(5)
+
        , j(13)
+
{
+
    // more lines
+
}
+
}}
+
 
+
*coding-style-check-Default-1.sh
+
*coding-style-check-Default-2.sh
+
*coding-style-change-Default-1.sh
+
*coding-style-change-Default-2.sh
+
*astyle cannot (yet) do it
+
 
+
Download the scripts: [[Media:Default.tar.gz]]
+
 
+
=== Surround all operators with spaces ===
+
 
+
This is well done with astyle:
+
{{Output| 1=astyle --pad-oper
+
}}
+
 
+
=== '''switch''' rules ===
+
 
+
This example shows the indentation we prefer:
+
 
+
{{Output|1=switch (a) {
+
case ''one'':
+
    // some lines
+
    break;
+
case ''two'': {
+
    // some lines
+
    break;
+
}
+
default:
+
    // some lines
+
    break;
+
}
+
}}
+
 
+
*coding-style-check-Switch.sh
+
*astyle
+
 
+
Download the scripts: [[Media:Switch.tar.gz]]
+
 
+
=== '''try-catch''' rules ===
+
 
+
This example shows the indentation we prefer:
+
 
+
{{Output|1=try {
+
    // some lines
+
} catch (...) {
+
}
+
}}
+
 
+
*coding-style-check-Try.sh
+
 
+
Download the scripts: [[Media:Try.tar.gz]]
+
 
+
=== '''if''', '''for''', '''while''' (and similar macros) rules ===
+
 
+
Even for block with only one statement, we prefer to use braces such as:
+
 
+
{{Output|1=if (''condition'') {
+
    ''statement;''
+
}
+
}}
+
 
+
This should be used with the keywords '''if''', '''for''', '''while''' and similar macros.
+
The output of the '''check''' script is:
+
 
+
{{Output|1=check the file test-if.cpp
+
62: if without { at end of line:    if ( collection.cachePolicyLocalParts() )
+
}}
+
 
+
*coding-style-check-If.sh
+
*astyle
+
 
+
Download the scripts: [[Media:If.tar.gz]]
+
 
+
But we get some false alarm with statement over more than one line:
+
 
+
{{Output|1=if (''condition_1''
+
    && ''condition_2'') {
+
    ''statement;''
+
}
+
}}
+
 
+
=== '''typedef''' statement over more lines ===
+
 
+
We don't provide (yet) any check for this rule.
+
 
+
=== Don't use '''&''', without a variable ===
+
 
+
=== Don't use untyped '''enum''' ===
+
 
+
Instead of having an untyped enum such as:
+
{{Output|1=  enum {
+
    aElement= 123
+
}
+
}}
+
we prefer a #define directive:
+
{{Output|1=#define aElement 123
+
}}
+
 
+
Download the scripts: [[Media:Enum.tar.gz]]
+
 
+
=== Don't use '''enum''' with empty member ===
+
 
+
The most compilers do not complain such a code:
+
 
+
{{Output|1=  enum mytype {
+
    aElement,
+
    bElement,
+
}
+
}}
+
 
+
The last element is empty.
+
We prefer a "pedantic" code such as:
+
 
+
{{Output|1=  enum mytype {
+
    aElement,
+
    bElement
+
}
+
}}
+
 
+
*coding-style-check-Enum-Pedantic.sh
+
 
+
The output of the '''check''' script is:
+
{{Output|1=check the file enum-example.cpp
+
enum with ,} found at
+
3->    bElement,
+
4->  }
+
}}
+
 
+
Download the scripts: [[Media:Enum-Pedantic.tar.gz]]
+
 
+
=== No ''';''' after some macros ===
+
 
+
Looking over the git-history, one can find some "pedantic" changes.
+
These are changes to make a better code. The most of them are at the use of macro, where it is not necessary to have a ''';''' at the end ofthe command.
+
The script make a check over all these:
+
'''AKTEST_MAIN;MAKE_CMD_ROW;Q_DECLARE_FLAGS;Q_PRIVATE_SLOT;Q_DECLARE_METATYPE;Q_DECLARE_OPERATORS_FOR_FLAGS;Q_DE
+
CLARE_PRIVATE;Q_DECLARE_PUBLIC;Q_DISABLE_COPY;K_GLOBAL_STATIC;Q_IMPORT_PLUGIN;Q_PROPERTY;Q_UNUSED;QTEST_KDEMAIN;QTEST_MAIN'''
+
 
+
*coding-style-check-Pedantic.sh
+
*astyle cannot (yet) do it
+
 
+
Download the scripts: [[Media:Pedantic.tar.gz]]
+
 
+
=== No "one line" '''if''' '''for''' '''while''' statement ===
+
 
+
Refer to http://techbase.kde.org/Policies/Kdelibs_Coding_Style#Braces
+
 
+
The following code:
+
{{Output|1=if (a > b) c = 123;}}
+
is correct, but we prefer the block:
+
{{Output|1=if (a > b) {
+
  c = 123;
+
}
+
}}
+
which is easier to debug, to read and to modify.
+
 
+
It is also possible to put a breakpoint at the line in the block.
+
 
+
As the awk-script is too simple to recognize all the if-statements, we get some false alarm and
+
we can't make the changes automatically.
+
 
+
*coding-style-check-OneLine-If.sh
+
 
+
Download the scripts: [[Media:One-Line-If.tar.gz]]
+
 
+
The output of the '''check''' script is:
+
{{Output|1=check the file if-example.cpp
+
25: one-line-if found
+
}}
+
 
+
=== No space between some keywords ===
+
 
+
We don't want to have a space:
+
*after a '''*''' (star), but at the multiplication
+
*between '''&''' and '''>'''
+
*between '''*''' and '''>'''
+
*between '''(''' and ''')''', an empty parameter list.
+
 
+
*coding-style-check-No-Space.sh
+
 
+
The output of the '''check''' script is:
+
{{Output|1=check the file NO-space-example.cpp
+
15: Star<Space> found. Check it.  int * myA;
+
28: AND<Space> found. Check it.  abc( & myA);
+
}}
+
 
+
Download the scripts: [[Media:NO-space.tar.gz]]
+
 
+
=== No space around the index of an array ===
+
 
+
We don't want to have spaces around the index of an array element.
+
 
+
*coding-style-check-No-Space.sh
+
 
+
The output of the '''check''' script is:
+
{{Output|1=check the file NO-space-example.cpp
+
15: [<Space> found. Check it.  a = b[ i ];
+
15: <Space>] found. Check it.  a = b[ i ];
+
}}
+
 
+
Download the scripts: [[Media:NO-space.tar.gz]]
+
 
+
=== No space around an expression surrounded with braces ===
+
 
+
We prefer function definition and function call with no space after the opening brace and before the closing brace.
+
 
+
*coding-style-check-Parenthesis.sh
+
*This is well done with astyle:
+
{{Output| 1=astyle --unpad-paren
+
}}
+
Note that astyle makes also changes within the macros SIGNAL and SLOT, which aren't desired.
+
This can be corrected with a Qt-utility qt5/qtrepotools/util/normalize/normalize:
+
{{Output| 1=normalize --modify ''filename''
+
}}
+
 
+
Download the scripts: [[Media:Parenthesis.tar.gz]]
+
 
+
=== No space before ''':''' in a case statement ===
+
 
+
We don't provide (yet) any check for this rule.
+
 
+
=== No space before ''';''' at the end of statement ===
+
 
+
We don't provide (yet) any check for this rule.
+
 
+
== Use all the scripts ==
+
 
+
All the scripts can be used with one only script.
+
 
+
Download the scripts: [[Media:All.tar.gz]]
+
 
+
=== put the comments away, change the strings, don't read the directive ===
+
 
+
The comments might contain some keyword. It is very difficult to avoid the confusion with the very simple awk-scripts. We prefer to change all the comments with the same number of empty lines.
+
It is very difficult to parse the strings correctly, so we prefer to change them to an empty string.
+
The same with the directive, so we prefer to change them to an empty line.
+
 
+
*CoStrDef.awk
+
 
+
Download the scripts: [[Media:CoStrDef.tar.gz]]
+
 
+
== Check the objects and the libs ==
+
 
+
As a first approach, not any object may have binary change after applying one of the rules.
+
To check this, one uses the '''Md5sum-the-Objects.sh'''. Download the script: [[Media:Md5sum-the-Objects.sh.gz]]
+
Same for the libs. Use the '''Md5sum-the-Libs.sh'''. Download the script: [[Media:Md5sum-the-Libs.sh.gz]]
+
 
+
The script can be used with one of the commands:
+
* save
+
* test
+
* clean
+
 
+
'''An example:'''
+
 
+
{{Input|1=cd <some_kdepim_directory>
+
mkdir build
+
cd build
+
ccmake ../
+
make}}
+
{{Output|1=<span style="color:Fuchsia">Scanning dependencies of target gpgmepp</span>
+
[  0%] <span style="color:green">Building CXX object gpgme++/CMakeFiles/gpgmepp.dir/gpgmepp_automoc.cpp.o</span>
+
[  0%] <span style="color:green">Building CXX object gpgme++/CMakeFiles/gpgmepp.dir/exception.cpp.o</span>
+
[  0%] <span style="color:green">Building CXX object gpgme++/CMakeFiles/gpgmepp.dir/context.cpp.o</span>
+
...}}
+
 
+
{{Input|1=Check-the-Objects.sh save}}
+
The script makes a copy of all the objects and a "time stamp":
+
{{Output|1=save the object ./kholidays/tests/CMakeFiles/testzodiac.dir/testzodiac.cpp.o
+
save the object ./kholidays/tests/CMakeFiles/testzodiac.dir/testzodiac_automoc.cpp.o
+
...
+
all objects are saved}}
+
 
+
Now, one makes somes change(s) on the source(s) and:
+
 
+
{{Input| 1=make}}
+
 
+
Depending on the Makefile, some objects will be compiled again:
+
 
+
{{Output| 1=<span style="color:Fuchsia">Scanning dependencies of target akonadi-kde</span>
+
[ 17%] <span style="color:green">Building CXX object akonadi/CMakeFiles/akonadi-kde.dir/entitytreeview.cpp.o</span>
+
[ 17%] <span style="color:green">Building CXX object akonadi/CMakeFiles/akonadi-kde.dir/itemfetchjob.cpp.o</span>
+
[ 17%] <span style="color:green">Building CXX object akonadi/CMakeFiles/akonadi-kde.dir/statisticsproxymodel.cpp.o</span>
+
...
+
<span style="color:Fuchsia">Scanning dependencies of target akonadi-kmime</span>
+
[ 56%] <span style="color:green">Building CXX object akonadi/kmime/CMakeFiles/akonadi-kmime.dir/standardmailactionmanager.cpp.o</span>}}
+
 
+
{{Input|1=Check-the-Objects.sh test}}
+
 
+
The script finds all the new objects, makes a comparision with the saved version:
+
{{Output|1=test the object ./akonadi/CMakeFiles/akonadi-kde.dir/statisticsproxymodel.cpp.o
+
test the object ./akonadi/CMakeFiles/akonadi-kde.dir/entitytreeview.cpp.o
+
test the object ./akonadi/CMakeFiles/akonadi-kde.dir/itemfetchjob.cpp.o
+
test the object ./akonadi/kmime/CMakeFiles/akonadi-kmime.dir/standardmailactionmanager.cpp.o
+
all tests are OK
+
}}
+
 
+
== Check the assembler files ==
+
If we add or remove some lines, the debug informations included in the object file will be change also.
+
 
+
This is the case with the test/change of "''Only single empty lines should be used''", "''First line, last line(s) may not be empty''" and some more test/change below (''adding some blocks'' with { and }).
+
 
+
For this reason it is no more possible to compare the objects.
+
We have to compare the assembler files.
+
This works pretty well for the version with '''CMAKE_BUILD_TYPE''' set to ''release''.
+
For the version with '''CMAKE_BUILD_TYPE''' set to ''debug'', we must remove all the debug informations before the comparision could take place.
+
 
+
=== Generate the assembler files ===
+
 
+
To generate the assembler files, we only need to modify the ''build.make'' in every folder.
+
 
+
The script '''Prepare-build_make_files.sh''' works on the all directory, finds the line with the compiler command,
+
duplicates the line, add a ''-S option'' and changes the name of the output to ''somename.s''.
+
After a new ''make'' command, we can save all the assembler files with the script '''Check-the-assembler_code.sh'''.
+
Download the script: [[Media:Prepare-build_make_files.gz]]
+
 
+
=== Remove the debug informations ===
+
 
+
The debug informations change with the changes of line numbers.
+
We drop all these debug informations before making the test.
+
 
+
The script to check the assembler files can be used in the same way as the one above (Check-the-Objects.sh).
+
To check this, one uses the '''Check-the-assembler_code.sh'''. Download the script: [[Media:Check-the-assembler_code.sh.gz]]
+
 
+
The script can be used with one of the commands:
+
* save
+
* test
+
* clean
+
 
+
== The results of the migration ==
+
 
+
The results can be seen [[here]].
+

Latest revision as of 18:19, 10 March 2016

This page is now on the community wiki.


This page was last modified on 10 March 2016, at 18:19. Content is available under Creative Commons License SA 3.0 as well as the GNU Free Documentation License 1.2 unless otherwise noted.