Orb/python/doxygen/filerenamer.py
changeset 1 82f11024044a
parent 0 42188c7ea2d9
equal deleted inserted replaced
0:42188c7ea2d9 1:82f11024044a
    13 import os.path
    13 import os.path
    14 import sys
    14 import sys
    15 import unittest
    15 import unittest
    16 import xml
    16 import xml
    17 import stat
    17 import stat
       
    18 import logging
    18 from cStringIO import StringIO
    19 from cStringIO import StringIO
    19 from xml.etree import ElementTree as etree
    20 from xml.etree import ElementTree as etree
    20 from lib import scan, main
    21 from lib import scan, main, XmlParser, StubXmlParser
    21 
    22 from optparse import OptionParser
    22 
    23 
    23 __version__ = '0.1'
    24 __version__ = '0.1'
    24 
    25 
    25 
    26 
    26 UNSAFE_CHARS = ("\n", "\t", ":", "?", ",", "=", ".", "\\", "/", "[", "]", "|", "<", ">", "+", ";", '"', "-")
    27 UNSAFE_CHARS = ("\n", "\t", ":", "?", ",", "=", ".", "\\", "/", "[", "]", "|", "<", ">", "+", ";", '"', "-")
    27 
       
    28 
       
    29 class XmlParser(object):
       
    30     """
       
    31     Simple class that reads an XML and returns its id
       
    32 
       
    33     >>> xp = XmlParser()
       
    34     >>> xp.parse(StringIO("<root id='rootid'>some content</root>"))
       
    35     'rootid'
       
    36     """
       
    37     def parse(self, xmlfile):
       
    38         try:
       
    39             root = etree.parse(xmlfile).getroot()
       
    40         except xml.parsers.expat.ExpatError, e:
       
    41             sys.stderr.write("ERROR: %s could not be parse: %s\n" % (xmlfile, str(e)))
       
    42             return ""
       
    43         if 'id' not in root.attrib:
       
    44             return ""
       
    45         return root.attrib['id']
       
    46 
    28 
    47 
    29 
    48 class FileRenamer(object):
    30 class FileRenamer(object):
    49     """
    31     """
    50     Given an xml file this class returns a MODE compatable filename
    32     Given an xml file this class returns a MODE compatable filename
    51 
    33 
    52     >>> fr = FileRenamer(xmlparser=StubXmlParser())
    34     >>> fr = FileRenamer(xmlparser=StubXmlParser())
    53     >>> fr.rename(r"c:\\temp\\xml\\class_c_active_scheduler.xml")
    35     >>> fr.rename(r"c:\\temp\\xml\\class_c_active_scheduler.xml")
    54     'class_c_active_scheduler=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference'
    36     'class_c_active_scheduler=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference'
    55     """
    37     """
    56     def __init__(self, xmlparser=XmlParser()):
    38     def __init__(self, xmlparser=XmlParser(), publishing_target="mode"):
    57         self.parser = xmlparser
    39         self.parser = xmlparser
       
    40         self.publishing_target = publishing_target
    58     
    41     
    59     def _escape(self, filename):
    42     def _escape(self, filename):
    60         for char in UNSAFE_CHARS:
    43         for char in UNSAFE_CHARS:
    61             filename = filename.replace(char, "")            
    44             filename = filename.replace(char, "")            
    62         filename = filename.encode('unicode-escape', 'ignore')
    45         filename = filename.encode('unicode-escape', 'ignore')
    72             Test-Document=GUID-1234=1=en=.reference
    55             Test-Document=GUID-1234=1=en=.reference
    73         """
    56         """
    74         id = self.parser.parse(xmlfile)
    57         id = self.parser.parse(xmlfile)
    75         filename = os.path.basename(xmlfile)
    58         filename = os.path.basename(xmlfile)
    76         filename, ext = os.path.splitext(filename)
    59         filename, ext = os.path.splitext(filename)
    77         filename = self._escape(filename) 
    60         if self.publishing_target == "mode":
    78         newfilename = "=".join((filename, id, '1', 'en', ''))
    61             filename = self._escape(filename) 
    79         ext = ext if ext == ".ditamap" else ".reference"
    62             newfilename = "=".join((filename, id, '1', 'en', ''))
       
    63             ext = ext if ext == ".ditamap" else ".reference"
       
    64         elif self.publishing_target == "ditaot":
       
    65             newfilename = id
       
    66             ext = ext = ext if ext == ".ditamap" else ".xml"
    80         return newfilename + ext
    67         return newfilename + ext
    81 
    68 
    82 
    69 
    83 def rename(indir):
    70 def rename(indir, publishing_target):
    84     fr = FileRenamer()
    71     fr = FileRenamer(publishing_target=publishing_target)
    85     for filepath in scan(indir):
    72     for filepath in scan(indir):
    86         newfilename = os.path.join(os.path.dirname(filepath), fr.rename(filepath))
    73         newfilename = os.path.join(os.path.dirname(filepath), fr.rename(filepath))
    87         os.chmod(filepath, stat.S_IWRITE)
    74         try:
    88         #print "Renaming %s to %s" % (filepath, newfilename)
    75             os.chmod(filepath, stat.S_IWRITE)
    89         os.rename(filepath, newfilename)
    76         except Exception, e:
       
    77             logging.error('Unable to make file \"%s\" writable, error was: %s' % (filepath, e))
       
    78             continue
       
    79         else:
       
    80             logging.debug("Renaming %s to %s" % (filepath, newfilename))
       
    81             try:
       
    82                 os.rename(filepath, newfilename)
       
    83             except Exception, e:
       
    84                 logging.error('Unable to rename file \"%s\" to \"%s\", error was: %s' % (filepath, newfilename, e))
    90 
    85 
       
    86 def main():        
       
    87     usage = "usage: %prog <Path to the XML content> <publishing_target>\n"
       
    88     parser = OptionParser(usage, version='%prog ' + __version__)
       
    89     parser.add_option("-p", dest="publishing_target", type="choice", choices=["mode", "ditaot"], default="mode", 
       
    90                           help="Publishing Target: mode|ditaot, [default: %default]")
       
    91     parser.add_option("-l", "--loglevel", type="int", default=30, help="Log Level (debug=10, info=20, warning=30, [error=40], critical=50)")
       
    92     (options, args) = parser.parse_args()
       
    93     if len(args) < 1:
       
    94         parser.print_help()
       
    95         parser.error("Please supply the path to the XML content")
       
    96         
       
    97     if options.loglevel:
       
    98         logging.basicConfig(level=options.loglevel)
       
    99     
       
   100     rename(args[0],options.publishing_target)
    91 
   101 
    92 if __name__ == '__main__':
   102 if __name__ == '__main__':
    93     sys.exit(main(rename, __version__))
   103     sys.exit(main())
    94 
   104 
    95 ######################################
   105 ######################################
    96 # Test code
   106 # Test code
    97 ######################################
   107 ######################################
    98 class StubXmlParser(object):
       
    99     def parse(self, path):
       
   100         return "GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F"
       
   101 
       
   102 
       
   103 class TestXmlParser(unittest.TestCase):
       
   104     def test_i_issue_a_warning_and_continue_if_a_file_is_invalid(self):
       
   105         xml = XmlParser()
       
   106         try:
       
   107             xml.parse(StringIO("<foo><bar</foo>"))
       
   108         except Exception:
       
   109             self.fail("I shouldn't have raised an exception") 
       
   110 
       
   111     def test_i_issue_a_warning_and_continue_if_a_file_does_not_have_an_id(self):
       
   112         xml = XmlParser()
       
   113         try:
       
   114             id = xml.parse(StringIO(brokencxxclass))
       
   115         except Exception:
       
   116             self.fail("I shouldn't have raised an exception")
       
   117         self.assertTrue(id == "") 
       
   118 
       
   119     def test_i_return_a_files_id(self):
       
   120         xml = XmlParser()
       
   121         id = xml.parse(StringIO(cxxclass))
       
   122         self.assertTrue(id == "class_c_active_scheduler")
       
   123  
       
   124 
   108 
   125 class TestFileRenamer(unittest.TestCase):
   109 class TestFileRenamer(unittest.TestCase):
   126     def test_i_can_return_a_files_new_name(self):
   110     def test_i_can_return_a_files_new_mode_name(self):
   127         fr = FileRenamer(xmlparser=StubXmlParser())
   111         fr = FileRenamer(xmlparser=StubXmlParser(),publishing_target="mode")
   128         newfile = fr.rename("hello.xml")
   112         newfile = fr.rename("hello.xml")
   129         self.assertTrue(newfile == "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference")
   113         self.assertTrue(newfile == "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference")
   130 
   114 
   131     def test_i_can_return_a_ditamaps_new_name(self):
   115     def test_i_can_return_a_ditamaps_new_mode_name(self,publishing_target="mode"):
   132         fr = FileRenamer(xmlparser=StubXmlParser())
   116         fr = FileRenamer(xmlparser=StubXmlParser())
   133         newfile = fr.rename("hello.ditamap")
   117         newfile = fr.rename("hello.ditamap")
   134         self.assertTrue(newfile == "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.ditamap")
   118         self.assertTrue(newfile == "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.ditamap")
   135 
   119 
   136 
   120 
   140         self.assertTrue(newfile == "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference")
   124         self.assertTrue(newfile == "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference")
   141         
   125         
   142     def test_i_can_remove_incompatable_characters_from_a_filename(self):
   126     def test_i_can_remove_incompatable_characters_from_a_filename(self):
   143         fr = FileRenamer(xmlparser=StubXmlParser())
   127         fr = FileRenamer(xmlparser=StubXmlParser())
   144         newfile = fr.rename("hello:?,=..xml")
   128         newfile = fr.rename("hello:?,=..xml")
   145         self.assertTrue(newfile == "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference")        
   129         self.assertTrue(newfile , "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference")        
   146 
   130 
   147     
   131     def test_i_can_return_a_files_new_ditaot_name(self):
   148 brokencxxclass = """<?xml version='1.0' encoding='UTF-8' standalone='no'?>
   132         fr = FileRenamer(xmlparser=StubXmlParser(),publishing_target="ditaot")
   149 <!DOCTYPE cxxClass PUBLIC "-//NOKIA//DTD DITA C++ API Class Reference Type v0.1.0//EN" "dtd/cxxClass.dtd" >
   133         newfile = fr.rename("hello.xml")
   150 <cxxClass>
   134         self.assertEquals(newfile, "GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F.xml")
   151     <apiName>CActiveScheduler</apiName>
       
   152     <shortdesc/>
       
   153 </cxxClass>
       
   154 """
       
   155 
   135 
   156 cxxclass = """<?xml version='1.0' encoding='UTF-8' standalone='no'?>
   136     def test_i_can_return_a_ditamaps_new_ditaot_name(self):
   157 <!DOCTYPE cxxClass PUBLIC "-//NOKIA//DTD DITA C++ API Class Reference Type v0.1.0//EN" "dtd/cxxClass.dtd" >
   137         fr = FileRenamer(xmlparser=StubXmlParser(),publishing_target="ditaot")
   158 <cxxClass id="class_c_active_scheduler">
   138         newfile = fr.rename("hello.ditamap")
   159     <apiName>CActiveScheduler</apiName>
   139         self.assertEquals(newfile, "GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F.ditamap")        
   160     <shortdesc/>
       
   161 </cxxClass>
       
   162 """