sink.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. # Copyright 2009-2014 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. try:
  28. from StringIO import StringIO
  29. except ImportError:
  30. from io import StringIO
  31. __all__ = ['Sink', 'create_sink', 'destroy_sink']
  32. # -------------------------------------------------------------------------- #
  33. # Globals
  34. sinks = {} # of int: Sink
  35. last_sink_id = 0
  36. sink_id_lock = threading.Lock()
  37. # -------------------------------------------------------------------------- #
  38. # ctypes type definitions
  39. # Fix for Windows b/c tidy uses stdcall on Windows
  40. if "Windows" == platform.system():
  41. functype = ctypes.WINFUNCTYPE
  42. else:
  43. functype = ctypes.CFUNCTYPE
  44. PutByteType = functype(None, ctypes.c_int, ctypes.c_char)
  45. class TidyOutputSink(ctypes.Structure):
  46. """ Mirrors the _TidyOutputSink structure in tidy.h """
  47. _fields_ = [
  48. ('sinkData', ctypes.c_void_p),
  49. ('putByte', PutByteType)
  50. ]
  51. # -------------------------------------------------------------------------- #
  52. # Python interface
  53. class Sink(object):
  54. """ Represent a buffer to which Tidy writes errors with a callback function """
  55. def __init__(self, sink_id):
  56. self.data = StringIO()
  57. self.sink_id = sink_id
  58. self.struct = TidyOutputSink()
  59. self.struct.sinkData = ctypes.cast(
  60. ctypes.pointer(ctypes.c_int(sink_id)), ctypes.c_void_p) # Windows fix
  61. write_func = self.data.write # Avoid 2 attr accesses per byte
  62. def put_byte(sink_id, byte):
  63. # We don't need sink_id because we have a separate put_byte
  64. # function for each sink
  65. write_func(byte.decode('utf-8'))
  66. self.struct.putByte = PutByteType(put_byte)
  67. self._as_parameter_ = ctypes.byref(self.struct)
  68. def __str__(self):
  69. return self.data.getvalue()
  70. def create_sink():
  71. """ Return a new Sink with a numeric ID incremented in a threadsafe way """
  72. global last_sink_id, sink_id_lock, sinks
  73. sink_id_lock.acquire()
  74. try:
  75. this_sink_id = last_sink_id
  76. last_sink_id = (last_sink_id + 1) % sys.maxsize
  77. # If you have more than maxint sinks open at a time, you're screwed
  78. finally:
  79. sink_id_lock.release()
  80. sink = Sink(this_sink_id)
  81. sinks[this_sink_id] = sink
  82. return sink
  83. def destroy_sink(sink):
  84. """ Free a Sink object by eliminating the reference from the global map """
  85. global sinks
  86. del sinks[sink.sink_id]
  87. del sink