From 7da6767440f0c699c327e6fcc1d6e7f68a317dfa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hubert=20Figui=C3=A8re?= <hub@figuiere.net>
Date: Wed, 30 Oct 2019 00:32:17 -0400
Subject: [PATCH] Make struct wraping C type to tuple

---
 src/lib.rs         | 62 ++++++++++++++++++++----------------------
 src/xmp.rs         | 68 ++++++++++++++++++++++------------------------
 src/xmpfile.rs     | 34 ++++++++++-------------
 src/xmpiterator.rs | 20 ++++++--------
 src/xmpstring.rs   | 22 ++++++---------
 5 files changed, 94 insertions(+), 112 deletions(-)

diff --git a/src/lib.rs b/src/lib.rs
index 85a39fe..32d8148 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -110,9 +110,7 @@ pub fn prefix_namespace(prefix: &str) -> Option<XmpString> {
 
 /// A wrapper around the C type DateTime
 #[derive(Clone, Debug, Default)]
-pub struct DateTime {
-    c: c::XmpDateTime,
-}
+pub struct DateTime(c::XmpDateTime);
 
 impl DateTime {
     pub fn new() -> Self {
@@ -120,61 +118,61 @@ impl DateTime {
     }
     /// Construct from the native C type
     pub fn from(d: c::XmpDateTime) -> DateTime {
-        DateTime { c: d }
+        DateTime(d)
     }
     /// Return the native pointer
     pub fn as_ptr(&self) -> *const c::XmpDateTime {
-        &self.c as *const c::XmpDateTime
+        &self.0 as *const c::XmpDateTime
     }
     /// Return the native mutable pointer
     pub fn as_mut_ptr(&mut self) -> *mut c::XmpDateTime {
-        &mut self.c as *mut c::XmpDateTime
+        &mut self.0 as *mut c::XmpDateTime
     }
     /// Set date
     pub fn set_date(&mut self, year: i32, month: i32, day: i32) {
-        self.c.year = year;
-        self.c.month = month;
-        self.c.day = day;
-        self.c.has_date = 1;
+        self.0.year = year;
+        self.0.month = month;
+        self.0.day = day;
+        self.0.has_date = 1;
     }
     /// Set time
     pub fn set_time(&mut self, hour: i32, min: i32, sec: i32) {
-        self.c.hour = hour;
-        self.c.minute = min;
-        self.c.second = sec;
-        self.c.has_time = 1;
+        self.0.hour = hour;
+        self.0.minute = min;
+        self.0.second = sec;
+        self.0.has_time = 1;
     }
     /// Set nano_second
     pub fn set_nano_second(&mut self, nano_second: i32) {
-        self.c.nano_second = nano_second;
+        self.0.nano_second = nano_second;
     }
     /// Set Timezone
     pub fn set_timezone(&mut self, sign: XmpTzSign, hour: i32, min: i32) {
-        self.c.tz_sign = sign;
-        self.c.tz_hour = hour;
-        self.c.tz_minute = min;
-        self.c.has_tz = 1;
+        self.0.tz_sign = sign;
+        self.0.tz_hour = hour;
+        self.0.tz_minute = min;
+        self.0.has_tz = 1;
     }
     pub fn year(&self) -> i32 {
-        self.c.year
+        self.0.year
     }
     pub fn month(&self) -> i32 {
-        self.c.month
+        self.0.month
     }
     pub fn day(&self) -> i32 {
-        self.c.day
+        self.0.day
     }
     pub fn hour(&self) -> i32 {
-        self.c.hour
+        self.0.hour
     }
     pub fn minute(&self) -> i32 {
-        self.c.minute
+        self.0.minute
     }
     pub fn second(&self) -> i32 {
-        self.c.second
+        self.0.second
     }
     pub fn nano_second(&self) -> i32 {
-        self.c.nano_second
+        self.0.nano_second
     }
 }
 
@@ -182,8 +180,8 @@ impl PartialEq for DateTime {
     fn eq(&self, other: &DateTime) -> bool {
         unsafe {
             c::xmp_datetime_compare(
-                &self.c as *const c::XmpDateTime,
-                &other.c as *const c::XmpDateTime,
+                &self.0 as *const c::XmpDateTime,
+                &other.0 as *const c::XmpDateTime,
             ) == 0
         }
     }
@@ -192,8 +190,8 @@ impl PartialOrd for DateTime {
     fn partial_cmp(&self, other: &DateTime) -> Option<Ordering> {
         match unsafe {
             c::xmp_datetime_compare(
-                &self.c as *const c::XmpDateTime,
-                &other.c as *const c::XmpDateTime,
+                &self.0 as *const c::XmpDateTime,
+                &other.0 as *const c::XmpDateTime,
             )
         } {
             0 => Some(Ordering::Equal),
@@ -208,8 +206,8 @@ impl Ord for DateTime {
     fn cmp(&self, other: &DateTime) -> Ordering {
         match unsafe {
             c::xmp_datetime_compare(
-                &self.c as *const c::XmpDateTime,
-                &other.c as *const c::XmpDateTime,
+                &self.0 as *const c::XmpDateTime,
+                &other.0 as *const c::XmpDateTime,
             )
         } {
             n if n < 0 => Ordering::Less,
diff --git a/src/xmp.rs b/src/xmp.rs
index 433e204..4b649d6 100644
--- a/src/xmp.rs
+++ b/src/xmp.rs
@@ -124,25 +124,21 @@ pub mod flags {
 
 use self::flags::*;
 
-pub struct Xmp {
-    ptr: *mut c::Xmp,
-}
+pub struct Xmp(*mut c::Xmp);
 
 unsafe impl Send for Xmp {
 }
 
 impl Default for Xmp {
     fn default() -> Xmp {
-        Xmp {
-            ptr: unsafe { c::xmp_new_empty() },
-        }
+        Xmp(unsafe { c::xmp_new_empty() })
     }
 }
 
 impl Xmp {
     /// Construct from a native ptr. Will own it.
     pub fn from(ptr: *mut c::Xmp) -> Xmp {
-        Xmp { ptr }
+        Xmp(ptr)
     }
     /// New Xmp object
     pub fn new() -> Xmp {
@@ -159,7 +155,7 @@ impl Xmp {
     }
     /// Parse buff into a Xmp
     pub fn parse(&mut self, buf: &[u8]) -> Result<()> {
-        if unsafe { c::xmp_parse(self.ptr, buf.as_ptr() as *const c_char, buf.len()) } {
+        if unsafe { c::xmp_parse(self.0, buf.as_ptr() as *const c_char, buf.len()) } {
             Ok(())
         } else {
             Err(super::get_error())
@@ -172,7 +168,7 @@ impl Xmp {
             return Err(super::Error::BadObject);
         }
         let mut buffer = XmpString::new();
-        if unsafe { c::xmp_serialize(self.ptr, buffer.as_mut_ptr(), options.bits(), padding) } {
+        if unsafe { c::xmp_serialize(self.0, buffer.as_mut_ptr(), options.bits(), padding) } {
             return Ok(buffer);
         }
         Err(super::get_error())
@@ -195,7 +191,7 @@ impl Xmp {
         let mut buffer = XmpString::new();
         if unsafe {
             c::xmp_serialize_and_format(
-                self.ptr,
+                self.0,
                 buffer.as_mut_ptr(),
                 options.bits(),
                 padding,
@@ -222,7 +218,7 @@ impl Xmp {
         let mut property = XmpString::new();
         let result = unsafe {
             c::xmp_get_property(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 property.as_mut_ptr(),
@@ -250,7 +246,7 @@ impl Xmp {
         let mut property = DateTime::new();
         let result = unsafe {
             c::xmp_get_property_date(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 property.as_mut_ptr(),
@@ -278,7 +274,7 @@ impl Xmp {
         let mut property = 0f64;
         let result = unsafe {
             c::xmp_get_property_float(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 &mut property as *mut f64,
@@ -306,7 +302,7 @@ impl Xmp {
         let mut property = false;
         let result = unsafe {
             c::xmp_get_property_bool(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 &mut property as *mut bool,
@@ -334,7 +330,7 @@ impl Xmp {
         let mut property = 0i32;
         let result = unsafe {
             c::xmp_get_property_int32(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 &mut property as *mut i32,
@@ -362,7 +358,7 @@ impl Xmp {
         let mut property = 0i64;
         let result = unsafe {
             c::xmp_get_property_int64(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 &mut property as *mut i64,
@@ -391,7 +387,7 @@ impl Xmp {
         let mut property = XmpString::new();
         let result = unsafe {
             c::xmp_get_array_item(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 index,
@@ -420,7 +416,7 @@ impl Xmp {
         let s_value = CString::new(value).unwrap();
         if unsafe {
             c::xmp_set_property(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 s_value.as_ptr(),
@@ -445,7 +441,7 @@ impl Xmp {
         let s_name = CString::new(name).unwrap();
         if unsafe {
             c::xmp_set_property_date(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 value.as_ptr(),
@@ -470,7 +466,7 @@ impl Xmp {
         let s_name = CString::new(name).unwrap();
         if unsafe {
             c::xmp_set_property_float(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 value,
@@ -495,7 +491,7 @@ impl Xmp {
         let s_name = CString::new(name).unwrap();
         if unsafe {
             c::xmp_set_property_bool(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 value,
@@ -520,7 +516,7 @@ impl Xmp {
         let s_name = CString::new(name).unwrap();
         if unsafe {
             c::xmp_set_property_int32(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 value,
@@ -545,7 +541,7 @@ impl Xmp {
         let s_name = CString::new(name).unwrap();
         if unsafe {
             c::xmp_set_property_int64(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 value,
@@ -572,7 +568,7 @@ impl Xmp {
         let s_value = CString::new(value).unwrap();
         if unsafe {
             c::xmp_set_array_item(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 index,
@@ -601,7 +597,7 @@ impl Xmp {
         let s_value = CString::new(value).unwrap();
         if unsafe {
             c::xmp_append_array_item(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 array_options.bits(),
@@ -619,7 +615,7 @@ impl Xmp {
     pub fn delete_property(&mut self, schema: &str, name: &str) -> Result<()> {
         let s_schema = CString::new(schema).unwrap();
         let s_name = CString::new(name).unwrap();
-        if unsafe { c::xmp_delete_property(self.ptr, s_schema.as_ptr(), s_name.as_ptr()) } {
+        if unsafe { c::xmp_delete_property(self.0, s_schema.as_ptr(), s_name.as_ptr()) } {
             Ok(())
         } else {
             Err(super::get_error())
@@ -630,7 +626,7 @@ impl Xmp {
     pub fn has_property(&self, schema: &str, name: &str) -> bool {
         let s_schema = CString::new(schema).unwrap();
         let s_name = CString::new(name).unwrap();
-        unsafe { c::xmp_has_property(self.ptr, s_schema.as_ptr(), s_name.as_ptr()) }
+        unsafe { c::xmp_has_property(self.0, s_schema.as_ptr(), s_name.as_ptr()) }
     }
 
     /// Get localized text.
@@ -653,7 +649,7 @@ impl Xmp {
         let mut raw_propsbits = 0u32;
         let result = unsafe {
             c::xmp_get_localized_text(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 s_gen_lang.as_ptr(),
@@ -688,7 +684,7 @@ impl Xmp {
         let s_value = CString::new(value).unwrap();
         if unsafe {
             c::xmp_set_localized_text(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 s_gen_lang.as_ptr(),
@@ -717,7 +713,7 @@ impl Xmp {
         let s_spec_lang = CString::new(spec_lang).unwrap();
         if unsafe {
             c::xmp_delete_localized_text(
-                self.ptr,
+                self.0,
                 s_schema.as_ptr(),
                 s_name.as_ptr(),
                 s_gen_lang.as_ptr(),
@@ -732,17 +728,17 @@ impl Xmp {
 
     /// Return if the native pointer is null.
     pub fn is_null(&self) -> bool {
-        self.ptr.is_null()
+        self.0.is_null()
     }
 
     /// Return the native pointer.
     pub fn as_ptr(&self) -> *const c::Xmp {
-        self.ptr
+        self.0
     }
 
     /// Return the mutable native pointer.
     pub fn as_mut_ptr(&mut self) -> *mut c::Xmp {
-        self.ptr
+        self.0
     }
 }
 
@@ -750,9 +746,9 @@ impl Clone for Xmp {
     fn clone(&self) -> Self {
         if self.is_null() {
             // inside ptr is NULL. cloning a null object.
-            return Xmp::from(self.ptr);
+            return Xmp::from(self.0);
         }
-        Xmp::from(unsafe { c::xmp_copy(self.ptr) })
+        Xmp::from(unsafe { c::xmp_copy(self.0) })
     }
 }
 
@@ -760,7 +756,7 @@ impl Drop for Xmp {
     /// Will release the Xmp native pointer on Drop.
     fn drop(&mut self) {
         if !self.is_null() {
-            unsafe { c::xmp_free(self.ptr) };
+            unsafe { c::xmp_free(self.0) };
         }
     }
 }
diff --git a/src/xmpfile.rs b/src/xmpfile.rs
index 307a053..e4d418a 100644
--- a/src/xmpfile.rs
+++ b/src/xmpfile.rs
@@ -83,15 +83,11 @@ pub mod flags {
 
 use self::flags::{CloseFlags, FormatOptionFlags, OpenFlags, FORMAT_NONE, OPEN_NONE};
 
-pub struct XmpFile {
-    ptr: *mut c::XmpFile,
-}
+pub struct XmpFile(*mut c::XmpFile);
 
 impl Default for XmpFile {
     fn default() -> XmpFile {
-        XmpFile {
-            ptr: unsafe { c::xmp_files_new() },
-        }
+        XmpFile(unsafe { c::xmp_files_new() })
     }
 }
 
@@ -110,7 +106,7 @@ impl XmpFile {
         if ptr.is_null() {
             return Err(super::get_error());
         }
-        Ok(XmpFile { ptr })
+        Ok(XmpFile(ptr))
     }
 
     /// Open an XmpFile. Usually called after new.
@@ -119,7 +115,7 @@ impl XmpFile {
             return Err(super::Error::BadObject);
         }
         let pp = CString::new(path).unwrap();
-        if unsafe { c::xmp_files_open(self.ptr, pp.as_ptr(), options.bits()) } {
+        if unsafe { c::xmp_files_open(self.0, pp.as_ptr(), options.bits()) } {
             Ok(())
         } else {
             Err(super::get_error())
@@ -131,7 +127,7 @@ impl XmpFile {
         if self.is_null() {
             return Err(super::Error::BadObject);
         }
-        if unsafe { c::xmp_files_close(self.ptr, options.bits()) } {
+        if unsafe { c::xmp_files_close(self.0, options.bits()) } {
             Ok(())
         } else {
             Err(super::get_error())
@@ -140,12 +136,12 @@ impl XmpFile {
 
     /// Return true if native pointer is null
     pub fn is_null(&self) -> bool {
-        self.ptr.is_null()
+        self.0.is_null()
     }
 
     /// Get a new Xmp fronm the currently open file
     pub fn get_new_xmp(&self) -> Result<Xmp> {
-        let ptr = unsafe { c::xmp_files_get_new_xmp(self.ptr) };
+        let ptr = unsafe { c::xmp_files_get_new_xmp(self.0) };
         if ptr.is_null() {
             return Err(super::get_error());
         }
@@ -157,7 +153,7 @@ impl XmpFile {
         if self.is_null() || xmp.is_null() {
             return Err(super::Error::BadObject);
         }
-        if unsafe { c::xmp_files_get_xmp(self.ptr, xmp.as_mut_ptr()) } {
+        if unsafe { c::xmp_files_get_xmp(self.0, xmp.as_mut_ptr()) } {
             Ok(())
         } else {
             Err(super::get_error())
@@ -170,7 +166,7 @@ impl XmpFile {
             return Err(super::Error::BadObject);
         }
         if unsafe {
-            c::xmp_files_get_xmp_xmpstring(self.ptr, packet.as_mut_ptr(), info as *mut PacketInfo)
+            c::xmp_files_get_xmp_xmpstring(self.0, packet.as_mut_ptr(), info as *mut PacketInfo)
         } {
             Ok(())
         } else {
@@ -183,7 +179,7 @@ impl XmpFile {
         if self.is_null() || xmp.is_null() {
             return false;
         }
-        unsafe { c::xmp_files_can_put_xmp(self.ptr, xmp.as_ptr()) }
+        unsafe { c::xmp_files_can_put_xmp(self.0, xmp.as_ptr()) }
     }
 
     /// Return true if it can put the XmpString packet into the XmpFile.
@@ -191,7 +187,7 @@ impl XmpFile {
         if self.is_null() || xmp_packet.is_null() {
             return false;
         }
-        unsafe { c::xmp_files_can_put_xmp_xmpstring(self.ptr, xmp_packet.as_ptr()) }
+        unsafe { c::xmp_files_can_put_xmp_xmpstring(self.0, xmp_packet.as_ptr()) }
     }
 
     /// Return true if it can put the XmpString packet into the XmpFile.
@@ -200,7 +196,7 @@ impl XmpFile {
             return false;
         }
         let pp = CString::new(xmp_packet).unwrap();
-        unsafe { c::xmp_files_can_put_xmp_cstr(self.ptr, pp.as_ptr(), xmp_packet.len()) }
+        unsafe { c::xmp_files_can_put_xmp_cstr(self.0, pp.as_ptr(), xmp_packet.len()) }
     }
 
     /// Put the Xmp into the XmpFile
@@ -208,7 +204,7 @@ impl XmpFile {
         if self.is_null() || xmp.is_null() {
             return Err(super::Error::BadObject);
         }
-        if unsafe { c::xmp_files_put_xmp(self.ptr, xmp.as_ptr()) } {
+        if unsafe { c::xmp_files_put_xmp(self.0, xmp.as_ptr()) } {
             Ok(())
         } else {
             Err(super::get_error())
@@ -232,7 +228,7 @@ impl XmpFile {
         let mut raw_handler_flags: u32 = 0;
         let result = unsafe {
             c::xmp_files_get_file_info(
-                self.ptr,
+                self.0,
                 s.as_mut_ptr(),
                 &mut raw_options,
                 format,
@@ -267,7 +263,7 @@ impl Drop for XmpFile {
     /// Drop the XmpFile.
     fn drop(&mut self) {
         if !self.is_null() {
-            unsafe { c::xmp_files_free(self.ptr) };
+            unsafe { c::xmp_files_free(self.0) };
         }
     }
 }
diff --git a/src/xmpiterator.rs b/src/xmpiterator.rs
index f6996be..58419aa 100644
--- a/src/xmpiterator.rs
+++ b/src/xmpiterator.rs
@@ -45,35 +45,31 @@ pub mod flags {
 
 use self::flags::*;
 
-pub struct XmpIterator {
-    ptr: *mut c::XmpIterator,
-}
+pub struct XmpIterator(*mut c::XmpIterator);
 
 impl XmpIterator {
     /// Construct a new `XmpIterator` from a native pointer
     pub fn new(xmp: &Xmp, schema: &str, name: &str, propsbits: IterFlags) -> XmpIterator {
         let s_schema = CString::new(schema).unwrap();
         let s_name = CString::new(name).unwrap();
-        XmpIterator {
-            ptr: unsafe {
+        XmpIterator(unsafe {
                 c::xmp_iterator_new(
                     xmp.as_ptr(),
                     s_schema.as_ptr(),
                     s_name.as_ptr(),
                     propsbits.bits(),
                 )
-            },
-        }
+        })
     }
 
     /// Whether native pointer is null
     pub fn is_null(&self) -> bool {
-        self.ptr.is_null()
+        self.0.is_null()
     }
 
     /// Return native pointer.
     pub fn as_ptr(&self) -> *mut c::XmpIterator {
-        self.ptr
+        self.0
     }
 
     /// Iterate to the next element following the option set by the iterator
@@ -92,7 +88,7 @@ impl XmpIterator {
         let mut raw_option: u32 = 0;
         let result = unsafe {
             c::xmp_iterator_next(
-                self.ptr,
+                self.0,
                 schema.as_mut_ptr(),
                 name.as_mut_ptr(),
                 value.as_mut_ptr(),
@@ -108,7 +104,7 @@ impl XmpIterator {
         if self.is_null() {
             return false;
         }
-        unsafe { c::xmp_iterator_skip(self.ptr, option.bits()) }
+        unsafe { c::xmp_iterator_skip(self.0, option.bits()) }
     }
 }
 
@@ -117,7 +113,7 @@ impl XmpIterator {
 impl Drop for XmpIterator {
     fn drop(&mut self) {
         if !self.is_null() {
-            unsafe { c::xmp_iterator_free(self.ptr) };
+            unsafe { c::xmp_iterator_free(self.0) };
         }
     }
 }
diff --git a/src/xmpstring.rs b/src/xmpstring.rs
index 7adf501..69ce0f1 100644
--- a/src/xmpstring.rs
+++ b/src/xmpstring.rs
@@ -25,15 +25,11 @@ use std::str;
 /// }
 /// ```
 #[derive(Debug)]
-pub struct XmpString {
-    ptr: *mut c::XmpString,
-}
+pub struct XmpString(*mut c::XmpString);
 
 impl Default for XmpString {
     fn default() -> XmpString {
-        XmpString {
-            ptr: unsafe { c::xmp_string_new() },
-        }
+        XmpString(unsafe { c::xmp_string_new() })
     }
 }
 
@@ -45,25 +41,25 @@ impl XmpString {
 
     /// Native pointer is NULL
     pub fn is_null(&self) -> bool {
-        self.ptr.is_null()
+        self.0.is_null()
     }
 
     /// Return the native pointer
     pub fn as_ptr(&self) -> *const c::XmpString {
-        self.ptr
+        self.0
     }
 
     /// Return the mutable native pointer
     pub fn as_mut_ptr(&mut self) -> *mut c::XmpString {
-        self.ptr
+        self.0
     }
 
     /// Return the length of the string
     pub fn len(&self) -> usize {
-        if self.ptr.is_null() {
+        if self.0.is_null() {
             return 0;
         }
-        unsafe { c::xmp_string_len(self.ptr) }
+        unsafe { c::xmp_string_len(self.0) }
     }
 
     pub fn is_empty(&self) -> bool {
@@ -74,7 +70,7 @@ impl XmpString {
     // XXX properly deal with the utf8 error
     pub fn to_str(&self) -> &str {
         unsafe {
-            let s = CStr::from_ptr(c::xmp_string_cstr(self.ptr));
+            let s = CStr::from_ptr(c::xmp_string_cstr(self.0));
             // we are supposed to receive UTF8 from the library.
             str::from_utf8_unchecked(s.to_bytes())
         }
@@ -85,7 +81,7 @@ impl Drop for XmpString {
     /// Will deallocate properly the underlying object
     fn drop(&mut self) {
         if !self.is_null() {
-            unsafe { c::xmp_string_free(self.ptr) };
+            unsafe { c::xmp_string_free(self.0) };
         }
     }
 }
-- 
GitLab