# HG changeset patch
# User Markus Mützel <markus.muetzel@gmx.de>
# Date 1600442132 -7200
#      Fri Sep 18 17:15:32 2020 +0200
# Node ID 005dcb5478927c9d29b1e4785c72b9914bb702f5
# Parent  b6bed53f059f0fee96af90df3beecf6651e70aef
Add function "dir_encoding" to set a file encoding per directory (bug #49685).

* corefcn/input.h (input_system): Add public functions "dir_encoding" and
  "set_dir_encoding". Add private map "m_dir_encoding".
* corefcn/input.cc (input_system::dir_encoding, input_system::set_dir_encoding):
  Add new functions.
  (base_reader, file_reader, input_reader): Add new constructors that accept an
  encoding. Add private property "m_encoding".
  (Fdir_encoding): New function.
* parse-tree/lex.h (lexer): Add new constructors that accept an encoding.
* parse-tree/parse.h (parser): Add new constructors that accept an encoding.
* parse-tree/oct-parse.yy (parse_fcn_file): Pass dir_encoding down to parser.
* doc/interpreter/func.txi: Add docstring of function "dir_encoding" to manual.

diff -r b6bed53f059f -r 005dcb547892 doc/interpreter/func.txi
--- a/doc/interpreter/func.txi	Fri Sep 18 19:17:12 2020 +0200
+++ b/doc/interpreter/func.txi	Fri Sep 18 17:15:32 2020 +0200
@@ -924,6 +924,8 @@
 
 @DOCSTRING(dir_in_loadpath)
 
+@DOCSTRING(dir_encoding)
+
 @node Subfunctions
 @subsection Subfunctions
 
diff -r b6bed53f059f -r 005dcb547892 libinterp/corefcn/input.cc
--- a/libinterp/corefcn/input.cc	Fri Sep 18 19:17:12 2020 +0200
+++ b/libinterp/corefcn/input.cc	Fri Sep 18 17:15:32 2020 +0200
@@ -541,6 +541,84 @@
     return retval;
   }
 
+  // Get part of the directory that would be added to the load path
+  static std::string load_path_dir (const std::string& dir)
+  {
+    std::string lp_dir = dir;
+
+    // get part of the path that would be added to the load path
+    // remove trailing filesep
+    size_t ipos = lp_dir.find_last_not_of (sys::file_ops::dir_sep_chars ());
+    if (ipos != std::string::npos)
+      lp_dir = lp_dir.erase (ipos+1);
+    // strip private folder
+    ipos = lp_dir.find_last_of (sys::file_ops::dir_sep_chars ());
+    if (ipos != std::string::npos
+        && lp_dir.substr (ipos+1).compare ("private") == 0)
+      {
+        lp_dir = lp_dir.erase (ipos);
+        ipos = lp_dir.find_last_of (sys::file_ops::dir_sep_chars ());
+      }
+    // strip @class folder
+    if (ipos != std::string::npos && lp_dir[ipos+1] == '@')
+      {
+        lp_dir = lp_dir.erase (ipos);
+        ipos = lp_dir.find_last_of (sys::file_ops::dir_sep_chars ());
+      }
+    // strip (nested) +namespace folders
+    while (ipos != std::string::npos && lp_dir[ipos+1] == '+')
+      {
+        lp_dir = lp_dir.erase (ipos);
+        ipos = lp_dir.find_last_of (sys::file_ops::dir_sep_chars ());
+      }
+
+    return lp_dir;
+  }
+
+  std::string input_system::dir_encoding (const std::string& dir)
+  {
+    std::string enc = m_mfile_encoding;
+
+    auto enc_it = m_dir_encoding.find (load_path_dir (dir));
+    if (enc_it != m_dir_encoding.end ())
+      enc = enc_it->second;
+
+    return enc;
+  }
+
+  void input_system::set_dir_encoding (const std::string& dir,
+                                       std::string& enc)
+  {
+    // use lower case
+    std::transform (enc.begin (), enc.end (), enc.begin (), ::tolower);
+
+    if (enc.compare ("utf-8"))
+      {
+        // Check for valid encoding name.
+        // FIXME: This will probably not happen very often and opening the
+        //        encoder doesn't take long.
+        //        Should we cache working encoding identifiers anyway?
+        void *codec
+          = octave_iconv_open_wrapper (enc.c_str (), "utf-8");
+
+        if (codec == reinterpret_cast<void *> (-1))
+          {
+            if (errno == EINVAL)
+              error ("dir_encoding: conversion from encoding '%s' "
+                     "not supported", enc.c_str ());
+            else
+              error ("dir_encoding: error %d opening encoding '%s'.",
+                     errno, enc.c_str ());
+          }
+        else
+          octave_iconv_close_wrapper (codec);
+      }
+
+    m_dir_encoding[load_path_dir (dir)] = enc;
+
+    return;
+  }
+
   bool input_system::yes_or_no (const std::string& prompt)
   {
     std::string prompt_string = prompt + "(yes or no) ";
@@ -808,7 +886,14 @@
   public:
 
     file_reader (interpreter& interp, FILE *f_arg)
-      : base_reader (interp), m_file (f_arg) { }
+      : base_reader (interp), m_file (f_arg)
+    {
+      octave::input_system& input_sys = interp.get_input_system ();
+      m_encoding = input_sys.mfile_encoding ();
+    }
+
+    file_reader (interpreter& interp, FILE *f_arg, const std::string& enc)
+      : base_reader (interp), m_file (f_arg), m_encoding (enc) { }
 
     std::string get_input (const std::string& prompt, bool& eof);
 
@@ -820,6 +905,8 @@
 
     FILE *m_file;
 
+    std::string m_encoding;
+
     static const std::string s_in_src;
   };
 
