"""
Saving and loading in Python3 binary objects, strings, lists, tab-CSV and dictionaries
Sauvegarder et charger en Python3 des objets binaires, chaînes, listes, tab-CSV et dictionnaires
Jean-Christophe Beumier - 2022.12.01 - Ver0.6 - adds : codings
http://jchr.be/python/v2f.htm for informations (french)

This program is free software. It comes without any warranty.
You can modify it, and redistribute it, under GPLv3 conditions.
https://www.gnu.org/licenses/gpl-3.0.en.html
Ce programme est libre, utilisez-le sous votre propre responsabilité.
Vous pouvez le modifier et le distribuer, aux conditions GPLv3.
"""

def coding() :
  """
  These codes are used to force a specific encodage of a texte
  Ces codes sont utilisés pour forcer l'encodage d'un texte
  """
  print("""
  utf-8 (default) : universal / universel (Unicode set)
  iso-8859-1 – latin1 – latin-1 : Western Europa / Europe occidentale
  iso-8859-15 – latin9 : slightly modified latin-1 légèrement modifié
  cp1252 : Windows enhanced latin-1 (char 160-191 are used)
  ISO-8859-2 – latin-2 : Eastern Europa / Europe de l'est
  ISO-8859-3 – latin-3 : Southern Europa / Europe du sud
  ISO-8859-4 – latin-4 : Northern Europa / Europe du nord
  ISO-8859-5 : Cyrillic / cyrillique
  ISO-8859-6 : Arabic / arabe
  ISO-8859-7 : Monotonic Greek / grec monotonique
  ISO-8859-8 : Hebrew / hébreu
  ISO-8859-9 – latin-5 : Turkish, Kurdish / turc, kurde
  ISO-8859-10 – latin-6 : Northern Europa / Europe du nord
  ISO-8859-11 : thai
  ISO-8859-13 – latin-7 : Baltic / baltique
  ISO-8859-14 – latin-8 : Celtic / celtique
  ISO-8859-16 – latin-10 : Southern-Eastern Europa / Europe du sud-est
  """)

### Killing spaces

def strip(chaine) :
  """
    v2f.strip(my_string)
  Remove spaces before and after a string ; who ever knows?
  Enlève les espaces entourant une chaîne ; sait-on jamais?
  """
  while len(chaine) and (chaine[0] ==" ") :
    chaine =chaine[1:]
  while len(chaine) and (chaine[-1] ==" ") :
    chaine =chaine[:-1]
  return chaine

### Blobs section (binary objects)

def saveB(fichier, blob) :
  """
     saveB(my_file, my_blob)
  Save a Blob (binary object, bytes from 0 to 255) in a file
  Sauvegarde un Blob (valeurs de 0 à 255) dans un fichier
  """
  with open(fichier, "wb") as fd :
    fd.write(blob)

def loadB(fichier) :
  """
     my_blob =loadB(filename)
  Load a Blob (binary file, bytes values from 0 to 255) from a file
  Charge un Blob (fichier binaire, octets de valeurs de 0 à 255) d'un fichier
  """
  with open(fichier, "rb") as fd :
    return fd.read()

### Section Strings

def saveS(fichier, chaine, mode="utf-8") :
  """
     saveS(my_file, my_string)
  Saves a String into a human readable file (utf-8 or your prefered encoding)
  Sauvegarde une chaîne dans un fichier lisible par un être humain, UTF-8 par
   défaut ou selon l'encodage désiré
  """
  with open(fichier, "w", encoding=mode) as fd :
    return fd.write(chaine)

def loadS(fichier, mode="") :
  """
     my_string =loadS(filename)
  Loads a String from a human readable file. You can mention an encoding,
   otherwise it's UTF-8 or CP1252 (an improved ISO-8859-1 / latin-1 )

  Charge une chaîne d'un fichier texte. Il est possible de préciser un encodage,
   sinon c'est UTF-8 qui est tenté, et puis CP1252 (extension du ISO-8859-1 / latin-1)
  """
  if mode :
    with open(fichier, "r", encoding=mode) as fd :
      return fd.read()
  else :
    try :
      with open(fichier, "r", encoding="utf-8") as fd :
        return fd.read()
    except :
      try :
        with open(fichier, "r", encoding="cp1252") as fd :
          return fd.read()
      except :
        return "Pas d'encodage rencontré. Désolé."


def addS(fichier, chaine) :
  """
     addS(filename, my_string)
  Adds a String to a file ; creation if inexistant
  Ajoute une chaîne à un fichier, le crée au besoin
  """
  with open(fichier, "a") as fd :
    fd.write(chaine)

