scripts/pybootchart: Port to python3

This updates the pybootchart code (used for viewing build timing profiles)
to use python3. The bulk of the changes are to use gi instead of pygtk, i.e.
port from gtk+2 to gtk+3.

The main change is to make the bootchart widget inherit gtk.Scrollable
and change the way the scrollbars are implemented to match the new method
upstream. The drawing code used cairo already so can remain unchanged,

(From OE-Core rev: 949144681ad7f536732169351cab6d0612e9c566)

Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Richard Purdie 2019-05-08 11:48:35 +01:00
parent d13b904305
commit 020911ab59
4 changed files with 113 additions and 113 deletions

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# #
# This file is part of pybootchartgui. # This file is part of pybootchartgui.

View File

@ -19,6 +19,7 @@ import math
import re import re
import random import random
import colorsys import colorsys
import functools
from operator import itemgetter from operator import itemgetter
class RenderOptions: class RenderOptions:
@ -449,7 +450,7 @@ def render_charts(ctx, options, clip, trace, curr_y, w, h, sec_w):
[(sample.time, [(sample.time,
# Sum up used space of all volumes including the current one # Sum up used space of all volumes including the current one
# so that the graphs appear as stacked on top of each other. # so that the graphs appear as stacked on top of each other.
reduce(lambda x,y: x+y, functools.reduce(lambda x,y: x+y,
[sample.records[volume] - min_used[volume] [sample.records[volume] - min_used[volume]
for volume in volumes[0:i] for volume in volumes[0:i]
if volume in sample.records], if volume in sample.records],
@ -501,7 +502,7 @@ def render_processes_chart(ctx, options, trace, curr_y, w, h, sec_w):
TASK_COLOR_SYSROOT, off_x+360, curr_y + 45, leg_s) TASK_COLOR_SYSROOT, off_x+360, curr_y + 45, leg_s)
draw_legend_box (ctx, "Package", \ draw_legend_box (ctx, "Package", \
TASK_COLOR_PACKAGE, off_x+480, curr_y + 45, leg_s) TASK_COLOR_PACKAGE, off_x+480, curr_y + 45, leg_s)
draw_legend_box (ctx, "Package Write", draw_legend_box (ctx, "Package Write", \
TASK_COLOR_PACKAGE_WRITE, off_x+600, curr_y + 45, leg_s) TASK_COLOR_PACKAGE_WRITE, off_x+600, curr_y + 45, leg_s)
ctx.set_font_size(PROC_TEXT_FONT_SIZE) ctx.set_font_size(PROC_TEXT_FONT_SIZE)
@ -518,13 +519,14 @@ def render_processes_chart(ctx, options, trace, curr_y, w, h, sec_w):
trace.processes[val][1] - s < options.app_options.mintime: trace.processes[val][1] - s < options.app_options.mintime:
continue continue
task = val.split(":")[1] task = val.split(":")[1]
#print val #print(val)
#print trace.processes[val][1] #print(trace.processes[val][1])
#print s #print(s)
x = chart_rect[0] + (s - offset) * sec_w x = chart_rect[0] + (s - offset) * sec_w
w = ((trace.processes[val][1] - s) * sec_w) w = ((trace.processes[val][1] - s) * sec_w)
#print "proc at %s %s %s %s" % (x, y, w, proc_h) #print("proc at %s %s %s %s" % (x, y, w, proc_h))
col = None col = None
if task == "do_compile": if task == "do_compile":
col = TASK_COLOR_COMPILE col = TASK_COLOR_COMPILE

View File

@ -13,64 +13,83 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with pybootchartgui. If not, see <http://www.gnu.org/licenses/>. # along with pybootchartgui. If not, see <http://www.gnu.org/licenses/>.
import gobject import gi
import gtk gi.require_version('Gtk', '3.0')
import gtk.gdk from gi.repository import Gtk as gtk
import gtk.keysyms from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GObject as gobject
from gi.repository import GObject
from . import draw from . import draw
from .draw import RenderOptions from .draw import RenderOptions
class PyBootchartWidget(gtk.DrawingArea): class PyBootchartWidget(gtk.DrawingArea, gtk.Scrollable):
__gsignals__ = { __gsignals__ = {
'expose-event': 'override', 'clicked' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, Gdk.Event)),
'clicked' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gtk.gdk.Event)),
'position-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT, gobject.TYPE_INT)), 'position-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT, gobject.TYPE_INT)),
'set-scroll-adjustments' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gtk.Adjustment, gtk.Adjustment)) 'set-scroll-adjustments' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gtk.Adjustment, gtk.Adjustment))
} }
hadjustment = GObject.property(type=Gtk.Adjustment,
default=Gtk.Adjustment(),
flags=GObject.PARAM_READWRITE)
hscroll_policy = GObject.property(type=Gtk.ScrollablePolicy,
default=Gtk.ScrollablePolicy.MINIMUM,
flags=GObject.PARAM_READWRITE)
vadjustment = GObject.property(type=Gtk.Adjustment,
default=Gtk.Adjustment(),
flags=GObject.PARAM_READWRITE)
vscroll_policy = GObject.property(type=Gtk.ScrollablePolicy,
default=Gtk.ScrollablePolicy.MINIMUM,
flags=GObject.PARAM_READWRITE)
def __init__(self, trace, options, xscale): def __init__(self, trace, options, xscale):
gtk.DrawingArea.__init__(self) gtk.DrawingArea.__init__(self)
self.trace = trace self.trace = trace
self.options = options self.options = options
self.set_flags(gtk.CAN_FOCUS) self.set_can_focus(True)
self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK) self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK)
self.connect("button-press-event", self.on_area_button_press) self.connect("button-press-event", self.on_area_button_press)
self.connect("button-release-event", self.on_area_button_release) self.connect("button-release-event", self.on_area_button_release)
self.add_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK | gtk.gdk.BUTTON_RELEASE_MASK) self.add_events(Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_HINT_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK)
self.connect("motion-notify-event", self.on_area_motion_notify) self.connect("motion-notify-event", self.on_area_motion_notify)
self.connect("scroll-event", self.on_area_scroll_event) self.connect("scroll-event", self.on_area_scroll_event)
self.connect('key-press-event', self.on_key_press_event) self.connect('key-press-event', self.on_key_press_event)
self.connect('set-scroll-adjustments', self.on_set_scroll_adjustments)
self.connect("size-allocate", self.on_allocation_size_changed) self.connect("size-allocate", self.on_allocation_size_changed)
self.connect("position-changed", self.on_position_changed) self.connect("position-changed", self.on_position_changed)
self.connect("draw", self.on_draw)
self.zoom_ratio = 1.0 self.zoom_ratio = 1.0
self.xscale = xscale self.xscale = xscale
self.x, self.y = 0.0, 0.0 self.x, self.y = 0.0, 0.0
self.chart_width, self.chart_height = draw.extents(self.options, self.xscale, self.trace) self.chart_width, self.chart_height = draw.extents(self.options, self.xscale, self.trace)
self.hadj = None self.our_width, self.our_height = self.chart_width, self.chart_height
self.vadj = None
self.hadj_changed_signal_id = None
self.vadj_changed_signal_id = None
def do_expose_event(self, event): self.hadj = gtk.Adjustment(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
cr = self.window.cairo_create() self.vadj = gtk.Adjustment(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
self.vadj.connect('value-changed', self.on_adjustments_changed)
self.hadj.connect('value-changed', self.on_adjustments_changed)
# set a clip region for the expose event def bound_vals(self):
cr.rectangle( self.x = max(0, self.x)
event.area.x, event.area.y, self.y = max(0, self.y)
event.area.width, event.area.height self.x = min(self.chart_width - self.our_width, self.x)
) self.y = min(self.chart_height - self.our_height, self.y)
cr.clip()
self.draw(cr, self.get_allocation())
return False
def draw(self, cr, rect): def on_draw(self, darea, cr):
# set a clip region
#cr.rectangle(
# self.x, self.y,
# self.chart_width, self.chart_height
#)
#cr.clip()
cr.set_source_rgba(1.0, 1.0, 1.0, 1.0) cr.set_source_rgba(1.0, 1.0, 1.0, 1.0)
cr.paint() cr.paint()
cr.scale(self.zoom_ratio, self.zoom_ratio) cr.scale(self.zoom_ratio, self.zoom_ratio)
@ -84,7 +103,7 @@ class PyBootchartWidget(gtk.DrawingArea):
def zoom_image (self, zoom_ratio): def zoom_image (self, zoom_ratio):
self.zoom_ratio = zoom_ratio self.zoom_ratio = zoom_ratio
self._set_scroll_adjustments (self.hadj, self.vadj) self._set_scroll_adjustments()
self.queue_draw() self.queue_draw()
def zoom_to_rect (self, rect): def zoom_to_rect (self, rect):
@ -122,126 +141,101 @@ class PyBootchartWidget(gtk.DrawingArea):
def show_toggled(self, button): def show_toggled(self, button):
self.options.app_options.show_all = button.get_property ('active') self.options.app_options.show_all = button.get_property ('active')
self.chart_width, self.chart_height = draw.extents(self.options, self.xscale, self.trace) self.chart_width, self.chart_height = draw.extents(self.options, self.xscale, self.trace)
self._set_scroll_adjustments(self.hadj, self.vadj) self._set_scroll_adjustments()
self.queue_draw() self.queue_draw()
POS_INCREMENT = 100 POS_INCREMENT = 100
def on_key_press_event(self, widget, event): def on_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.Left: if event.keyval == Gdk.keyval_from_name("Left"):
self.x -= self.POS_INCREMENT/self.zoom_ratio self.x -= self.POS_INCREMENT/self.zoom_ratio
elif event.keyval == gtk.keysyms.Right: elif event.keyval == Gdk.keyval_from_name("Right"):
self.x += self.POS_INCREMENT/self.zoom_ratio self.x += self.POS_INCREMENT/self.zoom_ratio
elif event.keyval == gtk.keysyms.Up: elif event.keyval == Gdk.keyval_from_name("Up"):
self.y -= self.POS_INCREMENT/self.zoom_ratio self.y -= self.POS_INCREMENT/self.zoom_ratio
elif event.keyval == gtk.keysyms.Down: elif event.keyval == Gdk.keyval_from_name("Down"):
self.y += self.POS_INCREMENT/self.zoom_ratio self.y += self.POS_INCREMENT/self.zoom_ratio
else: else:
return False return False
self.bound_vals()
self.queue_draw() self.queue_draw()
self.position_changed() self.position_changed()
return True return True
def on_area_button_press(self, area, event): def on_area_button_press(self, area, event):
if event.button == 2 or event.button == 1: if event.button == 2 or event.button == 1:
area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR)) window = self.get_window()
window.set_cursor(Gdk.Cursor(Gdk.CursorType.FLEUR))
self.prevmousex = event.x self.prevmousex = event.x
self.prevmousey = event.y self.prevmousey = event.y
if event.type not in (gtk.gdk.BUTTON_PRESS, gtk.gdk.BUTTON_RELEASE): if event.type not in (Gdk.EventType.BUTTON_PRESS, Gdk.EventType.BUTTON_RELEASE):
return False return False
return False return False
def on_area_button_release(self, area, event): def on_area_button_release(self, area, event):
if event.button == 2 or event.button == 1: if event.button == 2 or event.button == 1:
area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW)) window = self.get_window()
window.set_cursor(Gdk.Cursor(Gdk.CursorType.ARROW))
self.prevmousex = None self.prevmousex = None
self.prevmousey = None self.prevmousey = None
return True return True
return False return False
def on_area_scroll_event(self, area, event): def on_area_scroll_event(self, area, event):
if event.state & gtk.gdk.CONTROL_MASK: if event.state & Gdk.CONTROL_MASK:
if event.direction == gtk.gdk.SCROLL_UP: if event.direction == Gdk.SCROLL_UP:
self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT) self.zoom_image(self.zoom_ratio * self.ZOOM_INCREMENT)
return True return True
if event.direction == gtk.gdk.SCROLL_DOWN: if event.direction == Gdk.SCROLL_DOWN:
self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT) self.zoom_image(self.zoom_ratio / self.ZOOM_INCREMENT)
return True return True
return False return False
def on_area_motion_notify(self, area, event): def on_area_motion_notify(self, area, event):
state = event.state state = event.state
if state & gtk.gdk.BUTTON2_MASK or state & gtk.gdk.BUTTON1_MASK: if state & Gdk.ModifierType.BUTTON2_MASK or state & Gdk.ModifierType.BUTTON1_MASK:
x, y = int(event.x), int(event.y) x, y = int(event.x), int(event.y)
# pan the image # pan the image
self.x += (self.prevmousex - x)/self.zoom_ratio self.x += (self.prevmousex - x)/self.zoom_ratio
self.y += (self.prevmousey - y)/self.zoom_ratio self.y += (self.prevmousey - y)/self.zoom_ratio
self.bound_vals()
self.queue_draw() self.queue_draw()
self.prevmousex = x self.prevmousex = x
self.prevmousey = y self.prevmousey = y
self.position_changed() self.position_changed()
return True return True
def on_set_scroll_adjustments(self, area, hadj, vadj):
self._set_scroll_adjustments (hadj, vadj)
def on_allocation_size_changed(self, widget, allocation): def on_allocation_size_changed(self, widget, allocation):
self.hadj.page_size = allocation.width self.hadj.page_size = allocation.width
self.hadj.page_increment = allocation.width * 0.9 self.hadj.page_increment = allocation.width * 0.9
self.vadj.page_size = allocation.height self.vadj.page_size = allocation.height
self.vadj.page_increment = allocation.height * 0.9 self.vadj.page_increment = allocation.height * 0.9
self.our_width = allocation.width
if self.chart_width < self.our_width:
self.our_width = self.chart_width
self.our_height = allocation.height
if self.chart_height < self.our_height:
self.our_height = self.chart_height
self._set_scroll_adjustments()
def _set_adj_upper(self, adj, upper): def _set_adj_upper(self, adj, upper):
changed = False
value_changed = False
if adj.upper != upper: if adj.get_upper() != upper:
adj.upper = upper adj.set_upper(upper)
changed = True
max_value = max(0.0, upper - adj.page_size) def _set_scroll_adjustments(self):
if adj.value > max_value: self._set_adj_upper (self.hadj, self.zoom_ratio * (self.chart_width - self.our_width))
adj.value = max_value self._set_adj_upper (self.vadj, self.zoom_ratio * (self.chart_height - self.our_height))
value_changed = True
if changed:
adj.changed()
if value_changed:
adj.value_changed()
def _set_scroll_adjustments(self, hadj, vadj):
if hadj == None:
hadj = gtk.Adjustment(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
if vadj == None:
vadj = gtk.Adjustment(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
if self.hadj_changed_signal_id != None and \
self.hadj != None and hadj != self.hadj:
self.hadj.disconnect (self.hadj_changed_signal_id)
if self.vadj_changed_signal_id != None and \
self.vadj != None and vadj != self.vadj:
self.vadj.disconnect (self.vadj_changed_signal_id)
if hadj != None:
self.hadj = hadj
self._set_adj_upper (self.hadj, self.zoom_ratio * self.chart_width)
self.hadj_changed_signal_id = self.hadj.connect('value-changed', self.on_adjustments_changed)
if vadj != None:
self.vadj = vadj
self._set_adj_upper (self.vadj, self.zoom_ratio * self.chart_height)
self.vadj_changed_signal_id = self.vadj.connect('value-changed', self.on_adjustments_changed)
def on_adjustments_changed(self, adj): def on_adjustments_changed(self, adj):
self.x = self.hadj.value / self.zoom_ratio self.x = self.hadj.get_value() / self.zoom_ratio
self.y = self.vadj.value / self.zoom_ratio self.y = self.vadj.get_value() / self.zoom_ratio
self.queue_draw() self.queue_draw()
def on_position_changed(self, widget, x, y): def on_position_changed(self, widget, x, y):
self.hadj.value = x * self.zoom_ratio self.hadj.set_value(x * self.zoom_ratio)
self.vadj.value = y * self.zoom_ratio #self.hadj.value_changed()
self.vadj.set_value(y * self.zoom_ratio)
PyBootchartWidget.set_set_scroll_adjustments_signal('set-scroll-adjustments')
class PyBootchartShell(gtk.VBox): class PyBootchartShell(gtk.VBox):
ui = ''' ui = '''
@ -260,7 +254,7 @@ class PyBootchartShell(gtk.VBox):
def __init__(self, window, trace, options, xscale): def __init__(self, window, trace, options, xscale):
gtk.VBox.__init__(self) gtk.VBox.__init__(self)
self.widget = PyBootchartWidget(trace, options, xscale) self.widget2 = PyBootchartWidget(trace, options, xscale)
# Create a UIManager instance # Create a UIManager instance
uimanager = self.uimanager = gtk.UIManager() uimanager = self.uimanager = gtk.UIManager()
@ -275,12 +269,12 @@ class PyBootchartShell(gtk.VBox):
# Create actions # Create actions
actiongroup.add_actions(( actiongroup.add_actions((
('Expand', gtk.STOCK_ADD, None, None, None, self.widget.on_expand), ('Expand', gtk.STOCK_ADD, None, None, None, self.widget2.on_expand),
('Contract', gtk.STOCK_REMOVE, None, None, None, self.widget.on_contract), ('Contract', gtk.STOCK_REMOVE, None, None, None, self.widget2.on_contract),
('ZoomIn', gtk.STOCK_ZOOM_IN, None, None, None, self.widget.on_zoom_in), ('ZoomIn', gtk.STOCK_ZOOM_IN, None, None, None, self.widget2.on_zoom_in),
('ZoomOut', gtk.STOCK_ZOOM_OUT, None, None, None, self.widget.on_zoom_out), ('ZoomOut', gtk.STOCK_ZOOM_OUT, None, None, None, self.widget2.on_zoom_out),
('ZoomFit', gtk.STOCK_ZOOM_FIT, 'Fit Width', None, None, self.widget.on_zoom_fit), ('ZoomFit', gtk.STOCK_ZOOM_FIT, 'Fit Width', None, None, self.widget2.on_zoom_fit),
('Zoom100', gtk.STOCK_ZOOM_100, None, None, None, self.widget.on_zoom_100), ('Zoom100', gtk.STOCK_ZOOM_100, None, None, None, self.widget2.on_zoom_100),
)) ))
# Add the actiongroup to the uimanager # Add the actiongroup to the uimanager
@ -290,29 +284,33 @@ class PyBootchartShell(gtk.VBox):
uimanager.add_ui_from_string(self.ui) uimanager.add_ui_from_string(self.ui)
# Scrolled window # Scrolled window
scrolled = gtk.ScrolledWindow() scrolled = gtk.ScrolledWindow(self.widget2.hadj, self.widget2.vadj)
scrolled.add(self.widget) scrolled.add(self.widget2)
#scrolled.set_hadjustment()
#scrolled.set_vadjustment(self.widget2.vadj)
scrolled.set_policy(gtk.PolicyType.ALWAYS, gtk.PolicyType.ALWAYS)
# toolbar / h-box # toolbar / h-box
hbox = gtk.HBox(False, 8) hbox = gtk.HBox(False, 8)
# Create a Toolbar # Create a Toolbar
toolbar = uimanager.get_widget('/ToolBar') toolbar = uimanager.get_widget('/ToolBar')
hbox.pack_start(toolbar, True, True) hbox.pack_start(toolbar, True, True, 0)
if not options.kernel_only: if not options.kernel_only:
# Misc. options # Misc. options
button = gtk.CheckButton("Show more") button = gtk.CheckButton("Show more")
button.connect ('toggled', self.widget.show_toggled) button.connect ('toggled', self.widget2.show_toggled)
button.set_active(options.app_options.show_all) button.set_active(options.app_options.show_all)
hbox.pack_start (button, False, True) hbox.pack_start (button, False, True, 0)
self.pack_start(hbox, False) self.pack_start(hbox, False, True, 0)
self.pack_start(scrolled) self.pack_start(scrolled, True, True, 0)
self.show_all() self.show_all()
def grab_focus(self, window): def grab_focus(self, window):
window.set_focus(self.widget) window.set_focus(self.widget2)
class PyBootchartWindow(gtk.Window): class PyBootchartWindow(gtk.Window):

View File

@ -267,7 +267,7 @@ def _parse_headers(file):
value = line.strip() value = line.strip()
headers[last] += value headers[last] += value
return headers, last return headers, last
return reduce(parse, file.read().decode('utf-8').split('\n'), (defaultdict(str),''))[0] return reduce(parse, file.read().split('\n'), (defaultdict(str),''))[0]
def _parse_timed_blocks(file): def _parse_timed_blocks(file):
"""Parses (ie., splits) a file into so-called timed-blocks. A """Parses (ie., splits) a file into so-called timed-blocks. A
@ -281,7 +281,7 @@ def _parse_timed_blocks(file):
return (int(lines[0]), lines[1:]) return (int(lines[0]), lines[1:])
except ValueError: except ValueError:
raise ParseError("expected a timed-block, but timestamp '%s' is not an integer" % lines[0]) raise ParseError("expected a timed-block, but timestamp '%s' is not an integer" % lines[0])
blocks = file.read().decode('utf-8').split('\n\n') blocks = file.read().split('\n\n')
return [parse(block) for block in blocks if block.strip() and not block.endswith(' not running\n')] return [parse(block) for block in blocks if block.strip() and not block.endswith(' not running\n')]
def _parse_proc_ps_log(writer, file): def _parse_proc_ps_log(writer, file):
@ -577,7 +577,7 @@ def _parse_dmesg(writer, file):
processMap['k-boot'] = kernel processMap['k-boot'] = kernel
base_ts = False base_ts = False
max_ts = 0 max_ts = 0
for line in file.read().decode('utf-8').split('\n'): for line in file.read().split('\n'):
t = timestamp_re.match (line) t = timestamp_re.match (line)
if t is None: if t is None:
# print "duff timestamp " + line # print "duff timestamp " + line
@ -665,7 +665,7 @@ def _parse_pacct(writer, file):
def _parse_paternity_log(writer, file): def _parse_paternity_log(writer, file):
parent_map = {} parent_map = {}
parent_map[0] = 0 parent_map[0] = 0
for line in file.read().decode('utf-8').split('\n'): for line in file.read().split('\n'):
if not line: if not line:
continue continue
elems = line.split(' ') # <Child> <Parent> elems = line.split(' ') # <Child> <Parent>
@ -678,7 +678,7 @@ def _parse_paternity_log(writer, file):
def _parse_cmdline_log(writer, file): def _parse_cmdline_log(writer, file):
cmdLines = {} cmdLines = {}
for block in file.read().decode('utf-8').split('\n\n'): for block in file.read().split('\n\n'):
lines = block.split('\n') lines = block.split('\n')
if len (lines) >= 3: if len (lines) >= 3:
# print "Lines '%s'" % (lines[0]) # print "Lines '%s'" % (lines[0])
@ -751,7 +751,7 @@ def parse_file(writer, state, filename):
if state.filename is None: if state.filename is None:
state.filename = filename state.filename = filename
basename = os.path.basename(filename) basename = os.path.basename(filename)
with open(filename, "rb") as file: with open(filename, "r") as file:
return _do_parse(writer, state, filename, file) return _do_parse(writer, state, filename, file)
def parse_paths(writer, state, paths): def parse_paths(writer, state, paths):