@@ -853,6 +940,10 @@
     : m_rep (new file_reader (interp, file))
   { }
 
+  input_reader::input_reader (interpreter& interp, FILE *file, const std::string& enc)
+    : m_rep (new file_reader (interp, file, enc))
+  { }
+
   input_reader::input_reader (interpreter& interp, const std::string& str)
     : m_rep (new eval_string_reader (interp, str))
   { }
@@ -882,9 +973,15 @@
 
     std::string src_str = octave_fgets (m_file, eof);
 
-    input_system& input_sys = m_interpreter.get_input_system ();
+    std::string mfile_encoding;
 
-    std::string mfile_encoding = input_sys.mfile_encoding ();
+    if (m_encoding.empty ())
+      {
+        input_system& input_sys = m_interpreter.get_input_system ();
+        mfile_encoding = input_sys.mfile_encoding ();
+      }
+    else
+      mfile_encoding = m_encoding;
 
     std::string encoding;
     if (mfile_encoding.compare ("system") == 0)
@@ -1403,3 +1500,58 @@
 
   return input_sys.mfile_encoding (args, nargout);
 }
+
+DEFMETHOD (dir_encoding, interp, args, nargout,
+           doc: /* -*- texinfo -*-
+@deftypefn {}  {@var{current_encoding} =} dir_encoding (@var{dir})
+@deftypefnx {} {@var{prev_encoding} =} dir_encoding (@var{dir}, @var{encoding})
+Set and query the @var{encoding} that is used for reading m-files in @var{dir}.
+
+That encoding overrides the (globally set) m-file encoding.
+
+The directory @var{DIR} must match the form how it would appear in the load
+path.
+
+The currently or previously used encoding is returned in @var{current_encoding}
+or @var{prev_encoding}, respectively.
+
+This function can be used in @code{PKG_ADD} scripts to set the encoding for the
+m-files in that folder.  For example, the following line sets the encoding for
+all files in the same folder like the @code{PKG_ADD} script to ISO 8859-1
+(Latin-1):
+
+@example
+dir_encoding (fileparts (mfilename ("fullpath")), "iso8859-1");
+@end example
+
+@seealso{PKG_ADD, path, addpath}
+@end deftypefn */)
+{
+  int nargin = args.length ();
+
+  if (nargin < 1 || nargin > 2)
+    print_usage ();
+
+  std::string dir
+    = args(0).xstring_value ("dir_encoding: DIR must be a string");
+
+  octave_value retval;
+
+  octave::input_system& input_sys = interp.get_input_system ();
+
+  if (nargout > 0)
+    {
+      retval = input_sys.dir_encoding (dir);
+    }
+
+  if (nargin > 1)
+    {
+      std::string encoding
+        = args(1).xstring_value ("dir_encoding: ENCODING must be a string");
+
+      input_sys.set_dir_encoding (dir, encoding);
+    }
+
+  return ovl (retval);
+
+}
diff -r b6bed53f059f -r 005dcb547892 libinterp/corefcn/input.h
--- a/libinterp/corefcn/input.h	Fri Sep 18 19:17:12 2020 +0200
+++ b/libinterp/corefcn/input.h	Fri Sep 18 17:15:32 2020 +0200
@@ -147,6 +147,10 @@
 
     void set_mfile_encoding (const std::string& s) { m_mfile_encoding = s; }
 
