Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
P
protobuf-c
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
spbro
protobuf-c
Commits
bc2cb66d
Commit
bc2cb66d
authored
Feb 08, 2025
by
Robert Edmonds
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use `compat::StringView` type across various function signatures
parent
db5252c1
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
42 additions
and
38 deletions
+42
-38
protoc-gen-c/c_helpers.cc
protoc-gen-c/c_helpers.cc
+24
-20
protoc-gen-c/c_helpers.h
protoc-gen-c/c_helpers.h
+18
-18
No files found.
protoc-gen-c/c_helpers.cc
View file @
bc2cb66d
...
@@ -96,6 +96,7 @@ std::string SimpleFtoa(float f) {
...
@@ -96,6 +96,7 @@ std::string SimpleFtoa(float f) {
buf
[
sizeof
(
buf
)
-
1
]
=
0
;
/* should NOT be necessary */
buf
[
sizeof
(
buf
)
-
1
]
=
0
;
/* should NOT be necessary */
return
buf
;
return
buf
;
}
}
std
::
string
SimpleDtoa
(
double
d
)
{
std
::
string
SimpleDtoa
(
double
d
)
{
char
buf
[
100
];
char
buf
[
100
];
snprintf
(
buf
,
sizeof
(
buf
),
"%.*g"
,
DBL_DIG
,
d
);
snprintf
(
buf
,
sizeof
(
buf
),
"%.*g"
,
DBL_DIG
,
d
);
...
@@ -103,7 +104,7 @@ std::string SimpleDtoa(double d) {
...
@@ -103,7 +104,7 @@ std::string SimpleDtoa(double d) {
return
buf
;
return
buf
;
}
}
std
::
string
CamelToUpper
(
co
nst
std
::
string
&
name
)
{
std
::
string
CamelToUpper
(
co
mpat
::
StringView
name
)
{
bool
was_upper
=
true
;
// suppress initial _
bool
was_upper
=
true
;
// suppress initial _
std
::
string
rv
=
""
;
std
::
string
rv
=
""
;
int
len
=
name
.
length
();
int
len
=
name
.
length
();
...
@@ -120,7 +121,8 @@ std::string CamelToUpper(const std::string &name) {
...
@@ -120,7 +121,8 @@ std::string CamelToUpper(const std::string &name) {
}
}
return
rv
;
return
rv
;
}
}
std
::
string
CamelToLower
(
const
std
::
string
&
name
)
{
std
::
string
CamelToLower
(
compat
::
StringView
name
)
{
bool
was_upper
=
true
;
// suppress initial _
bool
was_upper
=
true
;
// suppress initial _
std
::
string
rv
=
""
;
std
::
string
rv
=
""
;
int
len
=
name
.
length
();
int
len
=
name
.
length
();
...
@@ -138,8 +140,7 @@ std::string CamelToLower(const std::string &name) {
...
@@ -138,8 +140,7 @@ std::string CamelToLower(const std::string &name) {
return
rv
;
return
rv
;
}
}
std
::
string
ToUpper
(
compat
::
StringView
name
)
{
std
::
string
ToUpper
(
const
std
::
string
&
name
)
{
std
::
string
rv
=
""
;
std
::
string
rv
=
""
;
int
len
=
name
.
length
();
int
len
=
name
.
length
();
for
(
int
i
=
0
;
i
<
len
;
i
++
)
{
for
(
int
i
=
0
;
i
<
len
;
i
++
)
{
...
@@ -147,7 +148,8 @@ std::string ToUpper(const std::string &name) {
...
@@ -147,7 +148,8 @@ std::string ToUpper(const std::string &name) {
}
}
return
rv
;
return
rv
;
}
}
std
::
string
ToLower
(
const
std
::
string
&
name
)
{
std
::
string
ToLower
(
compat
::
StringView
name
)
{
std
::
string
rv
=
""
;
std
::
string
rv
=
""
;
int
len
=
name
.
length
();
int
len
=
name
.
length
();
for
(
int
i
=
0
;
i
<
len
;
i
++
)
{
for
(
int
i
=
0
;
i
<
len
;
i
++
)
{
...
@@ -155,7 +157,8 @@ std::string ToLower(const std::string &name) {
...
@@ -155,7 +157,8 @@ std::string ToLower(const std::string &name) {
}
}
return
rv
;
return
rv
;
}
}
std
::
string
ToCamel
(
const
std
::
string
&
name
)
{
std
::
string
ToCamel
(
compat
::
StringView
name
)
{
std
::
string
rv
=
""
;
std
::
string
rv
=
""
;
int
len
=
name
.
length
();
int
len
=
name
.
length
();
bool
next_is_upper
=
true
;
bool
next_is_upper
=
true
;
...
@@ -172,7 +175,7 @@ std::string ToCamel(const std::string &name) {
...
@@ -172,7 +175,7 @@ std::string ToCamel(const std::string &name) {
return
rv
;
return
rv
;
}
}
std
::
string
OverrideFullName
(
co
nst
std
::
string
&
full_name
,
const
google
::
protobuf
::
FileDescriptor
*
file
)
{
std
::
string
OverrideFullName
(
co
mpat
::
StringView
full_name
,
const
google
::
protobuf
::
FileDescriptor
*
file
)
{
const
ProtobufCFileOptions
opt
=
file
->
options
().
GetExtension
(
pb_c_file
);
const
ProtobufCFileOptions
opt
=
file
->
options
().
GetExtension
(
pb_c_file
);
if
(
!
opt
.
has_c_package
())
if
(
!
opt
.
has_c_package
())
return
full_name
;
return
full_name
;
...
@@ -184,7 +187,7 @@ std::string OverrideFullName(const std::string &full_name, const google::protobu
...
@@ -184,7 +187,7 @@ std::string OverrideFullName(const std::string &full_name, const google::protobu
return
new_name
+
full_name
.
substr
(
file
->
package
().
length
());
return
new_name
+
full_name
.
substr
(
file
->
package
().
length
());
}
}
std
::
string
FullNameToLower
(
co
nst
std
::
string
&
full_name
,
const
google
::
protobuf
::
FileDescriptor
*
file
)
{
std
::
string
FullNameToLower
(
co
mpat
::
StringView
full_name
,
const
google
::
protobuf
::
FileDescriptor
*
file
)
{
std
::
vector
<
std
::
string
>
pieces
;
std
::
vector
<
std
::
string
>
pieces
;
SplitStringUsing
(
OverrideFullName
(
full_name
,
file
),
"."
,
&
pieces
);
SplitStringUsing
(
OverrideFullName
(
full_name
,
file
),
"."
,
&
pieces
);
std
::
string
rv
=
""
;
std
::
string
rv
=
""
;
...
@@ -195,7 +198,8 @@ std::string FullNameToLower(const std::string &full_name, const google::protobuf
...
@@ -195,7 +198,8 @@ std::string FullNameToLower(const std::string &full_name, const google::protobuf
}
}
return
rv
;
return
rv
;
}
}
std
::
string
FullNameToUpper
(
const
std
::
string
&
full_name
,
const
google
::
protobuf
::
FileDescriptor
*
file
)
{
std
::
string
FullNameToUpper
(
compat
::
StringView
full_name
,
const
google
::
protobuf
::
FileDescriptor
*
file
)
{
std
::
vector
<
std
::
string
>
pieces
;
std
::
vector
<
std
::
string
>
pieces
;
SplitStringUsing
(
OverrideFullName
(
full_name
,
file
),
"."
,
&
pieces
);
SplitStringUsing
(
OverrideFullName
(
full_name
,
file
),
"."
,
&
pieces
);
std
::
string
rv
=
""
;
std
::
string
rv
=
""
;
...
@@ -206,7 +210,8 @@ std::string FullNameToUpper(const std::string &full_name, const google::protobuf
...
@@ -206,7 +210,8 @@ std::string FullNameToUpper(const std::string &full_name, const google::protobuf
}
}
return
rv
;
return
rv
;
}
}
std
::
string
FullNameToC
(
const
std
::
string
&
full_name
,
const
google
::
protobuf
::
FileDescriptor
*
file
)
{
std
::
string
FullNameToC
(
compat
::
StringView
full_name
,
const
google
::
protobuf
::
FileDescriptor
*
file
)
{
std
::
vector
<
std
::
string
>
pieces
;
std
::
vector
<
std
::
string
>
pieces
;
SplitStringUsing
(
OverrideFullName
(
full_name
,
file
),
"."
,
&
pieces
);
SplitStringUsing
(
OverrideFullName
(
full_name
,
file
),
"."
,
&
pieces
);
std
::
string
rv
=
""
;
std
::
string
rv
=
""
;
...
@@ -248,7 +253,7 @@ void PrintComment(google::protobuf::io::Printer* printer, std::string comment)
...
@@ -248,7 +253,7 @@ void PrintComment(google::protobuf::io::Printer* printer, std::string comment)
}
}
}
}
std
::
string
ConvertToSpaces
(
co
nst
std
::
string
&
input
)
{
std
::
string
ConvertToSpaces
(
co
mpat
::
StringView
input
)
{
return
std
::
string
(
input
.
size
(),
' '
);
return
std
::
string
(
input
.
size
(),
' '
);
}
}
...
@@ -259,8 +264,7 @@ int compare_name_indices_by_name(const void *a, const void *b)
...
@@ -259,8 +264,7 @@ int compare_name_indices_by_name(const void *a, const void *b)
return
strcmp
(
ni_a
->
name
,
ni_b
->
name
);
return
strcmp
(
ni_a
->
name
,
ni_b
->
name
);
}
}
std
::
string
CEscape
(
compat
::
StringView
src
);
std
::
string
CEscape
(
const
std
::
string
&
src
);
const
char
*
const
kKeywordList
[]
=
{
const
char
*
const
kKeywordList
[]
=
{
"and"
,
"and_eq"
,
"asm"
,
"auto"
,
"bitand"
,
"bitor"
,
"bool"
,
"break"
,
"case"
,
"and"
,
"and_eq"
,
"asm"
,
"auto"
,
"bitand"
,
"bitor"
,
"bool"
,
"break"
,
"case"
,
...
@@ -300,7 +304,7 @@ std::string FieldDeprecated(const google::protobuf::FieldDescriptor* field) {
...
@@ -300,7 +304,7 @@ std::string FieldDeprecated(const google::protobuf::FieldDescriptor* field) {
return
""
;
return
""
;
}
}
std
::
string
StripProto
(
co
nst
std
::
string
&
filename
)
{
std
::
string
StripProto
(
co
mpat
::
StringView
filename
)
{
if
(
HasSuffixString
(
filename
,
".protodevel"
))
{
if
(
HasSuffixString
(
filename
,
".protodevel"
))
{
return
StripSuffixString
(
filename
,
".protodevel"
);
return
StripSuffixString
(
filename
,
".protodevel"
);
}
else
{
}
else
{
...
@@ -309,7 +313,7 @@ std::string StripProto(const std::string& filename) {
...
@@ -309,7 +313,7 @@ std::string StripProto(const std::string& filename) {
}
}
// Convert a file name into a valid identifier.
// Convert a file name into a valid identifier.
std
::
string
FilenameIdentifier
(
co
nst
std
::
string
&
filename
)
{
std
::
string
FilenameIdentifier
(
co
mpat
::
StringView
filename
)
{
std
::
string
result
;
std
::
string
result
;
for
(
unsigned
i
=
0
;
i
<
filename
.
size
();
i
++
)
{
for
(
unsigned
i
=
0
;
i
<
filename
.
size
();
i
++
)
{
if
(
isalnum
(
filename
[
i
]))
{
if
(
isalnum
(
filename
[
i
]))
{
...
@@ -335,7 +339,7 @@ std::string GetLabelName(google::protobuf::FieldDescriptor::Label label) {
...
@@ -335,7 +339,7 @@ std::string GetLabelName(google::protobuf::FieldDescriptor::Label label) {
}
}
unsigned
unsigned
WriteIntRanges
(
google
::
protobuf
::
io
::
Printer
*
printer
,
int
n_values
,
const
int
*
values
,
co
nst
std
::
string
&
name
)
WriteIntRanges
(
google
::
protobuf
::
io
::
Printer
*
printer
,
int
n_values
,
const
int
*
values
,
co
mpat
::
StringView
name
)
{
{
std
::
map
<
std
::
string
,
std
::
string
>
vars
;
std
::
map
<
std
::
string
,
std
::
string
>
vars
;
vars
[
"name"
]
=
name
;
vars
[
"name"
]
=
name
;
...
@@ -389,7 +393,7 @@ WriteIntRanges(google::protobuf::io::Printer* printer, int n_values, const int *
...
@@ -389,7 +393,7 @@ WriteIntRanges(google::protobuf::io::Printer* printer, int n_values, const int *
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
template
<
typename
ITR
>
template
<
typename
ITR
>
static
inline
static
inline
void
SplitStringToIteratorUsing
(
co
nst
std
::
string
&
full
,
void
SplitStringToIteratorUsing
(
co
mpat
::
StringView
full
,
const
char
*
delim
,
const
char
*
delim
,
ITR
&
result
)
{
ITR
&
result
)
{
// Optimize the common case where delim is a single character.
// Optimize the common case where delim is a single character.
...
@@ -422,7 +426,7 @@ void SplitStringToIteratorUsing(const std::string& full,
...
@@ -422,7 +426,7 @@ void SplitStringToIteratorUsing(const std::string& full,
}
}
}
}
void
SplitStringUsing
(
co
nst
std
::
string
&
full
,
void
SplitStringUsing
(
co
mpat
::
StringView
full
,
const
char
*
delim
,
const
char
*
delim
,
std
::
vector
<
std
::
string
>*
result
)
{
std
::
vector
<
std
::
string
>*
result
)
{
std
::
back_insert_iterator
<
std
::
vector
<
std
::
string
>
>
it
(
*
result
);
std
::
back_insert_iterator
<
std
::
vector
<
std
::
string
>
>
it
(
*
result
);
...
@@ -435,7 +439,6 @@ char* FastHexToBuffer(int i, char* buffer)
...
@@ -435,7 +439,6 @@ char* FastHexToBuffer(int i, char* buffer)
return
buffer
;
return
buffer
;
}
}
static
int
CEscapeInternal
(
const
char
*
src
,
int
src_len
,
char
*
dest
,
static
int
CEscapeInternal
(
const
char
*
src
,
int
src_len
,
char
*
dest
,
int
dest_len
,
bool
use_hex
)
{
int
dest_len
,
bool
use_hex
)
{
const
char
*
src_end
=
src
+
src_len
;
const
char
*
src_end
=
src
+
src_len
;
...
@@ -478,7 +481,8 @@ static int CEscapeInternal(const char* src, int src_len, char* dest,
...
@@ -478,7 +481,8 @@ static int CEscapeInternal(const char* src, int src_len, char* dest,
dest
[
used
]
=
'\0'
;
// doesn't count towards return value though
dest
[
used
]
=
'\0'
;
// doesn't count towards return value though
return
used
;
return
used
;
}
}
std
::
string
CEscape
(
const
std
::
string
&
src
)
{
std
::
string
CEscape
(
compat
::
StringView
src
)
{
const
int
dest_length
=
src
.
size
()
*
4
+
1
;
// Maximum possible expansion
const
int
dest_length
=
src
.
size
()
*
4
+
1
;
// Maximum possible expansion
std
::
unique_ptr
<
char
[]
>
dest
(
new
char
[
dest_length
]);
std
::
unique_ptr
<
char
[]
>
dest
(
new
char
[
dest_length
]);
const
int
len
=
CEscapeInternal
(
src
.
data
(),
src
.
size
(),
const
int
len
=
CEscapeInternal
(
src
.
data
(),
src
.
size
(),
...
...
protoc-gen-c/c_helpers.h
View file @
bc2cb66d
...
@@ -73,6 +73,8 @@
...
@@ -73,6 +73,8 @@
#include <protobuf-c/protobuf-c.pb.h>
#include <protobuf-c/protobuf-c.pb.h>
#include "compat.h"
namespace
protobuf_c
{
namespace
protobuf_c
{
// --- Borrowed from stubs. ---
// --- Borrowed from stubs. ---
...
@@ -84,11 +86,10 @@ template <typename T> std::string SimpleItoa(T n) {
...
@@ -84,11 +86,10 @@ template <typename T> std::string SimpleItoa(T n) {
std
::
string
SimpleFtoa
(
float
f
);
std
::
string
SimpleFtoa
(
float
f
);
std
::
string
SimpleDtoa
(
double
f
);
std
::
string
SimpleDtoa
(
double
f
);
void
SplitStringUsing
(
const
std
::
string
&
str
,
const
char
*
delim
,
std
::
vector
<
std
::
string
>
*
out
);
void
SplitStringUsing
(
compat
::
StringView
str
,
const
char
*
delim
,
std
::
vector
<
std
::
string
>
*
out
);
std
::
string
CEscape
(
const
std
::
string
&
src
);
std
::
string
CEscape
(
compat
::
StringView
src
);
std
::
string
StringReplace
(
const
std
::
string
&
s
,
const
std
::
string
&
oldsub
,
const
std
::
string
&
newsub
,
bool
replace_all
);
inline
bool
HasSuffixString
(
compat
::
StringView
str
,
compat
::
StringView
suffix
)
{
return
str
.
size
()
>=
suffix
.
size
()
&&
str
.
compare
(
str
.
size
()
-
suffix
.
size
(),
suffix
.
size
(),
suffix
)
==
0
;
}
inline
bool
HasSuffixString
(
const
std
::
string
&
str
,
const
std
::
string
&
suffix
)
{
return
str
.
size
()
>=
suffix
.
size
()
&&
str
.
compare
(
str
.
size
()
-
suffix
.
size
(),
suffix
.
size
(),
suffix
)
==
0
;
}
inline
std
::
string
StripSuffixString
(
compat
::
StringView
str
,
compat
::
StringView
suffix
)
{
if
(
HasSuffixString
(
str
,
suffix
))
{
return
str
.
substr
(
0
,
str
.
size
()
-
suffix
.
size
());
}
else
{
return
str
;
}
}
inline
std
::
string
StripSuffixString
(
const
std
::
string
&
str
,
const
std
::
string
&
suffix
)
{
if
(
HasSuffixString
(
str
,
suffix
))
{
return
str
.
substr
(
0
,
str
.
size
()
-
suffix
.
size
());
}
else
{
return
str
;
}
}
char
*
FastHexToBuffer
(
int
i
,
char
*
buffer
);
char
*
FastHexToBuffer
(
int
i
,
char
*
buffer
);
...
@@ -110,31 +111,31 @@ inline const google::protobuf::Descriptor* FieldScope(const google::protobuf::Fi
...
@@ -110,31 +111,31 @@ inline const google::protobuf::Descriptor* FieldScope(const google::protobuf::Fi
// convert a CamelCase class name into an all uppercase affair
// convert a CamelCase class name into an all uppercase affair
// with underscores separating words, e.g. MyClass becomes MY_CLASS.
// with underscores separating words, e.g. MyClass becomes MY_CLASS.
std
::
string
CamelToUpper
(
co
nst
std
::
string
&
class_name
);
std
::
string
CamelToUpper
(
co
mpat
::
StringView
class_name
);
std
::
string
CamelToLower
(
co
nst
std
::
string
&
class_name
);
std
::
string
CamelToLower
(
co
mpat
::
StringView
class_name
);
// lowercased, underscored name to camel case
// lowercased, underscored name to camel case
std
::
string
ToCamel
(
co
nst
std
::
string
&
name
);
std
::
string
ToCamel
(
co
mpat
::
StringView
name
);
// lowercase the string
// lowercase the string
std
::
string
ToLower
(
co
nst
std
::
string
&
class_name
);
std
::
string
ToLower
(
co
mpat
::
StringView
class_name
);
std
::
string
ToUpper
(
co
nst
std
::
string
&
class_name
);
std
::
string
ToUpper
(
co
mpat
::
StringView
class_name
);
// full_name() to lowercase with underscores
// full_name() to lowercase with underscores
std
::
string
FullNameToLower
(
co
nst
std
::
string
&
full_name
,
const
google
::
protobuf
::
FileDescriptor
*
file
);
std
::
string
FullNameToLower
(
co
mpat
::
StringView
full_name
,
const
google
::
protobuf
::
FileDescriptor
*
file
);
std
::
string
FullNameToUpper
(
co
nst
std
::
string
&
full_name
,
const
google
::
protobuf
::
FileDescriptor
*
file
);
std
::
string
FullNameToUpper
(
co
mpat
::
StringView
full_name
,
const
google
::
protobuf
::
FileDescriptor
*
file
);
// full_name() to c-typename (with underscores for packages, otherwise camel case)
// full_name() to c-typename (with underscores for packages, otherwise camel case)
std
::
string
FullNameToC
(
co
nst
std
::
string
&
class_name
,
const
google
::
protobuf
::
FileDescriptor
*
file
);
std
::
string
FullNameToC
(
co
mpat
::
StringView
class_name
,
const
google
::
protobuf
::
FileDescriptor
*
file
);
// Splits, indents, formats, and prints comment lines
// Splits, indents, formats, and prints comment lines
void
PrintComment
(
google
::
protobuf
::
io
::
Printer
*
printer
,
std
::
string
comment
);
void
PrintComment
(
google
::
protobuf
::
io
::
Printer
*
printer
,
std
::
string
comment
);
// make a string of spaces as long as input
// make a string of spaces as long as input
std
::
string
ConvertToSpaces
(
co
nst
std
::
string
&
input
);
std
::
string
ConvertToSpaces
(
co
mpat
::
StringView
input
);
// Strips ".proto" or ".protodevel" from the end of a filename.
// Strips ".proto" or ".protodevel" from the end of a filename.
std
::
string
StripProto
(
co
nst
std
::
string
&
filename
);
std
::
string
StripProto
(
co
mpat
::
StringView
filename
);
// Get the C++ type name for a primitive type (e.g. "double", "::google::protobuf::int32", etc.).
// Get the C++ type name for a primitive type (e.g. "double", "::google::protobuf::int32", etc.).
// Note: non-built-in type names will be qualified, meaning they will start
// Note: non-built-in type names will be qualified, meaning they will start
...
@@ -148,15 +149,14 @@ const char* PrimitiveTypeName(google::protobuf::FieldDescriptor::CppType type);
...
@@ -148,15 +149,14 @@ const char* PrimitiveTypeName(google::protobuf::FieldDescriptor::CppType type);
const
char
*
DeclaredTypeMethodName
(
google
::
protobuf
::
FieldDescriptor
::
Type
type
);
const
char
*
DeclaredTypeMethodName
(
google
::
protobuf
::
FieldDescriptor
::
Type
type
);
// Convert a file name into a valid identifier.
// Convert a file name into a valid identifier.
std
::
string
FilenameIdentifier
(
co
nst
std
::
string
&
filename
);
std
::
string
FilenameIdentifier
(
co
mpat
::
StringView
filename
);
// return 'required', 'optional', or 'repeated'
// return 'required', 'optional', or 'repeated'
std
::
string
GetLabelName
(
google
::
protobuf
::
FieldDescriptor
::
Label
label
);
std
::
string
GetLabelName
(
google
::
protobuf
::
FieldDescriptor
::
Label
label
);
// write IntRanges entries for a bunch of sorted values.
// write IntRanges entries for a bunch of sorted values.
// returns the number of ranges there are to bsearch.
// returns the number of ranges there are to bsearch.
unsigned
WriteIntRanges
(
google
::
protobuf
::
io
::
Printer
*
printer
,
int
n_values
,
const
int
*
values
,
co
nst
std
::
string
&
name
);
unsigned
WriteIntRanges
(
google
::
protobuf
::
io
::
Printer
*
printer
,
int
n_values
,
const
int
*
values
,
co
mpat
::
StringView
name
);
struct
NameIndex
struct
NameIndex
{
{
...
...
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