Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
nexedi
cython
Commits
d7322d2f
Commit
d7322d2f
authored
Aug 03, 2011
by
Mark Florisson
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
object -> struct conversion
parent
5197f5cf
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
167 additions
and
34 deletions
+167
-34
Cython/Compiler/Code.py
Cython/Compiler/Code.py
+4
-0
Cython/Compiler/MemoryView.py
Cython/Compiler/MemoryView.py
+1
-1
Cython/Compiler/PyrexTypes.py
Cython/Compiler/PyrexTypes.py
+61
-15
Cython/Utility/MemoryView_C.c
Cython/Utility/MemoryView_C.c
+9
-0
Cython/Utility/TypeConversion.c
Cython/Utility/TypeConversion.c
+36
-0
tests/run/memoryview.pyx
tests/run/memoryview.pyx
+10
-18
tests/run/struct_conversion.pyx
tests/run/struct_conversion.pyx
+46
-0
No files found.
Cython/Compiler/Code.py
View file @
d7322d2f
...
@@ -1598,9 +1598,13 @@ class CCodeWriter(object):
...
@@ -1598,9 +1598,13 @@ class CCodeWriter(object):
self
.
put_var_xdecref_clear
(
entry
)
self
.
put_var_xdecref_clear
(
entry
)
def
put_incref_memoryviewslice
(
self
,
slice_cname
,
have_gil
=
False
):
def
put_incref_memoryviewslice
(
self
,
slice_cname
,
have_gil
=
False
):
import
MemoryView
self
.
globalstate
.
use_utility_code
(
MemoryView
.
memviewslice_init_code
)
self
.
putln
(
"__PYX_INC_MEMVIEW(&%s, %d);"
%
(
slice_cname
,
int
(
have_gil
)))
self
.
putln
(
"__PYX_INC_MEMVIEW(&%s, %d);"
%
(
slice_cname
,
int
(
have_gil
)))
def
put_xdecref_memoryviewslice
(
self
,
slice_cname
,
have_gil
=
False
):
def
put_xdecref_memoryviewslice
(
self
,
slice_cname
,
have_gil
=
False
):
import
MemoryView
self
.
globalstate
.
use_utility_code
(
MemoryView
.
memviewslice_init_code
)
self
.
putln
(
"__PYX_XDEC_MEMVIEW(&%s, %d);"
%
(
slice_cname
,
int
(
have_gil
)))
self
.
putln
(
"__PYX_XDEC_MEMVIEW(&%s, %d);"
%
(
slice_cname
,
int
(
have_gil
)))
def
put_xgiveref_memoryviewslice
(
self
,
slice_cname
):
def
put_xgiveref_memoryviewslice
(
self
,
slice_cname
):
...
...
Cython/Compiler/MemoryView.py
View file @
d7322d2f
...
@@ -778,7 +778,7 @@ memviewslice_declare_code = load_memview_c_utility(
...
@@ -778,7 +778,7 @@ memviewslice_declare_code = load_memview_c_utility(
memviewslice_init_code
=
load_memview_c_utility
(
memviewslice_init_code
=
load_memview_c_utility
(
"MemviewSliceInit"
,
"MemviewSliceInit"
,
context
=
dict
(
context
,
BUF_MAX_NDIMS
=
Options
.
buffer_max_dims
),
context
=
dict
(
context
,
BUF_MAX_NDIMS
=
Options
.
buffer_max_dims
),
requires
=
[
memviewslice_declare_code
],
requires
=
[
memviewslice_declare_code
,
Buffer
.
acquire_utility_code
],
)
)
memviewslice_index_helpers
=
load_memview_c_utility
(
"MemviewSliceIndex"
)
memviewslice_index_helpers
=
load_memview_c_utility
(
"MemviewSliceIndex"
)
\ No newline at end of file
Cython/Compiler/PyrexTypes.py
View file @
d7322d2f
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
# Cython/Python language types
# Cython/Python language types
#
#
from
Code
import
UtilityCode
,
LazyUtilityCode
from
Code
import
UtilityCode
,
LazyUtilityCode
,
ContentHashingUtilityCode
import
StringEncoding
import
StringEncoding
import
Naming
import
Naming
import
copy
import
copy
...
@@ -518,7 +518,7 @@ class MemoryViewSliceType(PyrexType):
...
@@ -518,7 +518,7 @@ class MemoryViewSliceType(PyrexType):
def
lazy_utility_callback
(
code
):
def
lazy_utility_callback
(
code
):
context
[
'dtype_typeinfo'
]
=
Buffer
.
get_type_information_cname
(
context
[
'dtype_typeinfo'
]
=
Buffer
.
get_type_information_cname
(
code
,
self
.
dtype
)
code
,
self
.
dtype
)
return
Co
de
.
Co
ntentHashingUtilityCode
.
load
(
return
ContentHashingUtilityCode
.
load
(
"ObjectToMemviewSlice"
,
"MemoryView_C.c"
,
context
)
"ObjectToMemviewSlice"
,
"MemoryView_C.c"
,
context
)
env
.
use_utility_code
(
Buffer
.
acquire_utility_code
)
env
.
use_utility_code
(
Buffer
.
acquire_utility_code
)
...
@@ -576,11 +576,17 @@ class MemoryViewSliceType(PyrexType):
...
@@ -576,11 +576,17 @@ class MemoryViewSliceType(PyrexType):
if
self
.
dtype
.
is_pyobject
:
if
self
.
dtype
.
is_pyobject
:
utility_name
=
"MemviewObjectToObject"
utility_name
=
"MemviewObjectToObject"
else
:
else
:
if
not
(
self
.
dtype
.
create_to_py_utility_code
(
env
)
and
to_py
=
self
.
dtype
.
create_to_py_utility_code
(
env
)
self
.
dtype
.
create_from_py_utility_code
(
env
)):
from_py
=
self
.
dtype
.
create_from_py_utility_code
(
env
)
print
"cannot convert %s"
%
self
.
dtype
if
not
(
to_py
or
from_py
):
return
"NULL"
,
"NULL"
return
"NULL"
,
"NULL"
if
not
self
.
dtype
.
to_py_function
:
get_function
=
"NULL"
if
not
self
.
dtype
.
from_py_function
:
set_function
=
"NULL"
utility_name
=
"MemviewDtypeToObject"
utility_name
=
"MemviewDtypeToObject"
error_condition
=
(
self
.
dtype
.
error_condition
(
'value'
)
or
error_condition
=
(
self
.
dtype
.
error_condition
(
'value'
)
or
'PyErr_Occurred()'
)
'PyErr_Occurred()'
)
...
@@ -591,7 +597,7 @@ class MemoryViewSliceType(PyrexType):
...
@@ -591,7 +597,7 @@ class MemoryViewSliceType(PyrexType):
error_condition
=
error_condition
,
error_condition
=
error_condition
,
)
)
utility
=
Co
de
.
Co
ntentHashingUtilityCode
.
load
(
utility
=
ContentHashingUtilityCode
.
load
(
utility_name
,
"MemoryView_C.c"
,
context
=
context
)
utility_name
,
"MemoryView_C.c"
,
context
=
context
)
env
.
use_utility_code
(
utility
)
env
.
use_utility_code
(
utility
)
return
get_function
,
set_function
return
get_function
,
set_function
...
@@ -2329,17 +2335,19 @@ class CFuncTypeArg(object):
...
@@ -2329,17 +2335,19 @@ class CFuncTypeArg(object):
def
specialize
(
self
,
values
):
def
specialize
(
self
,
values
):
return
CFuncTypeArg
(
self
.
name
,
self
.
type
.
specialize
(
values
),
self
.
pos
,
self
.
cname
)
return
CFuncTypeArg
(
self
.
name
,
self
.
type
.
specialize
(
values
),
self
.
pos
,
self
.
cname
)
class
StructUtilityCode
(
object
):
class
ToPy
StructUtilityCode
(
object
):
requires
=
None
requires
=
None
def
__init__
(
self
,
type
,
forward_decl
):
def
__init__
(
self
,
type
,
forward_decl
):
self
.
type
=
type
self
.
type
=
type
self
.
header
=
"static PyObject* %s(%s)"
%
(
type
.
to_py_function
,
type
.
declaration_code
(
's'
))
self
.
header
=
"static PyObject* %s(%s)"
%
(
type
.
to_py_function
,
type
.
declaration_code
(
's'
))
self
.
forward_decl
=
forward_decl
self
.
forward_decl
=
forward_decl
def
__eq__
(
self
,
other
):
def
__eq__
(
self
,
other
):
return
isinstance
(
other
,
StructUtilityCode
)
and
self
.
header
==
other
.
header
return
isinstance
(
other
,
ToPyStructUtilityCode
)
and
self
.
header
==
other
.
header
def
__hash__
(
self
):
def
__hash__
(
self
):
return
hash
(
self
.
header
)
return
hash
(
self
.
header
)
...
@@ -2389,6 +2397,7 @@ class CStructOrUnionType(CType):
...
@@ -2389,6 +2397,7 @@ class CStructOrUnionType(CType):
is_struct_or_union
=
1
is_struct_or_union
=
1
has_attributes
=
1
has_attributes
=
1
exception_check
=
True
def
__init__
(
self
,
name
,
kind
,
scope
,
typedef_flag
,
cname
,
packed
=
False
):
def
__init__
(
self
,
name
,
kind
,
scope
,
typedef_flag
,
cname
,
packed
=
False
):
self
.
name
=
name
self
.
name
=
name
...
@@ -2399,26 +2408,63 @@ class CStructOrUnionType(CType):
...
@@ -2399,26 +2408,63 @@ class CStructOrUnionType(CType):
self
.
is_struct
=
kind
==
'struct'
self
.
is_struct
=
kind
==
'struct'
if
self
.
is_struct
:
if
self
.
is_struct
:
self
.
to_py_function
=
"%s_to_py_%s"
%
(
Naming
.
convert_func_prefix
,
self
.
cname
)
self
.
to_py_function
=
"%s_to_py_%s"
%
(
Naming
.
convert_func_prefix
,
self
.
cname
)
self
.
from_py_function
=
"%s_from_py_%s"
%
(
Naming
.
convert_func_prefix
,
self
.
cname
)
self
.
exception_check
=
True
self
.
exception_check
=
True
self
.
_convert_code
=
None
self
.
_convert_to_py_code
=
None
self
.
_convert_from_py_code
=
None
self
.
packed
=
packed
self
.
packed
=
packed
def
create_to_py_utility_code
(
self
,
env
):
def
create_to_py_utility_code
(
self
,
env
):
if
env
.
outer_scope
is
None
:
if
env
.
outer_scope
is
None
:
return
False
return
False
if
self
.
_convert_code
is
False
:
return
# tri-state-ish
if
self
.
_convert_
to_py_
code
is
False
:
return
# tri-state-ish
if
self
.
_convert_code
is
None
:
if
self
.
_convert_
to_py_
code
is
None
:
for
member
in
self
.
scope
.
var_entries
:
for
member
in
self
.
scope
.
var_entries
:
if
not
member
.
type
.
to_py_function
or
not
member
.
type
.
create_to_py_utility_code
(
env
):
if
not
member
.
type
.
to_py_function
or
not
member
.
type
.
create_to_py_utility_code
(
env
):
self
.
to_py_function
=
None
self
.
to_py_function
=
None
self
.
_convert_code
=
False
self
.
_convert_to_py_code
=
False
return
False
forward_decl
=
(
self
.
entry
.
visibility
!=
'extern'
)
self
.
_convert_to_py_code
=
ToPyStructUtilityCode
(
self
,
forward_decl
)
env
.
use_utility_code
(
self
.
_convert_to_py_code
)
return
True
def
create_from_py_utility_code
(
self
,
env
):
if
env
.
outer_scope
is
None
:
return
False
if
self
.
_convert_from_py_code
is
False
:
return
# tri-state-ish
if
self
.
_convert_from_py_code
is
None
:
for
member
in
self
.
scope
.
var_entries
:
if
(
not
member
.
type
.
from_py_function
or
not
member
.
type
.
create_from_py_utility_code
(
env
)):
self
.
from_py_function
=
None
self
.
_convert_from_py_code
=
False
return
False
return
False
forward_decl
=
(
self
.
entry
.
visibility
!=
'extern'
)
forward_decl
=
(
self
.
entry
.
visibility
!=
'extern'
)
self
.
_convert_code
=
StructUtilityCode
(
self
,
forward_decl
)
env
.
use_utility_code
(
self
.
_convert_code
)
# Avoid C compiler warnings
nesting_depth
=
0
type
=
self
while
type
.
is_struct_or_union
:
type
=
type
.
scope
.
var_entries
[
0
].
type
nesting_depth
+=
1
context
=
dict
(
struct_type_decl
=
self
.
declaration_code
(
""
),
var_entries
=
self
.
scope
.
var_entries
,
funcname
=
self
.
from_py_function
,
init
=
'%s 0 %s'
%
(
'{'
*
nesting_depth
,
'}'
*
nesting_depth
)
)
self
.
_convert_from_py_code
=
ContentHashingUtilityCode
.
load
(
"FromPyStructUtility"
,
"TypeConversion.c"
,
context
)
env
.
use_utility_code
(
self
.
_convert_from_py_code
)
return
True
return
True
def
__repr__
(
self
):
def
__repr__
(
self
):
...
...
Cython/Utility/MemoryView_C.c
View file @
d7322d2f
...
@@ -421,16 +421,24 @@ static CYTHON_INLINE char *__pyx_memviewslice_index_full(const char *bufp, Py_ss
...
@@ -421,16 +421,24 @@ static CYTHON_INLINE char *__pyx_memviewslice_index_full(const char *bufp, Py_ss
}
}
/////////////// MemviewDtypeToObject.proto ///////////////
/////////////// MemviewDtypeToObject.proto ///////////////
{{
if
to_py_function
}}
PyObject
*
{{
get_function
}}(
const
char
*
itemp
);
/* proto */
PyObject
*
{{
get_function
}}(
const
char
*
itemp
);
/* proto */
{{
endif
}}
{{
if
from_py_function
}}
int
{{
set_function
}}(
const
char
*
itemp
,
PyObject
*
obj
);
/* proto */
int
{{
set_function
}}(
const
char
*
itemp
,
PyObject
*
obj
);
/* proto */
{{
endif
}}
/////////////// MemviewDtypeToObject ///////////////
/////////////// MemviewDtypeToObject ///////////////
{{
#
__pyx_memview_
<
dtype_name
>
_to_object
}}
{{
#
__pyx_memview_
<
dtype_name
>
_to_object
}}
{{
if
to_py_function
}}
PyObject
*
{{
get_function
}}(
const
char
*
itemp
)
{
PyObject
*
{{
get_function
}}(
const
char
*
itemp
)
{
return
(
PyObject
*
)
{{
to_py_function
}}(
*
({{
dtype
}}
*
)
itemp
);
return
(
PyObject
*
)
{{
to_py_function
}}(
*
({{
dtype
}}
*
)
itemp
);
}
}
{{
endif
}}
{{
if
from_py_function
}}
int
{{
set_function
}}(
const
char
*
itemp
,
PyObject
*
obj
)
{
int
{{
set_function
}}(
const
char
*
itemp
,
PyObject
*
obj
)
{
{{
dtype
}}
value
=
{{
from_py_function
}}(
obj
);
{{
dtype
}}
value
=
{{
from_py_function
}}(
obj
);
if
({{
error_condition
}})
if
({{
error_condition
}})
...
@@ -438,6 +446,7 @@ int {{set_function}}(const char *itemp, PyObject *obj) {
...
@@ -438,6 +446,7 @@ int {{set_function}}(const char *itemp, PyObject *obj) {
*
({{
dtype
}}
*
)
itemp
=
value
;
*
({{
dtype
}}
*
)
itemp
=
value
;
return
1
;
return
1
;
}
}
{{
endif
}}
/////////////// MemviewObjectToObject.proto ///////////////
/////////////// MemviewObjectToObject.proto ///////////////
PyObject
*
{{
get_function
}}(
const
char
*
itemp
);
/* proto */
PyObject
*
{{
get_function
}}(
const
char
*
itemp
);
/* proto */
...
...
Cython/Utility/TypeConversion.c
0 → 100644
View file @
d7322d2f
/////////////// FromPyStructUtility.proto ///////////////
{{
struct_type_decl
}};
static
{{
struct_type_decl
}}
{{
funcname
}}(
PyObject
*
);
/////////////// FromPyStructUtility ///////////////
static
{{
struct_type_decl
}}
{{
funcname
}}(
PyObject
*
o
)
{
{{
struct_type_decl
}}
result
=
{{
init
}};
PyObject
*
value
=
NULL
;
if
(
!
PyMapping_Check
(
o
))
{
PyErr_Format
(
PyExc_TypeError
,
"Expected a mapping, not %s"
,
o
->
ob_type
->
tp_name
);
goto
bad
;
}
{{
for
member
in
var_entries
:
}}
{{
py
:
attr
=
"result."
+
member
.
cname
}}
value
=
PyMapping_GetItemString
(
o
,
"{{member.name}}"
);
if
(
!
value
)
{
PyErr_SetString
(
PyExc_ValueError
,
"No value specified for struct "
"attribute '{{member.name}}'"
);
goto
bad
;
}
{{
attr
}}
=
{{
member
.
type
.
from_py_function
}}(
value
);
if
({{
member
.
type
.
error_condition
(
attr
)}})
goto
bad
;
Py_DECREF
(
value
);
{{
endfor
}}
return
result
;
bad:
Py_XDECREF
(
value
);
return
result
;
}
tests/run/memoryview.pyx
View file @
d7322d2f
...
@@ -127,18 +127,18 @@ def test_cdef_attribute():
...
@@ -127,18 +127,18 @@ def test_cdef_attribute():
print
ExtClass
().
mview
print
ExtClass
().
mview
'''
def
basic_struct
(
MyStruct
[:]
mslice
):
def
basic_struct
(
MyStruct
[:]
mslice
):
"""
"""
See also buffmt.pyx
See also buffmt.pyx
>>> basic_struct(MyStructMockBuffer(None, [(1, 2, 3, 4, 5)]))
>>> basic_struct(MyStructMockBuffer(None, [(1, 2, 3, 4, 5)]))
1 2 3 4 5
[('a', 1), ('b', 2), ('c', 3L), ('d', 4), ('e', 5)]
>>> basic_struct(MyStructMockBuffer(None, [(1, 2, 3, 4, 5)], format="bbqii"))
>>> basic_struct(MyStructMockBuffer(None, [(1, 2, 3, 4, 5)], format="bbqii"))
1 2 3 4 5
[('a', 1), ('b', 2), ('c', 3L), ('d', 4), ('e', 5)]
"""
"""
buf
=
mslice
buf
=
mslice
print
buf[0].a, buf[0].b, buf[0].c, buf[0].d, buf[0].e
print
sorted
(
buf
[
0
].
items
())
def
nested_struct
(
NestedStruct
[:]
mslice
):
def
nested_struct
(
NestedStruct
[:]
mslice
):
"""
"""
...
@@ -150,7 +150,8 @@ def nested_struct(NestedStruct[:] mslice):
...
@@ -150,7 +150,8 @@ def nested_struct(NestedStruct[:] mslice):
1 2 3 4 5
1 2 3 4 5
"""
"""
buf
=
mslice
buf
=
mslice
print buf[0].x.a, buf[0].x.b, buf[0].y.a, buf[0].y.b, buf[0].z
d
=
buf
[
0
]
print
d
[
'x'
][
'a'
],
d
[
'x'
][
'b'
],
d
[
'y'
][
'a'
],
d
[
'y'
][
'b'
],
d
[
'z'
]
def
packed_struct
(
PackedStruct
[:]
mslice
):
def
packed_struct
(
PackedStruct
[:]
mslice
):
"""
"""
...
@@ -165,7 +166,7 @@ def packed_struct(PackedStruct[:] mslice):
...
@@ -165,7 +166,7 @@ def packed_struct(PackedStruct[:] mslice):
"""
"""
buf
=
mslice
buf
=
mslice
print buf[0]
.a, buf[0].b
print
buf
[
0
]
[
'a'
],
buf
[
0
][
'b'
]
def
nested_packed_struct
(
NestedPackedStruct
[:]
mslice
):
def
nested_packed_struct
(
NestedPackedStruct
[:]
mslice
):
"""
"""
...
@@ -179,7 +180,8 @@ def nested_packed_struct(NestedPackedStruct[:] mslice):
...
@@ -179,7 +180,8 @@ def nested_packed_struct(NestedPackedStruct[:] mslice):
1 2 3 4 5
1 2 3 4 5
"""
"""
buf
=
mslice
buf
=
mslice
print buf[0].a, buf[0].b, buf[0].sub.a, buf[0].sub.b, buf[0].c
d
=
buf
[
0
]
print
d
[
'a'
],
d
[
'b'
],
d
[
'sub'
][
'a'
],
d
[
'sub'
][
'b'
],
d
[
'c'
]
def
complex_dtype
(
long
double
complex
[:]
mslice
):
def
complex_dtype
(
long
double
complex
[:]
mslice
):
...
@@ -207,18 +209,8 @@ def complex_struct_dtype(LongComplex[:] mslice):
...
@@ -207,18 +209,8 @@ def complex_struct_dtype(LongComplex[:] mslice):
0.0 -1.0
0.0 -1.0
"""
"""
buf
=
mslice
buf
=
mslice
print buf[0]
.real, buf[0].imag
print
buf
[
0
]
[
'real'
],
buf
[
0
][
'imag'
]
def complex_struct_inplace(LongComplex[:] mslice):
"""
>>> complex_struct_inplace(LongComplexMockBuffer(None, [(0, -1)]))
1.0 1.0
"""
buf = mslice
buf[0].real += 1
buf[0].imag += 2
print buf[0].real, buf[0].imag
'''
#
#
# Getting items and index bounds checking
# Getting items and index bounds checking
#
#
...
...
tests/run/struct_conversion.pyx
View file @
d7322d2f
...
@@ -60,3 +60,49 @@ def test_pointers(int n, double x):
...
@@ -60,3 +60,49 @@ def test_pointers(int n, double x):
print
a
.
data
.
n
print
a
.
data
.
n
print
b
.
data
.
x
print
b
.
data
.
x
print
a
.
ptr
==
b
.
ptr
==
NULL
print
a
.
ptr
==
b
.
ptr
==
NULL
cdef
struct
MyStruct
:
char
c
int
i
float
f
char
*
s
def
test_obj_to_struct
(
MyStruct
mystruct
):
"""
>>> test_obj_to_struct(dict(c=10, i=20, f=6.7, s="hello"))
c=10 i=20 f=6.70 s=hello
>>> test_obj_to_struct(None)
Traceback (most recent call last):
...
TypeError: Expected a mapping, not NoneType
>>> test_obj_to_struct(dict(s="world"))
Traceback (most recent call last):
...
ValueError: No value specified for struct attribute 'c'
>>> test_obj_to_struct(dict(c="world"))
Traceback (most recent call last):
...
TypeError: an integer is required
"""
print
'c=%d i=%d f=%.2f s=%s'
%
(
mystruct
.
c
,
mystruct
.
i
,
mystruct
.
f
,
mystruct
.
s
)
cdef
struct
NestedStruct
:
MyStruct
mystruct
double
d
def
test_nested_obj_to_struct
(
NestedStruct
nested
):
"""
>>> test_nested_obj_to_struct(dict(mystruct=dict(c=10, i=20, f=6.7, s="hello"), d=4.5))
c=10 i=20 f=6.70 s=hello d=4.50
>>> test_nested_obj_to_struct(dict(d=7.6))
Traceback (most recent call last):
...
ValueError: No value specified for struct attribute 'mystruct'
>>> test_nested_obj_to_struct(dict(mystruct={}, d=7.6))
Traceback (most recent call last):
...
ValueError: No value specified for struct attribute 'c'
"""
print
'c=%d i=%d f=%.2f s=%s d=%.2f'
%
(
nested
.
mystruct
.
c
,
nested
.
mystruct
.
i
,
nested
.
mystruct
.
f
,
nested
.
mystruct
.
s
,
nested
.
d
)
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