Skip to content

Commit

Permalink
lib: Accept more newline styles in G_getl (OSGeo#3853)
Browse files Browse the repository at this point in the history
Use G_getl2 in G_getl to have everywhere the same behavior of supporting all newlines. Originally, G_getl2 was created to keep the behavior of G_getl. However, now, we want G_getl2 behavior everywhere.

Keeping G_getl2 for compatibility. Code can later be clean up to use only G_getl, probably at the time when G_getl2 is removed (v9).

The new test fails without the change in the library for CRLF for G_getl, but passes with the change. CR fails because it is not supported by fgets used since 88090da (OSGeo#3850), so expecting failure for CR.

Return and argument types for ctypes fopen need to be set for the code to work everywhere reliably.

---------

Co-authored-by: Nicklas Larsson <n_larsson@yahoo.com>
  • Loading branch information
2 people authored and Mahesh1998 committed Sep 19, 2024
1 parent 3a1deb2 commit 5190166
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 11 deletions.
16 changes: 5 additions & 11 deletions lib/gis/getl.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,26 @@
* \brief Gets a line of text from a file
*
* This routine runs fgets() to fetch a line of text from a file
* (advancing file pointer) and removes trailing newline. fgets() does
* not recognize '<code>\\r</code>' as an EOL and will read past * it.
* (advancing file pointer) and removes trailing newline.
*
* \param buf string buffer to receive read data
* \param n maximum number of bytes to read
* \param fd file descriptor structure
*
* \return 1 on success
* \return 0 EOF
*
* \see G_getl2()
*/
int G_getl(char *buf, int n, FILE *fd)
{
if (!fgets(buf, n, fd))
return 0;

for (; *buf && *buf != '\n'; buf++)
;
*buf = 0;

return 1;
return G_getl2(buf, n, fd);
}

/*!
* \brief Gets a line of text from a file of any pedigree
*
* This routine is like G_getl() but is more portable. It supports
* This routine supports
* text files created on various platforms (UNIX, MacOS9, DOS),
* i.e. <code>\\n (\\012)</code>, <code>\\r (\\015)</code>, and
* <code>\\r\\n (\\015\\012)</code> style newlines.
Expand Down
81 changes: 81 additions & 0 deletions lib/gis/testsuite/test_gis_lib_getl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Test of gis library line reading functions
@author Vaclav Petras
"""

import ctypes
import pathlib
import platform
import unittest

import grass.lib.gis as libgis
from grass.gunittest.case import TestCase
from grass.gunittest.main import test


class TestNewlinesWithGetlFunctions(TestCase):
"""Test C functions G_getl() and G_getl2() from gis library"""

@classmethod
def setUpClass(cls):
cls.libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("c"))
cls.libc.fopen.restype = ctypes.POINTER(libgis.FILE)
cls.libc.fopen.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
cls.file_path = pathlib.Path("test.txt")

def tearDown(self):
self.file_path.unlink()

def read_lines_and_assert(self, get_line_function, newline):
"""Write and read lines and then assert they are as expected"""
lines = ["Line 1", "Line 2", "Line 3"]
with open(self.file_path, mode="w", newline=newline) as stream:
for line in lines:
# Python text newline here.
# The specific newline is added by the stream.
stream.write(f"{line}\n")

file_ptr = self.libc.fopen(str(self.file_path).encode("utf-8"), b"r")
if not file_ptr:
raise FileNotFoundError(f"Could not open file: {self.file_path}")

try:
buffer_size = 50
buffer = ctypes.create_string_buffer(buffer_size)

for line in lines:
get_line_function(buffer, ctypes.sizeof(buffer), file_ptr)
result = buffer.value.decode("utf-8") if buffer else None
self.assertEqual(line, result)
finally:
self.libc.fclose(file_ptr)

def test_getl_lf(self):
r"""Check G_getl() with LF (\n)"""
self.read_lines_and_assert(libgis.G_getl, "\n")

@unittest.expectedFailure
def test_getl_cr(self):
r"""Check G_getl() with CR (\r)"""
self.read_lines_and_assert(libgis.G_getl, "\r")

def test_getl_crlf(self):
r"""Check G_getl() with CRLF (\r\n)"""
self.read_lines_and_assert(libgis.G_getl, "\r\n")

def test_getl2_lf(self):
r"""Check G_getl2() with LF (\n)"""
self.read_lines_and_assert(libgis.G_getl2, "\n")

@unittest.expectedFailure
def test_getl2_cr(self):
r"""Check G_getl2() with CR (\r)"""
self.read_lines_and_assert(libgis.G_getl2, "\r")

def test_getl2_crlf(self):
r"""Check G_getl2() with CRLF (\r\n)"""
self.read_lines_and_assert(libgis.G_getl2, "\r\n")


if __name__ == "__main__":
test()

0 comments on commit 5190166

Please sign in to comment.