#!/usr/bin/env python # # Copyright 2011 Ville Rantanen # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # '''SC based CSV editor.''' __author__ = "Ville Rantanen" __version__ = "0.1" import sys,os import csv from argparse import ArgumentParser import unicodedata, re import subprocess import shutil def which(program): import os def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) fpath, fname = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None class SCReader: """ Class for reading SC files. """ def __init__(self,fileobject): self.file=fileobject self.parserre=re.compile('.* ([A-Z]+)([0-9]+) = (.*)') def _parse_row(self,string): col=None row=None content=None m=self.parserre.match(string.strip()) if m: col=self.alpha_to_column(m.group(1)) row=self.try_int(m.group(2)) content=m.group(3) return col,row,content def try_int(self,string): try: return int(string) except: return string def alpha_to_column(self,alpha): ''' Returns a column number from spreadsheet column alphabet ''' n=0 o=0 for char in alpha[::-1]: o+=(ord(char.upper())-64)*(26**n) n+=1 return int(o-1) def next(self): ''' Returns the next row in the table, three items: column, row, content''' return self._parse_row(self.reader.next()) def __iter__(self): for row in self.file: yield self._parse_row(row) class SCWriter: """ Class for writing SC files. """ def __init__(self,fileobject): self.file=fileobject self.row=0 self.col=0 self.col_lengths=[] def parse_row(self,string): self.col=0 for el in row: self.write_element(self.row,self.col,el) if len(self.col_lengths) <= self.col: self.col_lengths.append(max(len(el),8)) else: self.col_lengths[self.col]=max(len(el),self.col_lengths[self.col]) self.col+=1 self.row+=1 def write_element(self,row,col,content): colalpha=self.column_to_alpha(col) content=content.strip('"') if self.is_num(content): self.file.write('let '+colalpha+str(row)+' = ' + str(self.to_num(content))+'\n') else: self.file.write('rightstring '+colalpha+str(row)+' = "' + content + '"\n') def column_to_alpha(self,column): ''' Returns a column alphabet from column number ''' o=chr(column+64+1) if column>25: return self.column_to_alpha((column / 26) -1) + self.column_to_alpha(column % 26); return o def write_row(self,row): ''' Writes a row as a SC file part ''' self.parse_row(row) def write_formats(self): for col in range(len(self.col_lengths)): self.file.write('format '+self.column_to_alpha(col)+' '+str(self.col_lengths[col])+' 2 0\n') def is_num(self,string): ''' returns the True if string can be converted to number safely ''' try: num=int(string) return True except: pass try: num=float(string) return True except: pass return False def to_num(self,string): ''' returns the number in the correct data type if string can be converted to number safely ''' try: num=int(string) return num except: pass try: num=float(string) return num except: pass return string def setup_options(): ''' Setup the command line options ''' parser=ArgumentParser() parser.add_argument("-v",action='version', version=__version__) parser.add_argument("--version",action='version', version=__version__) parser.add_argument("-b",action="store_false",dest="backup",default=True, help="Do not create a backup file.") parser.add_argument("-i",type=str,dest="delimiter",default="\t", help="Input delimiter for the CSV, default: [tab]") parser.add_argument("-D",action="store_true",dest="debug",default=False, help="Debug mode, i.e. do not delete the SC file.") parser.add_argument("csv",type=str,action="store", help="CSV file to edit") options=parser.parse_args() return options def csv_write(screader,fileout): ''' writes a CSV from SCReader iterator ''' content=[] rows=0 cols=0 for row in screader: if row[0]!=None: content.append(row) rows=max(row[1],rows) cols=max(row[0],cols) table=[] for r in range(rows+1): table.append([]) for c in range(cols+1): table[r].append('') for e in content: table[e[1]][e[0]]=e[2] for row in table: fileout.write('\t'.join(row)+'\n') if not which('sc'): print('You don\'t seem to have "sc" installed!') print('sudo apt-get install sc') sys.exit(1) opts=setup_options() f_bkp=opts.csv+'.bkp' f_sc=opts.csv+'.sc' f_sc_tmp=opts.csv+'.sc.tmp' # copy a backup file if opts.backup: shutil.copyfile(opts.csv, f_bkp) # Convert CSV -> SC f_sc_w=open(f_sc,'wt') f_csv_r=open(opts.csv,'rt') csv_reader = csv.reader(f_csv_r, delimiter=opts.delimiter, doublequote=False, escapechar='\\', quoting=csv.QUOTE_NONE) sc_writer=SCWriter(f_sc_w) for row in csv_reader: if len(row)>0: sc_writer.write_row(row) sc_writer.write_formats() f_sc_w.close() f_csv_r.close() # Launch sc subprocess.call(['sc',f_sc]) # Calculate values process = subprocess.Popen(['sc','-v','-P','%',f_sc], shell=False, stdout=subprocess.PIPE) f_cs_tmp_w=open(f_sc_tmp,'wt') for l in process.stdout.readlines(): f_cs_tmp_w.write(l) f_cs_tmp_w.close() # Convert SC -> CSV f_sc_r=open(f_sc_tmp,'rt') f_csv_w=open(opts.csv,'wt') sc_reader=SCReader(f_sc_r) csv_write(sc_reader,f_csv_w) f_sc_r.close() f_csv_w.close() # Delete SC if not in debug mode if not opts.debug: os.remove(f_sc) os.remove(f_sc_tmp) if os.path.isfile(f_sc+'~'): os.remove(f_sc+'~')