Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
G
Grpc
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container Registry
Model registry
Operate
Environments
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
tci-gateway-module
Grpc
Commits
59c20edc
Commit
59c20edc
authored
8 years ago
by
Jan Tattermusch
Browse files
Options
Downloads
Patches
Plain Diff
add comments from .proto file to generated C# files
parent
f128cd2c
No related branches found
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
src/compiler/csharp_generator.cc
+82
-7
82 additions, 7 deletions
src/compiler/csharp_generator.cc
with
82 additions
and
7 deletions
src/compiler/csharp_generator.cc
+
82
−
7
View file @
59c20edc
...
@@ -52,6 +52,7 @@ using grpc::protobuf::MethodDescriptor;
...
@@ -52,6 +52,7 @@ using grpc::protobuf::MethodDescriptor;
using
grpc
::
protobuf
::
io
::
Printer
;
using
grpc
::
protobuf
::
io
::
Printer
;
using
grpc
::
protobuf
::
io
::
StringOutputStream
;
using
grpc
::
protobuf
::
io
::
StringOutputStream
;
using
grpc_generator
::
MethodType
;
using
grpc_generator
::
MethodType
;
using
grpc_generator
::
GetCppComments
;
using
grpc_generator
::
GetMethodType
;
using
grpc_generator
::
GetMethodType
;
using
grpc_generator
::
METHODTYPE_NO_STREAMING
;
using
grpc_generator
::
METHODTYPE_NO_STREAMING
;
using
grpc_generator
::
METHODTYPE_CLIENT_STREAMING
;
using
grpc_generator
::
METHODTYPE_CLIENT_STREAMING
;
...
@@ -65,6 +66,56 @@ using std::vector;
...
@@ -65,6 +66,56 @@ using std::vector;
namespace
grpc_csharp_generator
{
namespace
grpc_csharp_generator
{
namespace
{
namespace
{
// This function is a massaged version of
// https://github.com/google/protobuf/blob/master/src/google/protobuf/compiler/csharp/csharp_doc_comment.cc
// Currently, we cannot easily reuse the functionality as
// google/protobuf/compiler/csharp/csharp_doc_comment.h is not a public header.
// TODO(jtattermusch): reuse the functionality from google/protobuf.
void
GenerateDocCommentBodyImpl
(
grpc
::
protobuf
::
io
::
Printer
*
printer
,
grpc
::
protobuf
::
SourceLocation
location
)
{
grpc
::
string
comments
=
location
.
leading_comments
.
empty
()
?
location
.
trailing_comments
:
location
.
leading_comments
;
if
(
comments
.
empty
())
{
return
;
}
// XML escaping... no need for apostrophes etc as the whole text is going to be a child
// node of a summary element, not part of an attribute.
comments
=
grpc_generator
::
StringReplace
(
comments
,
"&"
,
"&"
,
true
);
comments
=
grpc_generator
::
StringReplace
(
comments
,
"<"
,
"<"
,
true
);
std
::
vector
<
grpc
::
string
>
lines
;
grpc_generator
::
Split
(
comments
,
'\n'
,
&
lines
);
// TODO: We really should work out which part to put in the summary and which to put in the remarks...
// but that needs to be part of a bigger effort to understand the markdown better anyway.
printer
->
Print
(
"/// <summary>
\n
"
);
bool
last_was_empty
=
false
;
// We squash multiple blank lines down to one, and remove any trailing blank lines. We need
// to preserve the blank lines themselves, as this is relevant in the markdown.
// Note that we can't remove leading or trailing whitespace as *that's* relevant in markdown too.
// (We don't skip "just whitespace" lines, either.)
for
(
std
::
vector
<
grpc
::
string
>::
iterator
it
=
lines
.
begin
();
it
!=
lines
.
end
();
++
it
)
{
grpc
::
string
line
=
*
it
;
if
(
line
.
empty
())
{
last_was_empty
=
true
;
}
else
{
if
(
last_was_empty
)
{
printer
->
Print
(
"///
\n
"
);
}
last_was_empty
=
false
;
printer
->
Print
(
"/// $line$
\n
"
,
"line"
,
*
it
);
}
}
printer
->
Print
(
"/// </summary>
\n
"
);
}
template
<
typename
DescriptorType
>
void
GenerateDocCommentBody
(
grpc
::
protobuf
::
io
::
Printer
*
printer
,
const
DescriptorType
*
descriptor
)
{
grpc
::
protobuf
::
SourceLocation
location
;
if
(
descriptor
->
GetSourceLocation
(
&
location
))
{
GenerateDocCommentBodyImpl
(
printer
,
location
);
}
}
std
::
string
GetServiceClassName
(
const
ServiceDescriptor
*
service
)
{
std
::
string
GetServiceClassName
(
const
ServiceDescriptor
*
service
)
{
return
service
->
name
();
return
service
->
name
();
}
}
...
@@ -242,7 +293,7 @@ void GenerateStaticMethodField(Printer* out, const MethodDescriptor *method) {
...
@@ -242,7 +293,7 @@ void GenerateStaticMethodField(Printer* out, const MethodDescriptor *method) {
void
GenerateServiceDescriptorProperty
(
Printer
*
out
,
const
ServiceDescriptor
*
service
)
{
void
GenerateServiceDescriptorProperty
(
Printer
*
out
,
const
ServiceDescriptor
*
service
)
{
std
::
ostringstream
index
;
std
::
ostringstream
index
;
index
<<
service
->
index
();
index
<<
service
->
index
();
out
->
Print
(
"//
s
ervice descriptor
\n
"
);
out
->
Print
(
"//
/ <summary>S
ervice descriptor
</summary>
\n
"
);
out
->
Print
(
"public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor
\n
"
);
out
->
Print
(
"public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor
\n
"
);
out
->
Print
(
"{
\n
"
);
out
->
Print
(
"{
\n
"
);
out
->
Print
(
" get { return $umbrella$.Descriptor.Services[$index$]; }
\n
"
,
out
->
Print
(
" get { return $umbrella$.Descriptor.Services[$index$]; }
\n
"
,
...
@@ -253,7 +304,8 @@ void GenerateServiceDescriptorProperty(Printer* out, const ServiceDescriptor *se
...
@@ -253,7 +304,8 @@ void GenerateServiceDescriptorProperty(Printer* out, const ServiceDescriptor *se
}
}
void
GenerateClientInterface
(
Printer
*
out
,
const
ServiceDescriptor
*
service
)
{
void
GenerateClientInterface
(
Printer
*
out
,
const
ServiceDescriptor
*
service
)
{
out
->
Print
(
"// client interface
\n
"
);
out
->
Print
(
"/// <summary>Client for $servicename$</summary>
\n
"
,
"servicename"
,
GetServiceClassName
(
service
));
out
->
Print
(
"[System.Obsolete(
\"
Client side interfaced will be removed "
out
->
Print
(
"[System.Obsolete(
\"
Client side interfaced will be removed "
"in the next release. Use client class directly.
\"
)]
\n
"
);
"in the next release. Use client class directly.
\"
)]
\n
"
);
out
->
Print
(
"public interface $name$
\n
"
,
"name"
,
out
->
Print
(
"public interface $name$
\n
"
,
"name"
,
...
@@ -266,6 +318,7 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
...
@@ -266,6 +318,7 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
if
(
method_type
==
METHODTYPE_NO_STREAMING
)
{
if
(
method_type
==
METHODTYPE_NO_STREAMING
)
{
// unary calls have an extra synchronous stub method
// unary calls have an extra synchronous stub method
GenerateDocCommentBody
(
out
,
method
);
out
->
Print
(
out
->
Print
(
"$response$ $methodname$($request$ request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
\n
"
,
"$response$ $methodname$($request$ request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
\n
"
,
"methodname"
,
method
->
name
(),
"request"
,
"methodname"
,
method
->
name
(),
"request"
,
...
@@ -273,6 +326,7 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
...
@@ -273,6 +326,7 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
GetClassName
(
method
->
output_type
()));
GetClassName
(
method
->
output_type
()));
// overload taking CallOptions as a param
// overload taking CallOptions as a param
GenerateDocCommentBody
(
out
,
method
);
out
->
Print
(
out
->
Print
(
"$response$ $methodname$($request$ request, CallOptions options);
\n
"
,
"$response$ $methodname$($request$ request, CallOptions options);
\n
"
,
"methodname"
,
method
->
name
(),
"request"
,
"methodname"
,
method
->
name
(),
"request"
,
...
@@ -284,6 +338,7 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
...
@@ -284,6 +338,7 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
if
(
method_type
==
METHODTYPE_NO_STREAMING
)
{
if
(
method_type
==
METHODTYPE_NO_STREAMING
)
{
method_name
+=
"Async"
;
// prevent name clash with synchronous method.
method_name
+=
"Async"
;
// prevent name clash with synchronous method.
}
}
GenerateDocCommentBody
(
out
,
method
);
out
->
Print
(
out
->
Print
(
"$returntype$ $methodname$($request_maybe$Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
\n
"
,
"$returntype$ $methodname$($request_maybe$Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken));
\n
"
,
"methodname"
,
method_name
,
"request_maybe"
,
"methodname"
,
method_name
,
"request_maybe"
,
...
@@ -291,6 +346,7 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
...
@@ -291,6 +346,7 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
GetMethodReturnTypeClient
(
method
));
GetMethodReturnTypeClient
(
method
));
// overload taking CallOptions as a param
// overload taking CallOptions as a param
GenerateDocCommentBody
(
out
,
method
);
out
->
Print
(
out
->
Print
(
"$returntype$ $methodname$($request_maybe$CallOptions options);
\n
"
,
"$returntype$ $methodname$($request_maybe$CallOptions options);
\n
"
,
"methodname"
,
method_name
,
"request_maybe"
,
"methodname"
,
method_name
,
"request_maybe"
,
...
@@ -303,7 +359,8 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
...
@@ -303,7 +359,8 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
}
}
void
GenerateServerInterface
(
Printer
*
out
,
const
ServiceDescriptor
*
service
)
{
void
GenerateServerInterface
(
Printer
*
out
,
const
ServiceDescriptor
*
service
)
{
out
->
Print
(
"// server-side interface
\n
"
);
out
->
Print
(
"/// <summary>Interface of server-side implementations of $servicename$</summary>
\n
"
,
"servicename"
,
GetServiceClassName
(
service
));
out
->
Print
(
"[System.Obsolete(
\"
Service implementations should inherit"
out
->
Print
(
"[System.Obsolete(
\"
Service implementations should inherit"
" from the generated abstract base class instead.
\"
)]
\n
"
);
" from the generated abstract base class instead.
\"
)]
\n
"
);
out
->
Print
(
"public interface $name$
\n
"
,
"name"
,
out
->
Print
(
"public interface $name$
\n
"
,
"name"
,
...
@@ -312,6 +369,7 @@ void GenerateServerInterface(Printer* out, const ServiceDescriptor *service) {
...
@@ -312,6 +369,7 @@ void GenerateServerInterface(Printer* out, const ServiceDescriptor *service) {
out
->
Indent
();
out
->
Indent
();
for
(
int
i
=
0
;
i
<
service
->
method_count
();
i
++
)
{
for
(
int
i
=
0
;
i
<
service
->
method_count
();
i
++
)
{
const
MethodDescriptor
*
method
=
service
->
method
(
i
);
const
MethodDescriptor
*
method
=
service
->
method
(
i
);
GenerateDocCommentBody
(
out
,
method
);
out
->
Print
(
out
->
Print
(
"$returntype$ $methodname$($request$$response_stream_maybe$, "
"$returntype$ $methodname$($request$$response_stream_maybe$, "
"ServerCallContext context);
\n
"
,
"ServerCallContext context);
\n
"
,
...
@@ -326,13 +384,15 @@ void GenerateServerInterface(Printer* out, const ServiceDescriptor *service) {
...
@@ -326,13 +384,15 @@ void GenerateServerInterface(Printer* out, const ServiceDescriptor *service) {
}
}
void
GenerateServerClass
(
Printer
*
out
,
const
ServiceDescriptor
*
service
)
{
void
GenerateServerClass
(
Printer
*
out
,
const
ServiceDescriptor
*
service
)
{
out
->
Print
(
"// server-side abstract class
\n
"
);
out
->
Print
(
"/// <summary>Base class for server-side implementations of $servicename$</summary>
\n
"
,
"servicename"
,
GetServiceClassName
(
service
));
out
->
Print
(
"public abstract class $name$
\n
"
,
"name"
,
out
->
Print
(
"public abstract class $name$
\n
"
,
"name"
,
GetServerClassName
(
service
));
GetServerClassName
(
service
));
out
->
Print
(
"{
\n
"
);
out
->
Print
(
"{
\n
"
);
out
->
Indent
();
out
->
Indent
();
for
(
int
i
=
0
;
i
<
service
->
method_count
();
i
++
)
{
for
(
int
i
=
0
;
i
<
service
->
method_count
();
i
++
)
{
const
MethodDescriptor
*
method
=
service
->
method
(
i
);
const
MethodDescriptor
*
method
=
service
->
method
(
i
);
GenerateDocCommentBody
(
out
,
method
);
out
->
Print
(
out
->
Print
(
"public virtual $returntype$ $methodname$($request$$response_stream_maybe$, "
"public virtual $returntype$ $methodname$($request$$response_stream_maybe$, "
"ServerCallContext context)
\n
"
,
"ServerCallContext context)
\n
"
,
...
@@ -353,7 +413,8 @@ void GenerateServerClass(Printer* out, const ServiceDescriptor *service) {
...
@@ -353,7 +413,8 @@ void GenerateServerClass(Printer* out, const ServiceDescriptor *service) {
}
}
void
GenerateClientStub
(
Printer
*
out
,
const
ServiceDescriptor
*
service
)
{
void
GenerateClientStub
(
Printer
*
out
,
const
ServiceDescriptor
*
service
)
{
out
->
Print
(
"// client stub
\n
"
);
out
->
Print
(
"/// <summary>Client for $servicename$</summary>
\n
"
,
"servicename"
,
GetServiceClassName
(
service
));
out
->
Print
(
"#pragma warning disable 0618
\n
"
);
out
->
Print
(
"#pragma warning disable 0618
\n
"
);
out
->
Print
(
out
->
Print
(
"public class $name$ : ClientBase<$name$>, $interface$
\n
"
,
"public class $name$ : ClientBase<$name$>, $interface$
\n
"
,
...
@@ -392,6 +453,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
...
@@ -392,6 +453,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
if
(
method_type
==
METHODTYPE_NO_STREAMING
)
{
if
(
method_type
==
METHODTYPE_NO_STREAMING
)
{
// unary calls have an extra synchronous stub method
// unary calls have an extra synchronous stub method
GenerateDocCommentBody
(
out
,
method
);
out
->
Print
(
"public virtual $response$ $methodname$($request$ request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
\n
"
,
out
->
Print
(
"public virtual $response$ $methodname$($request$ request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
\n
"
,
"methodname"
,
method
->
name
(),
"request"
,
"methodname"
,
method
->
name
(),
"request"
,
GetClassName
(
method
->
input_type
()),
"response"
,
GetClassName
(
method
->
input_type
()),
"response"
,
...
@@ -404,6 +466,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
...
@@ -404,6 +466,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
out
->
Print
(
"}
\n
"
);
out
->
Print
(
"}
\n
"
);
// overload taking CallOptions as a param
// overload taking CallOptions as a param
GenerateDocCommentBody
(
out
,
method
);
out
->
Print
(
"public virtual $response$ $methodname$($request$ request, CallOptions options)
\n
"
,
out
->
Print
(
"public virtual $response$ $methodname$($request$ request, CallOptions options)
\n
"
,
"methodname"
,
method
->
name
(),
"request"
,
"methodname"
,
method
->
name
(),
"request"
,
GetClassName
(
method
->
input_type
()),
"response"
,
GetClassName
(
method
->
input_type
()),
"response"
,
...
@@ -420,6 +483,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
...
@@ -420,6 +483,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
if
(
method_type
==
METHODTYPE_NO_STREAMING
)
{
if
(
method_type
==
METHODTYPE_NO_STREAMING
)
{
method_name
+=
"Async"
;
// prevent name clash with synchronous method.
method_name
+=
"Async"
;
// prevent name clash with synchronous method.
}
}
GenerateDocCommentBody
(
out
,
method
);
out
->
Print
(
out
->
Print
(
"public virtual $returntype$ $methodname$($request_maybe$Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
\n
"
,
"public virtual $returntype$ $methodname$($request_maybe$Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
\n
"
,
"methodname"
,
method_name
,
"request_maybe"
,
"methodname"
,
method_name
,
"request_maybe"
,
...
@@ -435,6 +499,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
...
@@ -435,6 +499,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
out
->
Print
(
"}
\n
"
);
out
->
Print
(
"}
\n
"
);
// overload taking CallOptions as a param
// overload taking CallOptions as a param
GenerateDocCommentBody
(
out
,
method
);
out
->
Print
(
out
->
Print
(
"public virtual $returntype$ $methodname$($request_maybe$CallOptions options)
\n
"
,
"public virtual $returntype$ $methodname$($request_maybe$CallOptions options)
\n
"
,
"methodname"
,
method_name
,
"request_maybe"
,
"methodname"
,
method_name
,
"request_maybe"
,
...
@@ -485,7 +550,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
...
@@ -485,7 +550,7 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
void
GenerateBindServiceMethod
(
Printer
*
out
,
const
ServiceDescriptor
*
service
,
void
GenerateBindServiceMethod
(
Printer
*
out
,
const
ServiceDescriptor
*
service
,
bool
use_server_class
)
{
bool
use_server_class
)
{
out
->
Print
(
out
->
Print
(
"//
c
reates service definition that can be registered with a server
\n
"
);
"//
/ <summary>C
reates service definition that can be registered with a server
</summary>
\n
"
);
out
->
Print
(
"#pragma warning disable 0618
\n
"
);
out
->
Print
(
"#pragma warning disable 0618
\n
"
);
out
->
Print
(
out
->
Print
(
"public static ServerServiceDefinition BindService($interface$ serviceImpl)
\n
"
,
"public static ServerServiceDefinition BindService($interface$ serviceImpl)
\n
"
,
...
@@ -519,7 +584,8 @@ void GenerateBindServiceMethod(Printer* out, const ServiceDescriptor *service,
...
@@ -519,7 +584,8 @@ void GenerateBindServiceMethod(Printer* out, const ServiceDescriptor *service,
}
}
void
GenerateNewStubMethods
(
Printer
*
out
,
const
ServiceDescriptor
*
service
)
{
void
GenerateNewStubMethods
(
Printer
*
out
,
const
ServiceDescriptor
*
service
)
{
out
->
Print
(
"// creates a new client
\n
"
);
out
->
Print
(
"/// <summary>Creates a new client for $servicename$</summary>
\n
"
,
"servicename"
,
GetServiceClassName
(
service
));
out
->
Print
(
"public static $classname$ NewClient(Channel channel)
\n
"
,
out
->
Print
(
"public static $classname$ NewClient(Channel channel)
\n
"
,
"classname"
,
GetClientClassName
(
service
));
"classname"
,
GetClientClassName
(
service
));
out
->
Print
(
"{
\n
"
);
out
->
Print
(
"{
\n
"
);
...
@@ -534,6 +600,7 @@ void GenerateNewStubMethods(Printer* out, const ServiceDescriptor *service) {
...
@@ -534,6 +600,7 @@ void GenerateNewStubMethods(Printer* out, const ServiceDescriptor *service) {
void
GenerateService
(
Printer
*
out
,
const
ServiceDescriptor
*
service
,
void
GenerateService
(
Printer
*
out
,
const
ServiceDescriptor
*
service
,
bool
generate_client
,
bool
generate_server
,
bool
generate_client
,
bool
generate_server
,
bool
internal_access
)
{
bool
internal_access
)
{
GenerateDocCommentBody
(
out
,
service
);
out
->
Print
(
"$access_level$ static class $classname$
\n
"
,
"access_level"
,
out
->
Print
(
"$access_level$ static class $classname$
\n
"
,
"access_level"
,
GetAccessLevel
(
internal_access
),
"classname"
,
GetAccessLevel
(
internal_access
),
"classname"
,
GetServiceClassName
(
service
));
GetServiceClassName
(
service
));
...
@@ -590,6 +657,14 @@ grpc::string GetServices(const FileDescriptor *file, bool generate_client,
...
@@ -590,6 +657,14 @@ grpc::string GetServices(const FileDescriptor *file, bool generate_client,
// Write out a file header.
// Write out a file header.
out
.
Print
(
"// Generated by the protocol buffer compiler. DO NOT EDIT!
\n
"
);
out
.
Print
(
"// Generated by the protocol buffer compiler. DO NOT EDIT!
\n
"
);
out
.
Print
(
"// source: $filename$
\n
"
,
"filename"
,
file
->
name
());
out
.
Print
(
"// source: $filename$
\n
"
,
"filename"
,
file
->
name
());
// use C++ style as there are no file-level XML comments in .NET
grpc
::
string
leading_comments
=
GetCppComments
(
file
,
true
);
if
(
!
leading_comments
.
empty
())
{
out
.
Print
(
"// Original file comments:
\n
"
);
out
.
Print
(
leading_comments
.
c_str
());
}
out
.
Print
(
"#region Designer generated code
\n
"
);
out
.
Print
(
"#region Designer generated code
\n
"
);
out
.
Print
(
"
\n
"
);
out
.
Print
(
"
\n
"
);
out
.
Print
(
"using System;
\n
"
);
out
.
Print
(
"using System;
\n
"
);
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment