<commit_before>import os
import sys
import stat

from topaz.coerce import Coerce
from topaz.error import error_for_oserror
from topaz.module import ClassDef
from topaz.objects.arrayobject import W_ArrayObject
from topaz.objects.hashobject import W_HashObject
from topaz.objects.objectobject import W_Object
from topaz.objects.ioobject import W_IOObject
from topaz.objects.stringobject import W_StringObject
from topaz.system import IS_WINDOWS
from topaz.utils.ll_file import O_BINARY, ftruncate, isdir, fchmod
from topaz.utils.filemode import map_filemode


FNM_NOESCAPE = 0x01
FNM_PATHNAME = 0x02
FNM_DOTMATCH = 0x04


class W_FileObject(W_IOObject):
    classdef = ClassDef("File", W_IOObject.classdef, filepath=__file__)

    @classdef.setup_class
    def setup_class(cls, space, w_cls):
        if IS_WINDOWS:
            w_alt_seperator = space.newstr_fromstr("\\")
            w_fnm_syscase = space.newint(0x08)
            w_devnull = space.newstr_fromstr("NUL")
        else:
            w_alt_seperator = space.w_nil
            w_fnm_syscase = space.newint(0)
            w_devnull = space.newstr_fromstr("/dev/null")
        space.set_const(w_cls, "SEPARATOR", space.newstr_fromstr("/"))
        space.set_const(w_cls, "ALT_SEPARATOR", w_alt_seperator)
        space.set_const(w_cls, "PATH_SEPARATOR", space.newstr_fromstr(os.pathsep))
        space.set_const(w_cls, "NULL", w_devnull)
        space.set_const(w_cls, "FNM_SYSCASE", w_fnm_syscase)
        space.set_const(w_cls, "FNM_NOESCAPE", space.newint(FNM_NOESCAPE))
        space.set_const(w_cls, "FNM_PATHNAME", space.newint(FNM_PATHNAME))
        space.set_const(w_cls, "FNM_DOTMATCH", space.newint(FNM_DOTMATCH))
        space.set_const(w_cls, "BINARY", space.newint(O_BINARY))
        space.set_const(w_cls, "RDONLY", space.newint(os.O_RDONLY))
        space.set_const(w_cls, "WRONLY", space.newint(os.O_WRONLY))
        space.set_const(w_cls, "RDWR", space.newint(os.O_RDWR))
        space.set_const(w_cls, "APPEND", space.newint(os.O_APPEND))
        space.set_const(w_cls, "CREAT", space.newint(os.O_CREAT))
        space.set_const(w_cls, "EXCL", space.newint(os.O_EXCL))
        space.set_const(w_cls, "TRUNC", space.newint(os.O_TRUNC))

        space.set_const(w_cls, "Stat", space.getclassfor(W_FileStatObject))

    @classdef.singleton_method("allocate")
    def method_allocate(self, space, args_w):
        return W_FileObject(space)

    @classdef.singleton_method("size?", name="path")
    def singleton_method_size_p(self, space, name):
        try:
            stat_val = os.stat(name)
        except OSError:
            return space.w_nil
        return space.w_nil if stat_val.st_size == 0 else space.newint_or_bigint(stat_val.st_size)

    @classdef.singleton_method("unlink")
    @classdef.singleton_method("delete")
    def singleton_method_delete(self, space, args_w):
        for w_path in args_w:
            path = Coerce.path(space, w_path)
            try:
                os.unlink(path)
            except OSError as e:
                raise error_for_oserror(space, e)
        return space.newint(len(args_w))

    @classdef.method("initialize", filename="path")
    def method_initialize(self, space, filename, w_mode=None, w_perm_or_opt=None, w_opt=None):
        if w_mode is None:
            w_mode = space.w_nil
        if w_perm_or_opt is None:
            w_perm_or_opt = space.w_nil
        if w_opt is None:
            w_opt = space.w_nil
        if isinstance(w_perm_or_opt, W_HashObject):
            assert w_opt is space.w_nil
            perm = 0665
            w_opt = w_perm_or_opt
        elif w_opt is not space.w_nil:
            perm = space.int_w(w_perm_or_opt)
        else:
            perm = 0665
        mode = map_filemode(space, w_mode)
        if w_perm_or_opt is not space.w_nil or w_opt is not space.w_nil:
            raise space.error(space.w_NotImplementedError, "options hash or permissions for File.new")
        try:
            self.fd = os.open(filename, mode, perm)
        except OSError as e:
            raise error_for_oserror(space, e)
        return self

    @classdef.singleton_method("dirname", path="path")
    def method_dirname(self, space, path):
        separators = ["/"]
        if IS_WINDOWS:
            separators.append("\\")

        has_separator = False
        for separator in separators:
            if separator in path:
                has_separator = True
                break
        if not has_separator:
            return space.newstr_fromstr(".")

        if path in separators:
            return space.newstr_fromstr("/")

        while path[-1] in separators:
            newlen = len(path) - 1
            assert newlen >= 0
            path = path[:newlen]

        idx = -1
        for separator in separators:
            idx = max(idx, path.rfind(separator))
        while idx > 0 and path[idx - 1] in separators:
            idx -= 1
        if idx == 0:
            return space.newstr_fromstr("/")
        assert idx >= 0
        return space.newstr_fromstr(path[:idx])

    @classdef.singleton_method("expand_path", path="path")
    def method_expand_path(self, space, path, w_dir=None):
        if path and path[0] == "~":
            if len(path) >= 2 and path[1] == "/":
                path = os.environ["HOME"] + path[1:]
            elif len(path) < 2:
                return space.newstr_fromstr(os.environ["HOME"])
            else:
                raise NotImplementedError
        elif not path or path[0] != "/":
            if w_dir is not None and w_dir is not space.w_nil:
                dir = space.str_w(space.send(self, space.newsymbol("expand_path"), [w_dir]))
            else:
                dir = os.getcwd()

            path = dir + "/" + path

        items = []
        path = path.replace("\\", "/")
        parts = path.split("/")
        for part in parts:
            if part == "..":
                items.pop()
            elif part and part != ".":
                items.append(part)
