Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
doxverilog
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Commits
Open sidebar
Elphel
doxverilog
Commits
744d1ca5
Commit
744d1ca5
authored
Dec 29, 2013
by
Dimitri van Heesch
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
More work on the template and context mechanisms
parent
2912829c
Changes
14
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
1284 additions
and
306 deletions
+1284
-306
classdef.cpp
src/classdef.cpp
+13
-9
classdef.h
src/classdef.h
+6
-2
context.cpp
src/context.cpp
+832
-96
context.h
src/context.h
+41
-1
filedef.cpp
src/filedef.cpp
+15
-5
filedef.h
src/filedef.h
+4
-0
memberdef.cpp
src/memberdef.cpp
+18
-9
memberdef.h
src/memberdef.h
+2
-1
message.cpp
src/message.cpp
+7
-2
message.h
src/message.h
+2
-0
namespacedef.cpp
src/namespacedef.cpp
+33
-23
namespacedef.h
src/namespacedef.h
+1
-0
template.cpp
src/template.cpp
+264
-124
template.h
src/template.h
+46
-34
No files found.
src/classdef.cpp
View file @
744d1ca5
...
...
@@ -1767,20 +1767,24 @@ void ClassDef::writeMoreLink(OutputList &ol,const QCString &anchor)
}
}
bool
ClassDef
::
visibleInParentsDeclList
()
const
{
static
bool
extractPrivate
=
Config_getBool
(
"EXTRACT_PRIVATE"
);
static
bool
hideUndocClasses
=
Config_getBool
(
"HIDE_UNDOC_CLASSES"
);
static
bool
extractLocalClasses
=
Config_getBool
(
"EXTRACT_LOCAL_CLASSES"
);
bool
linkable
=
isLinkable
();
return
(
name
().
find
(
'@'
)
==-
1
&&
!
isExtension
()
&&
(
protection
()
!=::
Private
||
extractPrivate
)
&&
(
linkable
||
(
!
hideUndocClasses
&&
(
!
isLocal
()
||
extractLocalClasses
)))
);
}
void
ClassDef
::
writeDeclarationLink
(
OutputList
&
ol
,
bool
&
found
,
const
char
*
header
,
bool
localNames
)
{
//static bool fortranOpt = Config_getBool("OPTIMIZE_FOR_FORTRAN");
//static bool vhdlOpt = Config_getBool("OPTIMIZE_OUTPUT_VHDL");
static
bool
hideUndocClasses
=
Config_getBool
(
"HIDE_UNDOC_CLASSES"
);
static
bool
extractLocalClasses
=
Config_getBool
(
"EXTRACT_LOCAL_CLASSES"
);
bool
isLink
=
isLinkable
();
SrcLangExt
lang
=
getLanguage
();
if
(
isLink
||
(
!
hideUndocClasses
&&
(
!
isLocal
()
||
extractLocalClasses
)
)
)
if
(
visibleInParentsDeclList
())
{
if
(
!
found
)
// first class
{
...
...
@@ -1820,7 +1824,7 @@ void ClassDef::writeDeclarationLink(OutputList &ol,bool &found,const char *heade
ol
.
writeString
(
" "
);
ol
.
insertMemberAlign
();
}
if
(
isLink
)
if
(
isLink
able
()
)
{
ol
.
writeObjectLink
(
getReference
(),
getOutputFileBase
(),
...
...
src/classdef.h
View file @
744d1ca5
...
...
@@ -168,6 +168,9 @@ class ClassDef : public Definition
/** the class is visible in a class diagram, or class hierarchy */
bool
isVisibleInHierarchy
();
/** show this class in the declaration section of its parent? */
bool
visibleInParentsDeclList
()
const
;
/** Returns the template arguments of this class
* Will return 0 if not applicable.
*/
...
...
@@ -310,14 +313,13 @@ class ClassDef : public Definition
QCString
generatedFromFiles
()
const
;
const
FileList
&
usedFiles
()
const
;
QCString
includeStatement
()
const
;
const
ArgumentList
*
typeConstraints
()
const
;
const
ExampleSDict
*
exampleList
()
const
;
bool
hasExamples
()
const
;
QCString
getMemberListFileName
()
const
;
bool
subGrouping
()
const
;
//-----------------------------------------------------------------------------------
// --- setters ----
//-----------------------------------------------------------------------------------
...
...
@@ -432,6 +434,8 @@ class ClassDef : public Definition
QPtrDict
<
void
>
*
visitedClasses
);
void
getTitleForMemberListType
(
MemberListType
type
,
QCString
&
title
,
QCString
&
subtitle
);
QCString
includeStatement
()
const
;
ClassDefImpl
*
m_impl
;
...
...
src/context.cpp
View file @
744d1ca5
This diff is collapsed.
Click to expand it.
src/context.h
View file @
744d1ca5
...
...
@@ -3,6 +3,7 @@
#include "types.h"
#include "template.h"
#include <qlist.h>
class
Definition
;
class
ClassDef
;
...
...
@@ -108,7 +109,7 @@ class UsedFilesContext : public TemplateListIntf
class
IncludeInfoContext
:
public
TemplateStructIntf
{
public
:
IncludeInfoContext
(
IncludeInfo
*
,
SrcLangExt
lang
);
IncludeInfoContext
(
const
IncludeInfo
*
,
SrcLangExt
lang
);
~
IncludeInfoContext
();
// TemplateStructIntf methods
...
...
@@ -119,6 +120,25 @@ class IncludeInfoContext : public TemplateStructIntf
Private
*
p
;
};
//----------------------------------------------------
class
IncludeInfoListContext
:
public
TemplateListIntf
{
public
:
IncludeInfoListContext
(
const
QList
<
IncludeInfo
>
&
list
,
SrcLangExt
lang
);
~
IncludeInfoListContext
();
// TemplateListIntf
virtual
int
count
()
const
;
virtual
TemplateVariant
at
(
int
index
)
const
;
virtual
TemplateListIntf
::
ConstIterator
*
createIterator
()
const
;
private
:
class
Private
;
Private
*
p
;
};
//----------------------------------------------------
class
ClassContext
:
public
TemplateStructIntf
...
...
@@ -254,6 +274,26 @@ class NestedClassListContext : public TemplateListIntf
//----------------------------------------------------
class
NestedNamespaceListContext
:
public
TemplateListIntf
{
public
:
NestedNamespaceListContext
();
~
NestedNamespaceListContext
();
// TemplateListIntf
virtual
int
count
()
const
;
virtual
TemplateVariant
at
(
int
index
)
const
;
virtual
TemplateListIntf
::
ConstIterator
*
createIterator
()
const
;
void
append
(
NamespaceDef
*
cd
);
private
:
class
Private
;
Private
*
p
;
};
//----------------------------------------------------
class
ClassListContext
:
public
TemplateListIntf
{
public
:
...
...
src/filedef.cpp
View file @
744d1ca5
...
...
@@ -170,12 +170,18 @@ void FileDef::findSectionsInDocumentation()
}
}
bool
FileDef
::
hasDetailedDescription
()
const
{
static
bool
sourceBrowser
=
Config_getBool
(
"SOURCE_BROWSER"
);
return
((
!
briefDescription
().
isEmpty
()
&&
Config_getBool
(
"REPEAT_BRIEF"
))
||
!
documentation
().
stripWhiteSpace
().
isEmpty
()
||
// avail empty section
(
sourceBrowser
&&
getStartBodyLine
()
!=-
1
&&
getBodyDef
())
);
}
void
FileDef
::
writeDetailedDescription
(
OutputList
&
ol
,
const
QCString
&
title
)
{
if
((
!
briefDescription
().
isEmpty
()
&&
Config_getBool
(
"REPEAT_BRIEF"
))
||
!
documentation
().
stripWhiteSpace
().
isEmpty
()
||
// avail empty section
(
Config_getBool
(
"SOURCE_BROWSER"
)
&&
getStartBodyLine
()
!=-
1
&&
getBodyDef
())
)
if
(
hasDetailedDescription
())
{
ol
.
pushGeneratorState
();
ol
.
disable
(
OutputGenerator
::
Html
);
...
...
@@ -379,7 +385,7 @@ void FileDef::writeIncludedByGraph(OutputList &ol)
{
warn_uncond
(
"Included by graph for '%s' not generated, too many nodes. Consider increasing DOT_GRAPH_MAX_NODES.
\n
"
,
name
().
data
());
}
if
(
!
incDepGraph
.
isTrivial
())
else
if
(
!
incDepGraph
.
isTrivial
())
{
ol
.
startTextBlock
();
ol
.
disable
(
OutputGenerator
::
Man
);
...
...
@@ -1800,3 +1806,7 @@ QCString FileDef::title() const
return
theTranslator
->
trFileReference
(
name
());
}
QCString
FileDef
::
fileVersion
()
const
{
return
m_fileVersion
;
}
src/filedef.h
View file @
744d1ca5
...
...
@@ -133,6 +133,10 @@ class FileDef : public Definition
ClassSDict
*
getClassSDict
()
const
{
return
m_classSDict
;
}
QCString
title
()
const
;
bool
hasDetailedDescription
()
const
;
QCString
fileVersion
()
const
;
bool
subGrouping
()
const
{
return
m_subGrouping
;
}
//---------------------------------
...
...
src/memberdef.cpp
View file @
744d1ca5
...
...
@@ -3036,6 +3036,18 @@ static Definition *getClassFromType(Definition *scope,const QCString &type,SrcLa
}
#endif
QCString
MemberDef
::
fieldType
()
const
{
QCString
type
=
m_impl
->
accessorType
;
if
(
type
.
isEmpty
())
{
type
=
m_impl
->
type
;
}
if
(
isTypedef
())
type
.
prepend
(
"typedef "
);
return
simplifyTypeForTable
(
type
);
}
void
MemberDef
::
writeMemberDocSimple
(
OutputList
&
ol
,
Definition
*
container
)
{
Definition
*
scope
=
getOuterScope
();
...
...
@@ -3056,15 +3068,7 @@ void MemberDef::writeMemberDocSimple(OutputList &ol, Definition *container)
ol
.
startInlineMemberType
();
ol
.
startDoxyAnchor
(
cfname
,
cname
,
memAnchor
,
doxyName
,
doxyArgs
);
QCString
type
=
m_impl
->
accessorType
;
if
(
type
.
isEmpty
())
{
type
=
m_impl
->
type
;
}
if
(
isTypedef
())
type
.
prepend
(
"typedef "
);
QCString
ts
=
simplifyTypeForTable
(
type
);
QCString
ts
=
fieldType
();
if
(
cd
)
// cd points to an anonymous struct pointed to by this member
// so we add a link to it from the type column.
...
...
@@ -3855,6 +3859,11 @@ void MemberDef::setAccessorType(ClassDef *cd,const char *t)
m_impl
->
accessorType
=
t
;
}
ClassDef
*
MemberDef
::
accessorClass
()
const
{
return
m_impl
->
accessorClass
;
}
void
MemberDef
::
findSectionsInDocumentation
()
{
docFindSections
(
documentation
(),
this
,
0
,
docFile
());
...
...
src/memberdef.h
View file @
744d1ca5
...
...
@@ -81,6 +81,7 @@ class MemberDef : public Definition
ClassDef
*
getClassDef
()
const
;
FileDef
*
getFileDef
()
const
;
NamespaceDef
*
getNamespaceDef
()
const
;
ClassDef
*
accessorClass
()
const
;
// grabbing the property read/write accessor names
const
char
*
getReadAccessor
()
const
;
...
...
@@ -251,7 +252,7 @@ class MemberDef : public Definition
// overrules
QCString
documentation
()
const
;
QCString
briefDescription
(
bool
abbr
=
FALSE
)
const
;
QCString
fieldType
()
const
;
//-----------------------------------------------------------------------------------
...
...
src/message.cpp
View file @
744d1ca5
...
...
@@ -13,7 +13,6 @@
*
*/
#include <stdarg.h>
#include <stdio.h>
#include <qdatetime.h>
#include "config.h"
...
...
@@ -22,6 +21,7 @@
#include "doxygen.h"
#include "portable.h"
#include "filedef.h"
#include "message.h"
static
QCString
outputFormat
;
static
const
char
*
warning_str
=
"warning: "
;
...
...
@@ -110,7 +110,7 @@ void msg(const char *fmt, ...)
va_list
args
;
va_start
(
args
,
fmt
);
vfprintf
(
stdout
,
fmt
,
args
);
va_end
(
args
);
va_end
(
args
);
}
}
...
...
@@ -172,6 +172,11 @@ void warn(const char *file,int line,const char *fmt, ...)
va_end
(
args
);
}
void
warn
(
const
char
*
file
,
int
line
,
const
char
*
fmt
,
va_list
args
)
{
do_warn
(
"WARNINGS"
,
file
,
line
,
warning_str
,
fmt
,
args
);
}
void
warn_simple
(
const
char
*
file
,
int
line
,
const
char
*
text
)
{
if
(
!
Config_getBool
(
"WARNINGS"
))
return
;
// warning type disabled
...
...
src/message.h
View file @
744d1ca5
...
...
@@ -19,9 +19,11 @@
#define MESSAGE_H
#include <stdio.h>
#include <stdarg.h>
extern
void
msg
(
const
char
*
fmt
,
...);
extern
void
warn
(
const
char
*
file
,
int
line
,
const
char
*
fmt
,
...);
extern
void
warn
(
const
char
*
file
,
int
line
,
const
char
*
fmt
,
va_list
args
);
extern
void
warn_simple
(
const
char
*
file
,
int
line
,
const
char
*
text
);
extern
void
warn_undoc
(
const
char
*
file
,
int
line
,
const
char
*
fmt
,
...);
extern
void
warn_doc_error
(
const
char
*
file
,
int
line
,
const
char
*
fmt
,
...);
...
...
src/namespacedef.cpp
View file @
744d1ca5
...
...
@@ -916,29 +916,9 @@ void NamespaceSDict::writeDeclaration(OutputList &ol,const char *title,
continue
;
// will be output in another pass, see layout_default.xml
ol
.
startMemberDeclaration
();
ol
.
startMemberItem
(
nd
->
getOutputFileBase
(),
0
);
if
(
lang
==
SrcLangExt_Java
||
lang
==
SrcLangExt_CSharp
)
{
ol
.
docify
(
"package "
);
}
else
if
(
lang
==
SrcLangExt_Fortran
)
{
ol
.
docify
(
"module "
);
}
else
if
(
lang
==
SrcLangExt_IDL
)
{
if
(
nd
->
isModule
())
{
ol
.
docify
(
"module "
);
}
else
if
(
nd
->
isConstantGroup
())
{
ol
.
docify
(
"constants"
);
}
else
{
err
(
"Internal inconsistency: namespace in IDL not module or cg
\n
"
);
}
}
QCString
ct
=
nd
->
compoundTypeString
();
ol
.
docify
(
ct
);
ol
.
docify
(
" "
);
ol
.
insertMemberAlign
();
QCString
name
;
if
(
localName
)
...
...
@@ -1103,3 +1083,33 @@ QCString NamespaceDef::title() const
}
return
pageTitle
;
}
QCString
NamespaceDef
::
compoundTypeString
()
const
{
SrcLangExt
lang
=
getLanguage
();
if
(
lang
==
SrcLangExt_Java
||
lang
==
SrcLangExt_CSharp
)
{
return
"package"
;
}
else
if
(
lang
==
SrcLangExt_Fortran
)
{
return
"module"
;
}
else
if
(
lang
==
SrcLangExt_IDL
)
{
if
(
isModule
())
{
return
"module"
;
}
else
if
(
isConstantGroup
())
{
return
"constants"
;
}
else
{
err
(
"Internal inconsistency: namespace in IDL not module or constant group
\n
"
);
}
}
return
""
;
}
src/namespacedef.h
View file @
744d1ca5
...
...
@@ -94,6 +94,7 @@ class NamespaceDef : public Definition
NamespaceSDict
*
getNamespaceSDict
()
const
{
return
namespaceSDict
;
}
QCString
title
()
const
;
QCString
compoundTypeString
()
const
;
bool
visited
;
...
...
src/template.cpp
View file @
744d1ca5
This diff is collapsed.
Click to expand it.
src/template.h
View file @
744d1ca5
...
...
@@ -10,32 +10,32 @@ class TemplateListIntf;
class
TemplateStructIntf
;
class
TemplateEngine
;
/** @defgroup template_api Template API
/** @defgroup template_api Template API
*
* This is the API for a
* <a href="https://docs.djangoproject.com/en/1.6/topics/templates/">Django</a>
* This is the API for a
* <a href="https://docs.djangoproject.com/en/1.6/topics/templates/">Django</a>
* compatible template system written in C++.
* It is somewhat inspired by Stephen Kelly's
* It is somewhat inspired by Stephen Kelly's
* <a href="http://www.gitorious.org/grantlee/pages/Home">Grantlee</a>.
*
* A template is simply a text file.
* A template contains \b variables, which get replaced with values when the
* A template is simply a text file.
* A template contains \b variables, which get replaced with values when the
* template is evaluated, and \b tags, which control the logic of the template.
*
* Variables look like this: `{{ variable }}`
* When the template engine encounters a variable, it evaluates that variable and
* replaces it with the result. Variable names consist of any combination of
* When the template engine encounters a variable, it evaluates that variable and
* replaces it with the result. Variable names consist of any combination of
* alphanumeric characters and the underscore ("_").
* Use a dot (.) to access attributes of a structured variable.
*
*
* One can modify variables for display by using \b filters, for example:
* `{{ value|default:"nothing" }}`
*
* Tags look like this: `{% tag %}`. Tags are more complex than variables:
* Some create text in the output, some control flow by performing loops or logic,
* Tags look like this: `{% tag %}`. Tags are more complex than variables:
* Some create text in the output, some control flow by performing loops or logic,
* and some load external information into the template to be used by later variables.
*
* To comment-out part of a line in a template, use the comment syntax:
* To comment-out part of a line in a template, use the comment syntax:
* `{# comment text #}`.
*
* Supported Django tags:
...
...
@@ -144,13 +144,13 @@ class TemplateVariant
/** Constructs a new variant with a string value \a s. */
TemplateVariant
(
const
QCString
&
s
,
bool
raw
=
FALSE
);
/** Constructs a new variant with a struct value \a s.
/** Constructs a new variant with a struct value \a s.
* @note. Only a pointer to the struct is stored. The caller
* is responsible to manage the memory for the struct object.
*/
TemplateVariant
(
const
TemplateStructIntf
*
s
);
/** Constructs a new variant with a list value \a l.
/** Constructs a new variant with a list value \a l.
* @note. Only a pointer to the struct is stored. The caller
* is responsible to manage the memory for the list object.
*/
...
...
@@ -168,7 +168,7 @@ class TemplateVariant
/** Destroys the Variant object */
~
TemplateVariant
();
/** Constructs a copy of the variant, \a v,
/** Constructs a copy of the variant, \a v,
* passed as the argument to this constructor.
*/
TemplateVariant
(
const
TemplateVariant
&
v
);
...
...
@@ -176,7 +176,7 @@ class TemplateVariant
/** Assigns the value of the variant \a v to this variant. */
TemplateVariant
&
operator
=
(
const
TemplateVariant
&
v
);
/** Compares this QVariant with v and returns true if they are equal;
/** Compares this QVariant with v and returns true if they are equal;
* otherwise returns false.
*/
bool
operator
==
(
TemplateVariant
&
other
);
...
...
@@ -190,13 +190,13 @@ class TemplateVariant
/** Returns the variant as an integer. */
int
toInt
()
const
;
/** Returns the pointer to list referenced by this variant
* or 0 if this variant does not have list type.
/** Returns the pointer to list referenced by this variant
* or 0 if this variant does not have list type.
*/
const
TemplateListIntf
*
toList
()
const
;
/** Returns the pointer to struct referenced by this variant
* or 0 if this variant does not have struct type.
/** Returns the pointer to struct referenced by this variant
* or 0 if this variant does not have struct type.
*/
const
TemplateStructIntf
*
toStruct
()
const
;
...
...
@@ -205,7 +205,7 @@ class TemplateVariant
*/
TemplateVariant
call
(
const
QValueList
<
TemplateVariant
>
&
args
);
/** Sets whether or not the value of the Variant should be
/** Sets whether or not the value of the Variant should be
* escaped or written as-is (raw).
* @param[in] b TRUE means write as-is, FALSE means apply escaping.
*/
...
...
@@ -223,7 +223,7 @@ class TemplateVariant
//------------------------------------------------------------------------
/** @brief Abstract read-only interface for a context value of type list.
/** @brief Abstract read-only interface for a context value of type list.
* @note The values of the list are TemplateVariants.
*/
class
TemplateListIntf
...
...
@@ -245,7 +245,7 @@ class TemplateListIntf
virtual
void
toPrev
()
=
0
;
/* Returns TRUE if the iterator points to a valid element
* in the list, or FALSE otherwise.
* If TRUE is returned, the value pointed to be the
* If TRUE is returned, the value pointed to be the
* iterator is assigned to \a v.
*/
virtual
bool
current
(
TemplateVariant
&
v
)
const
=
0
;
...
...
@@ -260,7 +260,7 @@ class TemplateListIntf
/** Returns the element at index position \a index. */
virtual
TemplateVariant
at
(
int
index
)
const
=
0
;
/** Creates a new iterator for this list.
/** Creates a new iterator for this list.
* @note the user should call delete on the returned pointer.
*/
virtual
TemplateListIntf
::
ConstIterator
*
createIterator
()
const
=
0
;
...
...
@@ -279,7 +279,7 @@ class TemplateList : public TemplateListIntf
virtual
int
count
()
const
;
virtual
TemplateVariant
at
(
int
index
)
const
;
virtual
TemplateListIntf
::
ConstIterator
*
createIterator
()
const
;
/** Appends element \a v to the end of the list */
virtual
void
append
(
const
TemplateVariant
&
v
);
...
...
@@ -350,8 +350,8 @@ class TemplateSpacelessIntf
//------------------------------------------------------------------------
/** @brief Abstract interface for a template context.
*
/** @brief Abstract interface for a template context.
*
* A Context consists of a stack of dictionaries.
* A dictionary consists of a mapping of string keys onto TemplateVariant values.
* A key is searched starting with the dictionary at the top of the stack
...
...
@@ -370,10 +370,10 @@ class TemplateContext
/** Pop the current scope from the stack. */
virtual
void
pop
()
=
0
;
/** Sets a value in the current scope.
/** Sets a value in the current scope.
* @param[in] name The name of the value; the key in the dictionary.
* @param[in] v The value associated with the key.
* @note When a given key is already present,
* @note When a given key is already present,
* its value will be replaced by \a v
*/
virtual
void
set
(
const
char
*
name
,
const
TemplateVariant
&
v
)
=
0
;
...
...
@@ -409,8 +409,8 @@ class TemplateContext
//------------------------------------------------------------------------
/** @brief Abstract interface for a template.
* @note Must be created
by
TemplateEngine
/** @brief Abstract interface for a template.
* @note Must be created
and is deleted by the
TemplateEngine
*/
class
Template
{
...
...
@@ -418,7 +418,7 @@ class Template
/** Destructor */
virtual
~
Template
()
{}
/** Renders a template instance to a stream.
/** Renders a template instance to a stream.
* @param[in] ts The text stream to write the results to.
* @param[in] c The context containing data that can be used
* when instantiating the template.
...
...
@@ -444,13 +444,25 @@ class TemplateEngine
TemplateContext
*
createContext
()
const
;
/** Creates a new template whole contents are in a file.
* @param[in] fileName The name of the file containing the
* @param[in] fileName The name of the file containing the
* template data
* @param[in] fromLine The line number of the statement that triggered the load
* @return the new template, the caller will be the owner.
*/
Template
*
loadByName
(
const
QCString
&
fileName
);
Template
*
loadByName
(
const
QCString
&
fileName
,
int
fromLine
);
/** Indicates that template \a t is no longer needed. The engine
* may decide to delete it.
*/
void
unload
(
Template
*
t
);
void
printIncludeContext
(
const
char
*
fileName
,
int
line
)
const
;
private
:
friend
class
TemplateNodeBlock
;
void
enterBlock
(
const
QCString
&
fileName
,
const
QCString
&
blockName
,
int
line
);
void
leaveBlock
();
class
Private
;
Private
*
p
;
};
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment