text: #ifndef PYTHONIC_TYPES_FILE_HPP
  #define PYTHONIC_TYPES_FILE_HPP
  
  #include "pythonic/include/types/file.hpp"
  
  #include "pythonic/types/assignable.hpp"
  #include "pythonic/utils/shared_ref.hpp"
  #include "pythonic/types/str.hpp"
  #include "pythonic/types/list.hpp"
  #include "pythonic/types/NoneType.hpp"
  #include "pythonic/types/attr.hpp"
  #include "pythonic/builtins/IOError.hpp"
  #include "pythonic/builtins/ValueError.hpp"
  #include "pythonic/builtins/RuntimeError.hpp"
  #include "pythonic/builtins/StopIteration.hpp"
  
  #include <fstream>
  #include <iterator>
  #include <cstring>
  #include <string>
  #include <cstdio>
  #include <unistd.h>
  
  PYTHONIC_NS_BEGIN
  
  namespace types
  {
  
    /// _file implementation
  
    _file::_file() : f(nullptr)
    {
    }
  
    // TODO : no check on file existance?
    _file::_file(types::str const &filename, types::str const &strmode)
        : f(fopen(filename.c_str(), strmode.c_str()))
    {
    }
  
    FILE *_file::operator*() const
    {
      return f;
    }
  
    _file::~_file()
    {
      if (f)
        fclose(f);
    }
  
    /// file implementation
  
    // Constructors
    file::file() : file_iterator(), data(utils::no_memory())
    {
    }
  
    file::file(types::str const &filename, types::str const &strmode)
        : file_iterator(), data(utils::no_memory()), mode(strmode),
          name(filename), newlines('\n')
    {
      open(filename, strmode);
      if (mode.find_first_of("r+") != -1)
        *(file_iterator *)this = file_iterator(*this);
    }
  
    // Iterators
    file::iterator file::begin()
    {
      return *this;
    }
  
    file::iterator file::end()
    {
      return {};
    }
  
    // Modifiers
    void file::open(types::str const &filename, types::str const &strmode)
    {
      const char *smode = strmode.c_str();
      // Python enforces that the mode, after stripping 'U', begins with 'r',
      // 'w' || 'a'.
      if (*smode == 'U') {
        ++smode;
      } // Not implemented yet
  
      data = utils::shared_ref<container_type>(filename, smode);
      if (!**data)
        throw types::IOError("Couldn't open file " + filename);
      is_open = true;
    }
  
    void file::close()
    {
      fclose(**data);
      data->f = nullptr;
      is_open = false;
    }
  
    bool file::closed() const
    {
      return !is_open;
    }
  
    types::str const &file::getmode() const
    {
      return mode;
    }
  
    types::str const &file::getname() const
    {
      return name;
    }
  
    types::str const &file::getnewlines() const
    {
      // Python seems to always return none... Doing the same here
      return newlines;
    }
  
    bool file::eof()
    {
      return ::feof(**data);
    }
  
    void file::flush()
    {
      if (!is_open)
        throw ValueError("I/O operation on closed file");
      fflush(**data);
    }
  
    long file::fileno() const
    {
      if (!is_open)
        throw ValueError("I/O operation on closed file");
      return ::fileno(**data);
    }
  
    bool file::isatty() const
    {
      if (!is_open)
        throw ValueError("I/O operation on closed file");
      return ::isatty(this->fileno());
    }
  
    types::str file::read(long size)
    {
      if (!is_open)
        throw ValueError("I/O operation on closed file");
      if (mode.find_first_of("r+") == -1)
        throw IOError("File not open for reading");
      if (size == 0 || (feof(**data) && mode.find_first_of("ra") == -1))
        return types::str();
      long curr_pos = tell();
      seek(0, SEEK_END);
      size = size < 0 ? tell() - curr_pos : size;
      seek(curr_pos);
      char *content = new char[size + 1];
      // This part needs a new implementation of types::str(char*) to avoid
      // unnecessary copy.
      types::str res(content, fread(content, sizeof(char), size, **data));
      delete[] content;
      return res;
    }
  
    types::str file::readline(long size)
    {
      if (!is_open)
        throw ValueError("I/O operation on closed file");
      if (mode.find_first_of("r+") == -1)
        throw IOError("File not open for reading");
      constexpr static long BUFFER_SIZE = 1024;
      types::str res;
      char read_str[BUFFER_SIZE];
  
      for (long i = 0; i < size; i += BUFFER_SIZE) {
        // +1 because we read the last chunk so we don't want to count \0
        if (fgets(read_str, std::min(BUFFER_SIZE - 1, size - i) + 1, **data))
          res += read_str;
        if (feof(**data) || res[res.size() - 1] == "\n")
          break;
      }
      return res;
    }
  
    types::list<types::str> file::readlines(long sizehint)
    {
      // Official python doc specifies that sizehint is used as a max of chars
      // But it has not been implemented in the standard python interpreter...
      types::str str;
      types::list<types::str> lst(0);
      while ((str = readline()))
        lst.push_back(str);
      return lst;
    }
  
    void file::seek(long offset, long whence)
    {
      if (!is_open)
        throw ValueError("I/O operation on closed file");
      if (whence != SEEK_SET && whence != SEEK_CUR && whence != SEEK_END)
        throw IOError("file.seek() :  Invalid argument.");
      fseek(**data, offset, whence);
    }
  
    long file::tell() const
    {
      if (!is_open)
        throw ValueError("I/O operation on closed file");
      return ftell(**data);
    }
  
    void file::truncate(long size)
    {
      if (!is_open)
        throw ValueError("I/O operation on closed file");
      if (mode.find_first_of("wa+") == -1)
        throw IOError("file.write() :  File not open for writing.");
      if (size < 0)
        size = this->tell();
      long error = ftruncate(fileno(), size);
      if (error == -1)
        throw RuntimeError(strerror(errno));
    }
  
    void file::write(types::str const &str)
    {
      if (!is_open)
        throw ValueError("I/O operation on closed file");
      if (mode.find_first_of("wa+") == -1)
        throw IOError("file.write() :  File not open for writing.");
      fwrite(str.c_str(), sizeof(char), str.size(), **data);
    }
  
    template <class T>
    void file::writelines(T const &seq)
    {
      auto end = seq.end();
      for (auto it = seq.begin(); it != end; ++it)
        write(*it);
    }
  
    /// file_iterator implementation
    // TODO : What if the file disappears before the end?
    // Like in :
    // for line in open("myfile"):
    //     print line
    file_iterator::file_iterator(file &ref)
        : f(&ref), set(false), curr(), position(ref.tell())
    {
    }
  
    file_iterator::file_iterator()
        : f(nullptr), set(false), curr(),
          position(std::numeric_limits<long>::max()){};
  
    bool file_iterator::operator==(file_iterator const &f2) const
    {
      return position == f2.position;
    }
  
    bool file_iterator::operator!=(file_iterator const &f2) const
    {
      return position != f2.position;
    }
  
    bool file_iterator::operator<(file_iterator const &f2) const
    {
      // Not really elegant...
      // Equivalent to 'return *this != f2;'
      return position < f2.position;
    }
  
    file_iterator &file_iterator::operator++()
    {
      if (f->eof())
        return *this;
      operator*();
      set = false;
      operator*();
      position = f->eof() ? std::numeric_limits<long>::max() : f->tell();
      return *this;
    }
  
    types::str file_iterator::operator*() const
    {
      if (!set) {
        curr = f->readline();
        set = true;
      }
      return curr.chars(); // to make a copy
    }
  }
  PYTHONIC_NS_END
  
  /* pythran attribute system { */
  PYTHONIC_NS_BEGIN
  
  namespace builtins
  {
    bool getattr(types::attr::CLOSED, types::file const &f)
    {
      return f.closed();
    }
  
    types::str const &getattr(types::attr::MODE, types::file const &f)
    {
      return f.getmode();
    }
  
    types::str const &getattr(types::attr::NAME, types::file const &f)
    {
      return f.getname();
    }
  
    // Python seems to always return none... Doing the same.
    types::none_type getattr(types::attr::NEWLINES, types::file const &f)
    {
      return builtins::None;
    }
  }
  PYTHONIC_NS_END
  
  /* } */
  
  #endif
  