### Section Listes

def saveL(fichier, liste) :
  """
     saveL(my_file, my_list)
  Saves a chain-list in a string form, with line separators ; strings are 'quoted'
  Sauve une liste sous forme de chaîne avec sauts de ligne ; chaînes avec guillemets
  """
  for i in range(len(liste)) :
    liste[i] =repr(liste[i])
  saveS(fichier, "\n".join(liste))

def loadL(fichier) :
  """
     my_list =loadL(filename)
  Loads a String file into a list
  Charge un fichier chaîne en liste
  """
  liste =loadS(fichier).split("\n")
  for i in range(len(liste)-1, -1, -1) : # permitting pop()
    item =strip(liste[i])
    if item =="" :
      liste.pop(i) ; continue
    liste[i] =eval(item)
  return liste

### Section Tables

def saveT(fichier, liste, mode="") :
  """
     saveT(my_file, my_list)
  Saves a CSV table, TSV (Tab Separated Values)
  Sauvegarde une table CSV, TSV (valeurs séparées par des tabulations)
     saveT(my_file, my_list, "-")
  Doesn't quote the strings
  N'entoure pas les chaînes de guillemets
     saveT(my_file, my_list, ",")
  Transforms the number dots to comma (french decimal notation)
  Transforme les point décimaux en virgule (export pour LibreOffice Calc)
  """
  for i in range(len(liste)) :
    rangee =liste[i]
    for j in range(len(rangee)) :
      if "-" not in mode or type(rangee[j]) !=type("") :
        rangee[j] =repr(rangee[j]) # string transformation
        if "," in mode :
          rangee[j] =rangee[j].replace(".", ",")
    liste[i] ="\t".join(rangee)
  saveS(fichier, "\n".join(liste))

def loadT(fichier) :
  """
     my_table =loadT(filename)
  Loads a TSV (Tab-Separated-Value) file into a list (rows) of lists (cells)
  Charge un fichier TSV Valeurs séparées par des Tab (liste de listes)
  """
  chaine =loadS(fichier)
  liste =chaine.split("\n")
  for i in range(len(liste)-1, -1, -1) :
    ligne =strip(liste[i])
    if ligne =="" :
      liste.pop(i) ; continue
    cells =liste[i].split("\t")
    for j in range(len(cells)) :
      cells[j] =eval(cells[j])
    liste[i] =cells
  return liste

### Section Dictionnaires

def saveD(fichier, dico) :
  """
     saveL(my_file, my_dic)
  Saves a dictionary into lines 'key + tab + value'
  Sauve un dictionnaire sous forme de lignes "clé + tab + valeur"
  """
  liste =[]
  for i in dico :
    liste +=["%s\t%s" %(repr(i), repr(dico[i]))]
  chaine ="\n".join(liste)
  saveS(fichier, chaine)


def loadD(fichier) :
  """
     my_dic =loadD(filename)
  Reads a lines-file 'keys + tab + values' to fill a dictionary
  Charge un fichier de lignes "clé + tab + valeur" pour constituer un dictionnaire
  """
  chaine =loadS(fichier)
  liste =chaine.split("\n")
  dico ={}
  for i in range(len(liste)) :
    morceaux =liste[i].split("\t")
    if morceaux ==[''] : continue
    if len(morceaux) ==1 :
      print("!!! %s : Clé sans valeur" %morceaux[0])
      continue
    if len(morceaux) > 2 :
      print("!!! Trop de valeurs sur la ligne :", morceaux)
      continue
    cle, val =morceaux
    dico[eval(cle)] =eval(val) #
  return dico

### Imports with différents cell-separators

def guess(chaine) :
  """
     v2f.guess(my_string)
  Tries to guess the character used to separate the fields of a CSV
  Tente de déterminer le caractère utilisé pour séparer les champs d'un CSV
  """
  cs =[]
  cs +=[[chaine.count("\t"), "\t"]]
  cs +=[[chaine.count(";"), ";"]]
  cs +=[[chaine.count(","), ","]]
  cs =sorted(cs, reverse =True)
  return cs[0][1]

def impT(fichier) :
  """
     v2f.impT(filename)
  Loads a CSV (Comma/Semi-comma/Tabulation-Separated File) into a list, guessing cellseps
  Charge un fichier CSV dans une table, en devinant les séparateurs de cellules
  """
  chaine =loadS(fichier)
  cellsep =guess(chaine)
  liste =chaine.split("\n")
  for i in range(len(liste)) :
    cells =liste[i].split(cellsep)
    for j in range(len(cells)) :
      cell =cells[j]
      try :
        cell =eval(cell)
      except :
        pass
      cells[j] =cell
    liste[i] =cells
  return liste

