├── LICENSE.md ├── README.md ├── pythonfile.nim └── pythonfile.nimble /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018 Adam Chesak 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | nim-pythonfile is a Nim module to wrap the file functions and provide an interface as similar as possible to that of Python. 5 | 6 | Examples: 7 | 8 | # Open a file for reading, read and print one line, then read and store the next ten bytes. 9 | var f: PythonFile = open("my_file.txt", "r") # f = open("my_file.txt", "r") 10 | echo(f.readline()) # print(f.readline()) 11 | var s: string = f.read(10) # s = f.read(10) 12 | f.close() # f.close() 13 | 14 | # Open a file for writing, write "Hello World!", then write multiple lines at once. 15 | var f: PythonFile = open("my_file.txt", "w") # f = open("my_file.txt", "w") 16 | f.write("Hello World!") # f.write("Hello World!") 17 | f.writelines(["This", "is", "an", "example"]) # f.writelines(["This", "is", "an", "example"]) 18 | f.close() # f.close() 19 | 20 | # Open a file for reading or writing, then read and write from multiple locations 21 | # using seek() and tell(). 22 | var f: PythonFile = open("my_file.txt", "r+") # f = open("my_file.txt", "r+") 23 | f.seek(10) # f.seek(10) 24 | echo(f.read()) # print(f.read()) 25 | echo(f.tell()) # print(f.tell()) 26 | f.seek(0) # f.seek(0) 27 | f.seek(-50, 2) # f.seek(-50, 2) 28 | f.write("Inserted at pos 50 from end") # f.write("Inserted at pos 50 from end") 29 | f.close() # f.close() 30 | 31 | # Basic with/as grammer for file operation 32 | with open("temp.txt", "w+") as f: 33 | f.write("test") 34 | f.seek(0, 0) 35 | echo(f.read()) 36 | 37 | Note that due to some inherent differences between how Nim and Python handle files, a complete 38 | 1 to 1 wrapper is not possible. Notably, Nim has no equivalent to the `newlines` and `encoding` 39 | properties, and while they are present in this implementation they are always set to empty string. In 40 | addition, the `fileno()` procedure functions differently from how it does in Python, yet it has the 41 | same basic functionality. 42 | 43 | For general use, however, this wrapper provides all of the common Python file methods. 44 | 45 | License 46 | ======= 47 | 48 | nim-pythonfile is released under the MIT open source license. 49 | -------------------------------------------------------------------------------- /pythonfile.nim: -------------------------------------------------------------------------------- 1 | # Nim module to wrap the file functions and provide an interface 2 | # as similar as possible to that of Python. 3 | 4 | # Written by Adam Chesak. 5 | # Released under the MIT open source license. 6 | 7 | 8 | ## pythonfile is a Nim module to wrap the file functions and provide an interface as similar as possible to that of Python. 9 | ## 10 | ## Examples: 11 | ## 12 | ## .. code-block:: nimrod 13 | ## 14 | ## # Open a file for reading, read and print one line, then read and store the next ten bytes. 15 | ## var f: PythonFile = open("my_file.txt", "r") # f = open("my_file.txt", "r") 16 | ## echo(f.readline()) # print(f.readline()) 17 | ## var s: string = f.read(10) # s = f.read(10) 18 | ## f.close() # f.close() 19 | ## 20 | ## .. code-block:: nimrod 21 | ## 22 | ## # Open a file for writing, write "Hello World!", then write multiple lines at once. 23 | ## var f: PythonFile = open("my_file.txt", "w") # f = open("my_file.txt", "w") 24 | ## f.write("Hello World!") # f.write("Hello World!") 25 | ## f.writelines(["This", "is", "an", "example"]) # f.writelines(["This", "is", "an", "example"]) 26 | ## f.close() # f.close() 27 | ## 28 | ## .. code-block:: nimrod 29 | ## 30 | ## # Open a file for reading or writing, then read and write from multiple locations 31 | ## # using seek() and tell(). 32 | ## var f: PythonFile = open("my_file.txt", "r+") # f = open("my_file.txt", "r+") 33 | ## f.seek(10) # f.seek(10) 34 | ## echo(f.read()) # print(f.read()) 35 | ## echo(f.tell()) # print(f.tell()) 36 | ## f.seek(0) # f.seek(0) 37 | ## f.seek(-50, 2) # f.seek(-50, 2) 38 | ## f.write("Inserted at pos 50 from end") # f.write("Inserted at pos 50 from end") 39 | ## f.close() # f.close() 40 | ## 41 | ## Note that due to some inherent differences between how Nim and Python handle files, a complete 42 | ## 1 to 1 wrapper is not possible. Notably, Nim has no equivalent to the ``newlines`` and ``encoding`` 43 | ## properties, and while they are present in this implementation they are always set to ``nil``. In 44 | ## addition, the ``fileno()`` procedure functions differently from how it does in Python, yet it has the 45 | ## same basic functionality. Finally, the ``itatty()`` procedure will always return ``false``. 46 | ## 47 | ## For general use, however, this wrapper provides all of the common Python file methods. 48 | 49 | 50 | import macros, strutils, terminal 51 | 52 | when defined(Unix): 53 | import posix 54 | 55 | 56 | type 57 | PythonFile* = ref object 58 | f*: File 59 | mode*: string 60 | closed*: bool 61 | name*: string 62 | softspace*: bool 63 | encoding*: string 64 | newlines*: string 65 | filename*: string 66 | 67 | macro with*(args: untyped, body: untyped): untyped = 68 | ### A with macro. 69 | 70 | args.expectKind nnkInfix 71 | args.expectLen 3 72 | 73 | # basic stmt 74 | var stmt = newStmtList() 75 | stmt.add newTree(nnkVarSection, 76 | newIdentDefs( 77 | args[2], 78 | newEmptyNode(), 79 | args[1] 80 | ) 81 | ) 82 | 83 | for i in body: stmt.add i 84 | stmt.add newTree(nnkCall, 85 | newDotExpr( 86 | args[2], 87 | newIdentNode("close") 88 | ) 89 | ) 90 | 91 | # add basic stmt to block stmt, for better exception catch 92 | result = newStmtList() 93 | result.add newTree(nnkBlockStmt, 94 | newEmptyNode(), 95 | stmt 96 | ) 97 | 98 | proc open*(filename: string, mode: string = "r", buffering: int = -1): PythonFile = 99 | ## Opens the specified file. 100 | ## 101 | ## mode can be either ``r`` (reading), ``w`` (writing), ``a`` (appending), ``r+`` (read/write, only existing files), and ``w+`` 102 | ## (read/write, file created if needed). 103 | ## 104 | ## ``buffering`` specifies the file’s desired buffer size: 0 means unbuffered, 1 means line buffered, any other positive value means 105 | ## use a buffer of (approximately) that size (in bytes). A negative buffering means to use the system default, which is usually 106 | ## line buffered for tty devices and fully buffered for other files. 107 | 108 | var f: PythonFile = PythonFile(f: nil, mode: mode, closed: false, softspace: false, encoding: "", newlines: "", filename: filename) 109 | var m: FileMode 110 | case mode: 111 | of "r", "rb": 112 | m = fmRead 113 | of "w", "wb": 114 | m = fmWrite 115 | of "a", "ab": 116 | m = fmAppend 117 | of "r+", "rb+": 118 | m = fmReadWriteExisting 119 | of "w+", "wb+": 120 | m = fmReadWrite 121 | else: 122 | m = fmRead 123 | f.f = open(filename, m, buffering) 124 | return f 125 | 126 | 127 | proc close*(file: PythonFile): void = 128 | ## Closes the file. 129 | 130 | file.f.close() 131 | file.closed = true 132 | 133 | 134 | proc tell*(file: PythonFile): int = 135 | ## Returns the file's current position. 136 | 137 | return int(file.f.getFilePos()) 138 | 139 | 140 | proc seek*(file: PythonFile, offset: int): void = 141 | ## Sets the file's current position to the specified value. 142 | 143 | file.f.setFilePos(offset) 144 | 145 | 146 | proc seek*(file: PythonFile, offset: int, whence: int): void = 147 | ## Sets the file's current position to the specified value. ``whence`` can be either 0 (absolute positioning), 148 | ## 1 (seek relative to current position), or 2 (seek relative to file's end). 149 | 150 | case whence: 151 | of 0: 152 | file.seek(offset) 153 | of 1: 154 | file.seek(file.tell() + offset) 155 | of 2: 156 | file.seek(int(file.f.getFileSize()) + offset) 157 | else: 158 | file.seek(offset) 159 | 160 | 161 | proc write*(file: PythonFile, s: string): void = 162 | ## Writes ``s`` to the file. 163 | 164 | file.f.write(s) 165 | 166 | 167 | proc write*(file: PythonFile, s: float32): void = 168 | ## Writes ``s`` to the file. 169 | 170 | file.f.write(s) 171 | 172 | 173 | proc write*(file: PythonFile, s: int): void = 174 | ## Writes ``s`` to the file. 175 | 176 | file.f.write(s) 177 | 178 | 179 | proc write*(file: PythonFile, s: BiggestInt): void = 180 | ## Writes ``s`` to the file. 181 | 182 | file.f.write(s) 183 | 184 | 185 | proc write*(file: PythonFile, s: BiggestFloat): void = 186 | ## Writes ``s`` to the file. 187 | 188 | file.f.write(s) 189 | 190 | 191 | proc write*(file: PythonFile, s: bool): void = 192 | ## Writes ``s`` to the file. 193 | 194 | file.f.write(s) 195 | 196 | 197 | proc write*(file: PythonFile, s: char): void = 198 | ## Writes ``s`` to the file. 199 | 200 | file.f.write(s) 201 | 202 | 203 | proc write*(file: PythonFile, s: cstring): void = 204 | ## Writes ``s`` to the file. 205 | 206 | file.f.write(s) 207 | 208 | 209 | proc read*(file: PythonFile): string = 210 | ## Reads all of the contents of the file. 211 | 212 | let pos = int(file.f.getFilePos()) 213 | file.f.setFilePos(0) 214 | return file.f.readAll().substr(pos) 215 | 216 | 217 | proc read*(file: PythonFile, count: int): string = 218 | ## Reads the specified number of bytes from the file. 219 | 220 | var chars: seq[char] = newSeq[char](count) 221 | discard file.f.readChars(chars, 0, count) 222 | return chars.join() 223 | 224 | 225 | proc readline*(file: PythonFile): string = 226 | ## Reads a line from the file. 227 | 228 | let line: string = file.f.readLine() 229 | if file.f.endOfFile(): 230 | return line 231 | else: 232 | return line & "\n" 233 | 234 | 235 | proc readline*(file: PythonFile, count: int): string = 236 | ## Reads a line from the file, up to a maximum of the specified number of bytes. 237 | 238 | let line: string = file.readLine() 239 | if len(line) <= count: 240 | return line 241 | else: 242 | file.seek(-1 * (len(line) - count), 1) 243 | return line.substr(0, count) 244 | 245 | 246 | proc readlines*(file: PythonFile): seq[string] = 247 | ## Reads all of the lines from the file. 248 | 249 | file.f.setFilePos(0) 250 | let contents: string = file.read() 251 | file.f.setFilePos(0) 252 | var lines: seq[string] = @[] 253 | for index in 0..countLines(contents): 254 | if file.f.endOfFile(): 255 | break 256 | lines.add(file.readline()) 257 | return lines 258 | 259 | 260 | proc readlines*(file: PythonFile, count: int): seq[string] = 261 | ## Reads all of the lines from the file, up to a maximum of the specified number of bytes. 262 | 263 | file.f.setFilePos(0) 264 | let contents: string = file.read() 265 | file.f.setFilePos(0) 266 | var lines: seq[string]= newSeq[string](countLines(contents)) 267 | var charCount: int = 0 268 | for index in 0..countlines(contents): 269 | if file.f.endOfFile(): 270 | break 271 | let line: string = file.readLine() 272 | charCount += len(line) 273 | if charCount < count: 274 | lines[index] = line 275 | elif charCount > count: 276 | var diff: int = len(line) - (charCount - count) 277 | lines[index] = line.substr(0, diff) 278 | file.seek(-1 * (charCount - count), 1) 279 | break 280 | else: 281 | lines[index] = line 282 | break 283 | return lines 284 | 285 | 286 | proc flush*(file: PythonFile): void = 287 | ## Flushes the file's internal buffer. 288 | 289 | file.f.flushFile() 290 | 291 | 292 | proc fileno*(file: PythonFile): FileHandle = 293 | ## Returns the underlying file handle. Note that due to implementation details this is NOT the same in Nim as it 294 | ## is in Python and CANNOT be used the same way! 295 | 296 | return file.f.getfileHandle() 297 | 298 | 299 | proc writelines*(file: PythonFile, lines: openarray[string]): void = 300 | ## Writes the lines to the file. 301 | 302 | for line in lines: 303 | file.write(line) 304 | 305 | 306 | proc isatty*(file: PythonFile): bool = 307 | ## Returns true if the opened file is a tty device, else returns false 308 | 309 | return file.f.isatty() 310 | 311 | proc truncate*(file: PythonFile, length: int): int = 312 | ## Truncate the file to a specify size, returns 0 if successed, else returns -1. 313 | ## TODO: add support for windows platform. 314 | 315 | when defined(Unix): 316 | result = ftruncate(file.f.getFileHandle(), length) 317 | if result == 0: file.seek(0, 0) 318 | elif defined(Windows): 319 | result = 0 320 | 321 | when isMainModule: 322 | ## test code 323 | with open("temp.txt", "w+") as f: 324 | f.write("test") 325 | f.seek(0, 0) 326 | echo(f.read()) 327 | 328 | import os 329 | os.removeFile("temp.txt") -------------------------------------------------------------------------------- /pythonfile.nimble: -------------------------------------------------------------------------------- 1 | [Package] 2 | name = "pythonfile" 3 | version = "1.1" 4 | author = "Adam Chesak" 5 | description = "Wrapper of the file functions to provide an interface as similar as possible to that of Python" 6 | license = "MIT" 7 | 8 | [Deps] 9 | Requires: "nim >= 0.18" 10 | --------------------------------------------------------------------------------