+    std::string dir_encoding (const std::string& dir);
+
+    void set_dir_encoding (const std::string& dir, std::string& enc);
+
     bool yes_or_no (const std::string& prompt);
 
     std::string interactive_input (const std::string& s, bool& eof);
@@ -184,6 +188,9 @@
     // Codepage which is used to read .m files
     std::string m_mfile_encoding;
 
+    // map of directories -> used mfile encoding
+    std::map<std::string, std::string> m_dir_encoding;
+
     // If we are in debugging mode, this is the last command entered,
     // so that we can repeat the previous command if the user just
     // types RET.
@@ -241,6 +248,8 @@
 
     input_reader (interpreter& interp, FILE *file);
 
+    input_reader (interpreter& interp, FILE *file, const std::string& enc);
+
     input_reader (interpreter& interp, const std::string& str);
 
     input_reader (const input_reader& ir) = default;
diff -r b6bed53f059f -r 005dcb547892 libinterp/parse-tree/lex.h
--- a/libinterp/parse-tree/lex.h	Fri Sep 18 19:17:12 2020 +0200
+++ b/libinterp/parse-tree/lex.h	Fri Sep 18 17:15:32 2020 +0200
@@ -774,6 +774,10 @@
       : base_lexer (interp), m_reader (interp, file), m_initial_input (true)
     { }
 
+    lexer (FILE *file, interpreter& interp, const std::string& encoding)
+      : base_lexer (interp), m_reader (interp, file, encoding), m_initial_input (true)
+    { }
+
     lexer (const std::string& eval_string, interpreter& interp)
       : base_lexer (interp), m_reader (interp, eval_string),
         m_initial_input (true)
diff -r b6bed53f059f -r 005dcb547892 libinterp/parse-tree/oct-parse.yy
--- a/libinterp/parse-tree/oct-parse.yy	Fri Sep 18 19:17:12 2020 +0200
+++ b/libinterp/parse-tree/oct-parse.yy	Fri Sep 18 17:15:32 2020 +0200
@@ -4997,7 +4997,9 @@
                          fclose (ffile);
                        });
 
-    parser parser (ffile, interp);
+    // get the encoding for this folder
+    octave::input_system& input_sys = interp.get_input_system ();
+    parser parser (ffile, interp, input_sys.dir_encoding (dir_name));
 
     parser.m_curr_class_name = dispatch_type;
     parser.m_curr_package_name = package_name;
diff -r b6bed53f059f -r 005dcb547892 libinterp/parse-tree/parse.h
--- a/libinterp/parse-tree/parse.h	Fri Sep 18 19:17:12 2020 +0200
+++ b/libinterp/parse-tree/parse.h	Fri Sep 18 17:15:32 2020 +0200
@@ -610,6 +610,10 @@
       : base_parser (*(new lexer (file, interp)))
     { }
 
+    parser (FILE *file, interpreter& interp, std::string encoding)
+      : base_parser (*(new lexer (file, interp, encoding)))
+    { }
+
     parser (const std::string& eval_string, interpreter& interp)
       : base_parser (*(new lexer (eval_string, interp)))
     { }