def impD(fichier) :
  """
     v2f.impD(filename)
  Loads a file into a python dictionnary, guessing cellseps
  Charge un fichier dans un dictionnaire, en devinant les séparateurs de cellules
  """
  chaine =loadS(fichier)
  cellsep =guess(chaine)
  liste =chaine.split("\n")
  dico ={}
  for i in range(len(liste)) :
    chaine =liste[i]
    if strip(chaine) =="" : continue
    cells =chaine.split(cellsep)
    lon =len(cells)
    if lon != 2 :
      if lon == 1 : print("Ligne à un seul elément:", chaine)
      if lon > 2 : print("Ligne à plusieurs valeurs:", chaine)
      continue
    cle =cells[0] ; val =cells[1]
    try :
      cle =eval(cle)
    except :
      pass
    try :
      val =eval(val)
    except :
      pass
    if dico.get(cle) :
      print("Clé ~%s~ déjà employée, l'ancienne valeur ~%s~ écrasée par ~%s~" %(cle, dico[cle], val))
    dico[cle] =val
  return dico

def what(fichier) :
  """  what("myfile")
  Attempts to determine the kind of file (binary/text, separators...)
  Tente de déterminer le type de fichier (binaire/texte, séparateurs...)
  """
  octets =bytes(loadB(fichier))
  lsr =octets.count(13) ; lsn =octets.count(10)
  coct =[0] *256
  lon =len(octets) ; lon0 =min(lon, 100000)
  for i in range(lon0) :
    coct[octets[i]] +=1

  if sum(coct[0:9]) > 0 and sum(coct[14:26]) > 0 :
    print("\n%s is a binary file; %s 'null bytes' on %s (%s%%)" %(fichier, coct[0], lon0, round(coct[0] *100 /lon0, 2)))
  else :
    print("\n«%s» is probably a text file" %fichier)
    if lsr ==0 and lsn ==0 :
      print("No line separator")
    else :
      if lsr ==lsn :
        print("%s line separator '\\r\\n' (bytes 13 + 10) Atari or Microsoft" %lsn) ; lsep ="\\r\\n"
      elif lsr ==0 :
        print("%s linesep '\\n' (octet 10) UNIX, Linux or McOSX" %lsn) ; lsep ="\\n"
      else :
        print("%s linesep '\\r' (octet 13) Amiga or Mc Classic" %lsr) ; lsep ="\\r"
      nspc =coct[32]; ntab =coct[9] ; npvi =coct[59] ; nvrg =coct[44]
      lignes =str(octets).split(lsep)
      if nspc ==0 :
        print("No space found")
      else :
        print("%s spaces on %s bytes (%s%%)" %(nspc, lon0, round(nspc *100 /lon0, 2)))
      if ntab ==0 :
        print("No tab")
      else :
        ltab =[]
        for i in lignes :
          ltab += [i.count("\\t")]
        mintab =min(ltab) ; maxtab =max(ltab)
        if mintab ==maxtab :
          print("%s tab(s) ; %s tab(s) a line" %(ntab, mintab))
        else :
          print("%s tab(s) ; de %s à %s tab(s) a line" %(ntab, mintab, maxtab))
      if npvi ==0 :
        print("No semicol")
      else :
        lpvi =[]
        for i in lignes :
          lpvi += [i.count(";")]
        minpvi =min(lpvi) ; maxpvi =max(lpvi)
        if minpvi ==maxpvi :
          print("%s semicol(s) ; %s semicol(s) a line" %(npvi, minpvi))
        else :
          print("%s semicol(s) ; from %s to %s semicol(s) a line" %(npvi, minpvi, maxpvi))
      if nvrg ==0 :
        print("No comma")
      else :
        lvrg =[]
        for i in lignes :
          lvrg += [i.count(",")]
        minvrg =min(lvrg) ; maxvrg =max(lvrg)
        if minvrg ==maxvrg :
          print("%s comma(s) ; %s comma(s) a line" %(nvrg, minvrg))
        else :
          print("%s comma(s) ; from %s to %s comma(s) a line" %(nvrg, minvrg, maxvrg))
  print("\nBytes:", end ="")
  for i in range(16) :
    print(" _%X  " %i, end ="")
  print()
  for i in range(16) :
    print("%2X_ " %i, end ="")
    for j in range(16) :
       print("%5d" %coct[i *16 +j], end ="")
    print()

