sink.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. # Copyright 2009 Jason Stitt
  2. #
  3. # Permission is hereby granted, free of charge, to any person obtaining a copy
  4. # of this software and associated documentation files (the "Software"), to deal
  5. # in the Software without restriction, including without limitation the rights
  6. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. # copies of the Software, and to permit persons to whom the Software is
  8. # furnished to do so, subject to the following conditions:
  9. #
  10. # The above copyright notice and this permission notice shall be included in
  11. # all copies or substantial portions of the Software.
  12. #
  13. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. # THE SOFTWARE.
  20. import ctypes
  21. import sys
  22. import threading
  23. import platform
  24. try:
  25. from cStringIO import StringIO
  26. except ImportError:
  27. from StringIO import StringIO
  28. __all__ = ['Sink', 'create_sink', 'destroy_sink']
  29. #----------------------------------------------------------------------------#
  30. # Globals
  31. sinks = {} # of int: Sink
  32. last_sink_id = 0
  33. sink_id_lock = threading.Lock()
  34. #----------------------------------------------------------------------------#
  35. # ctypes type definitions
  36. # Fix for Windows b/c tidy uses stdcall on Windows
  37. if "Windows" == platform.system():
  38. functype = ctypes.WINFUNCTYPE
  39. else:
  40. functype = ctypes.CFUNCTYPE
  41. PutByteType = functype(None, ctypes.c_int, ctypes.c_char)
  42. class TidyOutputSink(ctypes.Structure):
  43. """ Mirrors the _TidyOutputSink structure in tidy.h """
  44. _fields_ = [
  45. ('sinkData', ctypes.c_void_p),
  46. ('putByte', PutByteType)
  47. ]
  48. #----------------------------------------------------------------------------#
  49. # Python interface
  50. class Sink(object):
  51. """ Represent a buffer to which Tidy writes errors with a callback function """
  52. def __init__(self, sink_id):
  53. self.data = StringIO()
  54. self.sink_id = sink_id
  55. self.struct = TidyOutputSink()
  56. self.struct.sinkData = ctypes.cast(
  57. ctypes.pointer(ctypes.c_int(sink_id)), ctypes.c_void_p) # Windows fix
  58. write_func = self.data.write # Avoid 2 attr accesses per byte
  59. def put_byte(sink_id, byte):
  60. # We don't need sink_id because we have a separate put_byte
  61. # function for each sink
  62. write_func(byte)
  63. self.struct.putByte = PutByteType(put_byte)
  64. self._as_parameter_ = ctypes.byref(self.struct)
  65. def __str__(self):
  66. return self.data.getvalue()
  67. def create_sink():
  68. """ Return a new Sink with a numeric ID incremented in a threadsafe way """
  69. global last_sink_id, sink_id_lock, sinks
  70. sink_id_lock.acquire()
  71. try:
  72. this_sink_id = last_sink_id
  73. last_sink_id = (last_sink_id + 1) % sys.maxint
  74. # If you have more than maxint sinks open at a time, you're screwed
  75. finally:
  76. sink_id_lock.release()
  77. sink = Sink(this_sink_id)
  78. sinks[this_sink_id] = sink
  79. return sink
  80. def destroy_sink(sink):
  81. """ Free a Sink object by eliminating the reference from the global map """
  82. global sinks
  83. del sinks[sink.sink_id]
  84. del sink
  85. #----------------------------------------------------------------------------#