<commit_msg>make sure we don't go out of bounds<commit_after>import os
import sys
import stat

from topaz.coerce import Coerce
from topaz.error import error_for_oserror
from topaz.module import ClassDef
from topaz.objects.arrayobject import W_ArrayObject
from topaz.objects.hashobject import W_HashObject
from topaz.objects.objectobject import W_Object
from topaz.objects.ioobject import W_IOObject
from topaz.objects.stringobject import W_StringObject
from topaz.system import IS_WINDOWS
from topaz.utils.ll_file import O_BINARY, ftruncate, isdir, fchmod
from topaz.utils.filemode import map_filemode


FNM_NOESCAPE = 0x01
FNM_PATHNAME = 0x02
FNM_DOTMATCH = 0x04


class W_FileObject(W_IOObject):
    classdef = ClassDef("File", W_IOObject.classdef, filepath=__file__)

    @classdef.setup_class
    def setup_class(cls, space, w_cls):
        if IS_WINDOWS:
            w_alt_seperator = space.newstr_fromstr("\\")
            w_fnm_syscase = space.newint(0x08)
            w_devnull = space.newstr_fromstr("NUL")
        else:
            w_alt_seperator = space.w_nil
            w_fnm_syscase = space.newint(0)
            w_devnull = space.newstr_fromstr("/dev/null")
        space.set_const(w_cls, "SEPARATOR", space.newstr_fromstr("/"))
        space.set_const(w_cls, "ALT_SEPARATOR", w_alt_seperator)
        space.set_const(w_cls, "PATH_SEPARATOR", space.newstr_fromstr(os.pathsep))
        space.set_const(w_cls, "NULL", w_devnull)
        space.set_const(w_cls, "FNM_SYSCASE", w_fnm_syscase)
        space.set_const(w_cls, "FNM_NOESCAPE", space.newint(FNM_NOESCAPE))
        space.set_const(w_cls, "FNM_PATHNAME", space.newint(FNM_PATHNAME))
        space.set_const(w_cls, "FNM_DOTMATCH", space.newint(FNM_DOTMATCH))
        space.set_const(w_cls, "BINARY", space.newint(O_BINARY))
        space.set_const(w_cls, "RDONLY", space.newint(os.O_RDONLY))
        space.set_const(w_cls, "WRONLY", space.newint(os.O_WRONLY))
        space.set_const(w_cls, "RDWR", space.newint(os.O_RDWR))
        space.set_const(w_cls, "APPEND", space.newint(os.O_APPEND))
        space.set_const(w_cls, "CREAT", space.newint(os.O_CREAT))
        space.set_const(w_cls, "EXCL", space.newint(os.O_EXCL))
        space.set_const(w_cls, "TRUNC", space.newint(os.O_TRUNC))

        space.set_const(w_cls, "Stat", space.getclassfor(W_FileStatObject))

    @classdef.singleton_method("allocate")
    def method_allocate(self, space, args_w):
        return W_FileObject(space)

    @classdef.singleton_method("size?", name="path")
    def singleton_method_size_p(self, space, name):
        try:
            stat_val = os.stat(name)
        except OSError:
            return space.w_nil
        return space.w_nil if stat_val.st_size == 0 else space.newint_or_bigint(stat_val.st_size)

    @classdef.singleton_method("unlink")
    @classdef.singleton_method("delete")
    def singleton_method_delete(self, space, args_w):
        for w_path in args_w:
            path = Coerce.path(space, w_path)
            try:
                os.unlink(path)
            except OSError as e:
                raise error_for_oserror(space, e)
        return space.newint(len(args_w))

    @classdef.method("initialize", filename="path")
    def method_initialize(self, space, filename, w_mode=None, w_perm_or_opt=None, w_opt=None):
        if w_mode is None:
            w_mode = space.w_nil
        if w_perm_or_opt is None:
            w_perm_or_opt = space.w_nil
        if w_opt is None:
            w_opt = space.w_nil
        if isinstance(w_perm_or_opt, W_HashObject):
            assert w_opt is space.w_nil
            perm = 0665
            w_opt = w_perm_or_opt
        elif w_opt is not space.w_nil:
            perm = space.int_w(w_perm_or_opt)
        else:
            perm = 0665
        mode = map_filemode(space, w_mode)
        if w_perm_or_opt is not space.w_nil or w_opt is not space.w_nil:
            raise space.error(space.w_NotImplementedError, "options hash or permissions for File.new")
        try:
            self.fd = os.open(filename, mode, perm)
        except OSError as e:
            raise error_for_oserror(space, e)
        return self

    @classdef.singleton_method("dirname", path="path")
    def method_dirname(self, space, path):
        separators = ["/"]
        if IS_WINDOWS:
            separators.append("\\")

        has_separator = False
        for separator in separators:
            if separator in path:
                has_separator = True
                break
        if not has_separator:
            return space.newstr_fromstr(".")

        if path in separators:
            return space.newstr_fromstr("/")

        while path and path[-1] in separators:
            newlen = len(path) - 1
            assert newlen >= 0
            path = path[:newlen]

        idx = -1
        for separator in separators:
            idx = max(idx, path.rfind(separator))
        while idx > 0 and path[idx - 1] in separators:
            idx -= 1
        if idx <= 0:
            return space.newstr_fromstr("/")
        assert idx >= 0
        return space.newstr_fromstr(path[:idx])

    @classdef.singleton_method("expand_path", path="path")
    def method_expand_path(self, space, path, w_dir=None):
        if path and path[0] == "~":
            if len(path) >= 2 and path[1] == "/":
                path = os.environ["HOME"] + path[1:]
            elif len(path) < 2:
                return space.newstr_fromstr(os.environ["HOME"])
            else:
                raise NotImplementedError
        elif not path or path[0] != "/":
            if w_dir is not None and w_dir is not space.w_nil:
                dir = space.str_w(space.send(self, space.newsymbol("expand_path"), [w_dir]))
            else:
                dir = os.getcwd()

            path = dir + "/" + path

        items = []
        path = path.replace("\\", "/")
        parts = path.split("/")
        for part in parts:
            if part == "..":
                items.pop()
            elif part and part != ".":
                items.append(part)

{'fraction_non_alphanumeric': 0.10584334852880233, 'fraction_numerical': 0.00613344384583506, 'mean_word_length': 2.3553948832035596, 'pattern_counts': {'":': 10, '<': 6, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2105799', 'n_tokens_mistral': 4124, 'n_tokens_neox': 3970, 'n_words': 837}