• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

midstar / pycstruct / #144

pending completion
#144

push

coveralls-python

web-flow
Merge pull request #41 from mhkline/expose-subprocess-kwargs

expose subprocess kwargs through parse_file call

41 of 41 new or added lines in 3 files covered. (100.0%)

979 of 1013 relevant lines covered (96.64%)

0.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.03
/pycstruct/cparser.py
1
"""pycstruct cparser
2

3
Copyright 2021 by Joel Midstjärna.
4
All rights reserved.
5
This file is part of the pycstruct python library and is
6
released under the "MIT License Agreement". Please see the LICENSE
7
file that should have been included as part of this package.
8
"""
9

10
import xml.etree.ElementTree as ET
1✔
11

12
import hashlib
1✔
13
import logging
1✔
14
import math
1✔
15
import os
1✔
16
import shutil
1✔
17
import subprocess
1✔
18
import tempfile
1✔
19

20
from pycstruct.pycstruct import StructDef, BitfieldDef, EnumDef
1✔
21

22
###############################################################################
23
# Global constants
24

25
logger = logging.getLogger("pycstruct")
1✔
26

27
###############################################################################
28
# Internal functions
29

30

31
def _run_castxml(
1✔
32
    input_files,
33
    xml_filename,
34
    castxml_cmd="castxml",
35
    castxml_extra_args=None,
36
    **subprocess_kwargs,
37
):
38
    """Run castcml as a 'shell command'"""
39
    if shutil.which(castxml_cmd) is None:
1✔
40
        raise RuntimeError(
1✔
41
            f'Executable "{castxml_cmd}" not found.\n'
42
            + "External software castxml is not installed.\n"
43
            + "You need to install it and put it in your PATH."
44
        )
45
    if castxml_extra_args is None:
1✔
46
        castxml_extra_args = []
1✔
47
    args = [castxml_cmd]
1✔
48
    args += castxml_extra_args
1✔
49
    args += input_files
1✔
50
    args.append("--castxml-gccxml")
1✔
51
    args.append("-o")
1✔
52
    args.append(xml_filename)
1✔
53

54
    # to preserve behavior before subprocess_kwargs were allowed to be passed
55
    # in, default to redirecting stderr to STDOUT.
56
    if "stderr" not in subprocess_kwargs:
1✔
57
        subprocess_kwargs["stderr"] = subprocess.STDOUT
1✔
58

59
    try:
1✔
60
        output = subprocess.check_output(args, **subprocess_kwargs)
1✔
61
    except subprocess.CalledProcessError as exception:
×
62
        raise RuntimeError(
×
63
            "Unable to run:\n"
64
            + f"{' '.join(args)}\n\n"
65
            + "Output:\n"
66
            + exception.output.decode()
67
        ) from exception
68

69
    if not os.path.isfile(xml_filename):
1✔
70
        raise RuntimeError(
1✔
71
            "castxml did not report any error but "
72
            + f"{xml_filename} was never produced.\n\n"
73
            + f"castxml output was:\n{output.decode()}"
74
        )
75

76

77
def _get_hash(list_of_strings):
1✔
78
    """Get a reproducible short name of a list of strings"""
79
    long_string = "".join(list_of_strings)
1✔
80
    sha256 = hashlib.sha256(long_string.encode())
1✔
81
    hexdigest = sha256.hexdigest()
1✔
82
    return hexdigest[:10]
1✔
83

84

85
def _listify(list_or_str):
1✔
86
    """If list_or_str is a string it will be put in a list"""
87
    if isinstance(list_or_str, str):
1✔
88
        list_or_str = [list_or_str]
1✔
89
    return list_or_str
1✔
90

91

92
###############################################################################
93
# CastXMLParser class (internal)
94

95

96
class _CastXmlParser:
1✔
97
    """Parses XML:s produced by CastXML and creates a new dictionary where
98
    each key represent a type name of a defined type (struct, bitstruct,
99
    enum, union etc.). The value represents the metadata about the type,
100
    such as members etc.
101
    """
102

103
    # pylint: disable=too-few-public-methods, broad-except
104

105
    def __init__(self, xml_filename):
1✔
106
        self._xml_filename = xml_filename
1✔
107
        self._anonymous_count = 0
1✔
108
        self._embedded_bf_count = 0
1✔
109
        self._embedded_bf = []
1✔
110
        self.root = None
1✔
111

112
    def parse(self):
1✔
113
        """Parse the XML file provided in the constructor"""
114
        # pylint: disable=too-many-branches, too-many-locals
115
        self.root = ET.parse(self._xml_filename).getroot()
1✔
116

117
        supported_types = {}
1✔
118

119
        # Parse enums
120
        xml_enums = self.root.findall("Enumeration")
1✔
121
        for xml_enum in xml_enums:
1✔
122
            typeid = xml_enum.attrib["id"]
1✔
123
            supported_types[typeid] = self._parse_enum(xml_enum)
1✔
124

125
        # Parse unions
126
        xml_unions = self.root.findall("Union")
1✔
127
        for xml_union in xml_unions:
1✔
128
            typeid = xml_union.attrib["id"]
1✔
129
            supported_types[typeid] = self._parse_union(xml_union)
1✔
130

131
        # Parse structs and bitfields:
132
        xml_structs_and_bitfields = self.root.findall("Struct")
1✔
133
        for xml_struct_or_bitfield in xml_structs_and_bitfields:
1✔
134
            typeid = xml_struct_or_bitfield.attrib["id"]
1✔
135
            if self._is_bitfield(xml_struct_or_bitfield):
1✔
136
                supported_types[typeid] = self._parse_bitfield(xml_struct_or_bitfield)
1✔
137
            else:
138
                supported_types[typeid] = self._parse_struct(xml_struct_or_bitfield)
1✔
139

140
        # Add all embedded bitfields in supported_types
141
        for bitfield in self._embedded_bf:
1✔
142
            supported_types[bitfield["name"]] = bitfield
1✔
143

144
        # Change mapping from id to name
145
        type_meta = {}
1✔
146
        for _, datatype in supported_types.items():
1✔
147
            name = datatype["name"]
1✔
148
            if name in type_meta:
1✔
149
                # Name is not unique, make it unique
150
                for i in range(1, 1000):
1✔
151
                    new_name = name + str(i)
1✔
152
                    if new_name not in type_meta:
1✔
153
                        name = new_name
1✔
154
                        break
1✔
155
            datatype["name"] = name  # Change old name to new
1✔
156
            type_meta[name] = datatype
1✔
157

158
        # Secure all references points to names instead of id's
159
        for _, datatype in type_meta.items():
1✔
160
            for member in datatype["members"]:
1✔
161
                if "reference" in member:
1✔
162
                    typeid = member["reference"]
1✔
163
                    name = supported_types[typeid]["name"]
1✔
164
                    member["reference"] = name
1✔
165

166
        return type_meta
1✔
167

168
    def _is_bitfield(self, xml_struct_or_bitfield):
1✔
169
        """Returns true if this is a "true" bitfield, i.e. the
170
        struct only contains bitfield members"""
171
        for field in self._get_fields(xml_struct_or_bitfield):
1✔
172
            if "bits" not in field.attrib:
1✔
173
                # Only bitfield fields has the bits attribute set
174
                return False
1✔
175
        return True
1✔
176

177
    def _get_fields(self, xml_item):
1✔
178
        fields = []
1✔
179
        for member_id in self._get_attrib(xml_item, "members", "").split():
1✔
180
            xml_member = self._get_elem_with_id(member_id)
1✔
181
            if xml_member.tag != "Field":
1✔
182
                continue  # Probably just a struct/union definition
1✔
183
            fields.append(xml_member)
1✔
184
        return fields
1✔
185

186
    def _parse_enum(self, xml_enum):
1✔
187
        enum = {}
1✔
188
        enum["type"] = "enum"
1✔
189
        self._set_common_meta(xml_enum, enum)
1✔
190
        enum["members"] = []
1✔
191
        has_negative = False
1✔
192
        for xml_member in xml_enum.findall("EnumValue"):
1✔
193
            member = {}
1✔
194
            member["name"] = xml_member.attrib["name"]
1✔
195
            member["value"] = int(xml_member.attrib["init"])
1✔
196
            if member["value"] < 0:
1✔
197
                has_negative = True
1✔
198
            enum["members"].append(member)
1✔
199
        if has_negative:
1✔
200
            enum["signed"] = True
1✔
201
        else:
202
            enum["signed"] = False
1✔
203
        return enum
1✔
204

205
    def _parse_union(self, xml_union):
1✔
206
        union = {}
1✔
207
        union["type"] = "union"
1✔
208
        self._set_common_meta(xml_union, union)
1✔
209
        self._set_struct_union_members(xml_union, union)
1✔
210
        return union
1✔
211

212
    def _parse_struct(self, xml_struct):
1✔
213
        struct = {}
1✔
214
        struct["type"] = "struct"
1✔
215
        self._set_common_meta(xml_struct, struct)
1✔
216
        self._set_struct_union_members(xml_struct, struct)
1✔
217
        return struct
1✔
218

219
    def _parse_bitfield_members(self, xml_bitfield_members):
1✔
220
        result = []
1✔
221
        for field in xml_bitfield_members:
1✔
222
            member = {}
1✔
223
            member["name"] = field.attrib["name"]
1✔
224
            member["bits"] = int(field.attrib["bits"])
1✔
225
            member["signed"] = True
1✔
226

227
            # Figure out if it is signed
228
            type_elem = self._get_basic_type_element(field.attrib["type"])
1✔
229
            if type_elem.tag == "FundamentalType":
1✔
230
                # Figure out sign
231
                if "unsigned" in type_elem.attrib["name"]:
1✔
232
                    member["signed"] = False
1✔
233
            else:
234
                logger.warning(
×
235
                    "Unable to parse sign of bitfield member %s. Will be signed.",
236
                    member["name"],
237
                )
238

239
            result.append(member)
1✔
240
        return result
1✔
241

242
    def _parse_bitfield(self, xml_bitfield):
1✔
243
        bitfield = {}
1✔
244
        bitfield["type"] = "bitfield"
1✔
245
        self._set_common_meta(xml_bitfield, bitfield)
1✔
246
        bitfield["members"] = self._parse_bitfield_members(
1✔
247
            self._get_fields(xml_bitfield)
248
        )
249

250
        return bitfield
1✔
251

252
    def _set_common_meta(self, xml_input, dict_output):
1✔
253
        """Set common metadata available for all types"""
254
        typeid = xml_input.attrib["id"]
1✔
255
        name = self._get_attrib(xml_input, "name", "")
1✔
256
        if name == "":
1✔
257
            # Does not have a name - check for TypeDef
258
            name = self._get_typedef_name(typeid)
1✔
259
        dict_output["name"] = name
1✔
260
        dict_output["size"] = int(int(self._get_attrib(xml_input, "size", "0")) / 8)
1✔
261
        dict_output["align"] = int(int(self._get_attrib(xml_input, "align", "8")) / 8)
1✔
262
        dict_output["supported"] = True
1✔
263

264
    def _set_struct_union_members(self, xml_input, dict_output):
1✔
265
        """Set members - common for struct and unions"""
266
        dict_output["members"] = []
1✔
267
        fields = self._get_fields(xml_input)
1✔
268
        while len(fields) > 0:
1✔
269
            field = fields.pop(0)
1✔
270
            member = {}
1✔
271
            if "bits" in field.attrib:
1✔
272
                # This is a bitfield, we need to create our
273
                # own bitfield definition for this field and
274
                # all bitfield members directly after this
275
                # member
276
                bf_fields = []
1✔
277
                nbr_bits = 0
1✔
278
                fields.insert(0, field)
1✔
279
                while len(fields) > 0 and "bits" in fields[0].attrib:
1✔
280
                    bf_field = fields.pop(0)
1✔
281
                    nbr_bits += int(self._get_attrib(bf_field, "bits", "0"))
1✔
282
                    bf_fields.append(bf_field)
1✔
283
                bitfield = {}
1✔
284
                bitfield["type"] = "bitfield"
1✔
285
                bitfield["name"] = f"auto_bitfield_{self._embedded_bf_count}"
1✔
286
                self._embedded_bf_count += 1
1✔
287
                # WARNING! The size is compiler specific and not covered
288
                # by the C standard. We guess:
289
                bitfield["size"] = int(math.ceil(nbr_bits / 8.0))
1✔
290
                bitfield["align"] = dict_output["align"]  # Same as parent
1✔
291
                bitfield["supported"] = True
1✔
292
                bitfield["members"] = self._parse_bitfield_members(bf_fields)
1✔
293
                self._embedded_bf.append(bitfield)
1✔
294

295
                member["name"] = f"__{bitfield['name']}"
1✔
296
                member["type"] = "bitfield"
1✔
297
                member["reference"] = bitfield["name"]
1✔
298
                member["same_level"] = True
1✔
299
            else:
300
                member["name"] = field.attrib["name"]
1✔
301
                member_type = self._get_type(field.attrib["type"])
1✔
302
                member["type"] = member_type["type_name"]
1✔
303
                if "shape" in member_type:
1✔
304
                    member["shape"] = member_type["shape"]
1✔
305
                if "reference" in member_type:
1✔
306
                    member["reference"] = member_type["reference"]
1✔
307
            dict_output["members"].append(member)
1✔
308

309
    def _get_attrib(self, elem, attrib, default):
1✔
310
        if attrib in elem.attrib:
1✔
311
            return elem.attrib[attrib]
1✔
312
        return default
1✔
313

314
    def _get_elem_with_id(self, typeid):
1✔
315
        elem = self.root.find(f"*[@id='{typeid}']")
1✔
316
        if elem is None:
1✔
317
            raise RuntimeError(f"No XML element with id attribute {typeid} identified")
×
318
        return elem
1✔
319

320
    def _get_elem_with_attrib(self, tag, attrib, value):
1✔
321
        elem = self.root.find(f"{tag}[@{attrib}='{value}']")
1✔
322
        if elem is None:
1✔
323
            raise RuntimeError(
1✔
324
                f"No {tag} XML element with {attrib} attribute {value} identified"
325
            )
326
        return elem
1✔
327

328
    def _get_typedef_name(self, type_id):
1✔
329
        """Find out the typedef name of a type which do not have a name"""
330

331
        # First check if there is a connected ElaboratedType element
332
        try:
1✔
333
            type_id = self._get_elem_with_attrib(
1✔
334
                "ElaboratedType", "type", type_id
335
            ).attrib["id"]
336
        except Exception:
1✔
337
            pass
1✔
338

339
        # Now find the TypeDef element connected to the type or ElaboratedType element
340
        name = ""
1✔
341
        try:
1✔
342
            name = self._get_elem_with_attrib("Typedef", "type", type_id).attrib["name"]
1✔
343
        except Exception:
1✔
344
            name = f"anonymous_{self._anonymous_count}"
1✔
345
            self._anonymous_count += 1
1✔
346
        return name
1✔
347

348
    def _fundamental_type_to_pycstruct_type(self, elem, is_array):
1✔
349
        """Map the fundamental type to pycstruct type"""
350
        typename = elem.attrib["name"]
1✔
351
        typesize = elem.attrib["size"]
1✔
352
        pycstruct_type_name = "int"
1✔
353
        if "float" in typename or "double" in typename:
1✔
354
            pycstruct_type_name = "float"
1✔
355
        elif is_array and "char" in typename:
1✔
356
            if "unsigned" in typename:
1✔
357
                # "unsigned char[]" are considered uint8 array
358
                pycstruct_type_name = "uint"
1✔
359
            elif "signed" in typename:
1✔
360
                # "signed char[]" are considered int8 array
361
                pycstruct_type_name = "int"
1✔
362
            else:
363
                # "char[]" are considered UTF-8 data (string)
364
                pycstruct_type_name = "utf-"
1✔
365
        elif "unsigned" in typename:
1✔
366
            pycstruct_type_name = "uint"
1✔
367
        else:
368
            pycstruct_type_name = "int"
1✔
369

370
        return f"{pycstruct_type_name}{typesize}"
1✔
371

372
    def _get_basic_type_element(self, type_id):
1✔
373
        """Finds the basic type element possible hidden behind TypeDef's or ElaboratedType's"""
374
        elem = self._get_elem_with_id(type_id)
1✔
375
        while elem.tag in ("Typedef", "ElaboratedType"):
1✔
376
            elem = self._get_elem_with_id(elem.attrib["type"])
1✔
377
        return elem
1✔
378

379
    def _get_type(self, type_id, is_array=False):
1✔
380
        elem = self._get_basic_type_element(type_id)
1✔
381

382
        member_type = {}
1✔
383

384
        if elem.tag == "ArrayType":
1✔
385
            size = int(elem.attrib["max"]) - int(elem.attrib["min"]) + 1
1✔
386
            subtype = self._get_type(elem.attrib["type"], is_array=True)
1✔
387
            shape = (size,)
1✔
388
            if "shape" in subtype:
1✔
389
                shape = shape + subtype["shape"]
1✔
390
            member_type["shape"] = shape
1✔
391
            member_type["type_name"] = subtype["type_name"]
1✔
392
            if "reference" in subtype:
1✔
393
                member_type["reference"] = subtype["reference"]
1✔
394
            return member_type
1✔
395

396
        if elem.tag == "CvQualifiedType":  # volatile
1✔
397
            elem = self._get_basic_type_element(elem.attrib["type"])
1✔
398

399
        if elem.tag == "FundamentalType":
1✔
400
            member_type["type_name"] = self._fundamental_type_to_pycstruct_type(
1✔
401
                elem, is_array
402
            )
403
        elif elem.tag == "PointerType":
1✔
404
            member_type["type_name"] = f"uint{elem.attrib['size']}"
1✔
405
        elif elem.tag == "Struct":
1✔
406
            member_type["type_name"] = "struct"
1✔
407
            member_type["reference"] = elem.attrib["id"]
1✔
408
        elif elem.tag == "Union":
1✔
409
            member_type["type_name"] = "union"
1✔
410
            member_type["reference"] = elem.attrib["id"]
1✔
411
        elif elem.tag == "Enumeration":
1✔
412
            member_type["type_name"] = "enum"
1✔
413
            member_type["reference"] = elem.attrib["id"]
1✔
414
        else:
415
            raise RuntimeError(f"Member type {elem.tag} is not supported.")
×
416

417
        return member_type
1✔
418

419

420
###############################################################################
421
# _TypeMetaParser class (internal)
422

423

424
class _TypeMetaParser:
1✔
425
    """This class takes a dictionary with metadata about the types and
426
    generate pycstruct instances.
427
    """
428

429
    # pylint: disable=too-few-public-methods, broad-except
430

431
    def __init__(self, type_meta, byteorder):
1✔
432
        self._type_meta = type_meta
1✔
433
        self._instances = {}
1✔
434
        self._byteorder = byteorder
1✔
435

436
    def parse(self):
1✔
437
        """Parse the type_meta file provided in the constructor"""
438
        for name, datatype in self._type_meta.items():
1✔
439
            if datatype["supported"]:
1✔
440
                try:
1✔
441
                    self._to_instance(name)
1✔
442
                except Exception as exception:
1✔
443
                    logger.warning(
1✔
444
                        """Unable to convert %s, type %s, to pycstruct defintion:
445
  - %s
446
  - Type will be ignored.""",
447
                        name,
448
                        datatype["type"],
449
                        str(exception.args[0]),
450
                    )
451
                    datatype["supported"] = False
1✔
452
        return self._instances
1✔
453

454
    def _to_instance(self, name):
1✔
455
        """Create a pycstruct instance of type with name. Will recursively
456
        create instances of referenced types.
457

458
        Returns the instance.
459
        """
460
        # pylint: disable=too-many-branches
461
        if name in self._instances:
1✔
462
            return self._instances[name]  # Parsed before
1✔
463

464
        meta = self._type_meta[name]
1✔
465

466
        if not meta["supported"]:
1✔
467
            return None  # Not supported
×
468

469
        instance = None
1✔
470

471
        # Structs or union
472
        if meta["type"] == "struct" or meta["type"] == "union":
1✔
473
            is_union = meta["type"] == "union"
1✔
474
            instance = StructDef(self._byteorder, meta["align"], union=is_union)
1✔
475
            for member in meta["members"]:
1✔
476
                shape = member.get("shape", None)
1✔
477
                if "reference" in member:
1✔
478
                    other_instance = self._to_instance(member["reference"])
1✔
479
                    if other_instance is None:
1✔
480
                        raise RuntimeError(
×
481
                            f"Member {member['name']} is of type {member['type']} "
482
                            f"{member['reference']} that is not supported"
483
                        )
484
                    same_level = False
1✔
485
                    if ("same_level" in member) and member["same_level"]:
1✔
486
                        same_level = True
1✔
487
                    instance.add(
1✔
488
                        other_instance,
489
                        member["name"],
490
                        shape=shape,
491
                        same_level=same_level,
492
                    )
493
                else:
494
                    instance.add(member["type"], member["name"], shape=shape)
1✔
495

496
        # Enum
497
        elif meta["type"] == "enum":
1✔
498
            instance = EnumDef(self._byteorder, meta["size"], meta["signed"])
1✔
499
            for member in meta["members"]:
1✔
500
                instance.add(member["name"], member["value"])
1✔
501

502
        # Bitfield
503
        elif meta["type"] == "bitfield":
1✔
504
            instance = BitfieldDef(self._byteorder, meta["size"])
1✔
505
            for member in meta["members"]:
1✔
506
                instance.add(member["name"], member["bits"], member["signed"])
1✔
507

508
        # Not supported
509
        else:
510
            logger.warning(
×
511
                "Unable to create instance for %s (type %s). Not supported.",
512
                meta["name"],
513
                meta["type"],
514
            )
515
            meta["supported"] = False
×
516
            return None
×
517

518
        # Sanity check size:
519
        if meta["size"] != instance.size():
1✔
520
            logger.warning(
×
521
                "%s size, %s, does match indicated size %s",
522
                meta["name"],
523
                instance.size(),
524
                meta["size"],
525
            )
526

527
        self._instances[name] = instance
1✔
528
        return instance
1✔
529

530

531
###############################################################################
532
# Public functions
533

534

535
def parse_file(
1✔
536
    input_files,
537
    byteorder="native",
538
    castxml_cmd="castxml",
539
    castxml_extra_args=None,
540
    cache_path="",
541
    use_cached=False,
542
    **subprocess_kwargs,
543
):
544
    """Parse one or more C source files (C or C++) and generate pycstruct
545
    instances as a result.
546

547
    The result is a dictionary where the keys are the names of the
548
    struct, unions etc. typedef'ed names are also supported.
549

550
    The values of the resulting dictionary are the actual pycstruct
551
    instance connected to the name.
552

553
    This function requires that the external tool
554
    `castxml <https://github.com/CastXML/CastXML>`_ is installed.
555

556
    Alignment will automatically be detected and configured for the pycstruct
557
    instances.
558

559
    Note that following pycstruct types will be used for char arrays:
560

561
    - 'unsigned char []' = uint8 array
562
    - 'signed char []' = int8 array
563
    - 'char []' = utf-8 data (string)
564

565
    :param input_files: Source file name or a list of file names.
566
    :type input_files: str or list
567
    :param byteorder: Byteorder of all elements Valid values are 'native',
568
                      'little' and 'big'. If not specified the 'native'
569
                      byteorder is used.
570
    :type byteorder: str, optional
571
    :param castxml_cmd: Path to the castxml binary. If not specified
572
                        castxml must be within the PATH.
573
    :type castxml_cmd: str, optional
574
    :param castxml_extra_args: Extra arguments to provide to castxml.
575
                               For example definitions etc. Check
576
                               castxml documentation for which
577
                               arguments that are supported.
578
    :type castxml_extra_args: list, optional
579
    :param cache_path: Path where to store temporary files. If not
580
                       provided, the default system temporary
581
                       directory is used.
582
    :type cache_path: str, optional
583
    :param use_cached: If this is True, use previously cached
584
                       output from castxml to avoid re-running
585
                       castxml (since it could be time consuming).
586
                       Default is False.
587
    :type use_cached: boolean, optional
588
    :param subprocess_kwargs: keyword arguments that will be passed down to the
589
                              `subprocess.check_outputs()` call used to run
590
                              castxml. By default, stderr will be redirected to
591
                              stdout. To get the subprocess default behavior,
592
                              pass `stderr=None`.
593
    :return: A dictionary keyed on names of the structs, unions
594
             etc. The values are the actual pycstruct instances.
595
    :rtype: dict
596
    """
597
    # pylint: disable=too-many-arguments
598

599
    input_files = _listify(input_files)
×
600
    xml_filename = _get_hash(input_files) + ".xml"
×
601

602
    if castxml_extra_args is None:
×
603
        castxml_extra_args = []
×
604

605
    if cache_path == "":
×
606
        # Use temporary path to store xml
607
        cache_path = tempfile.gettempdir()
×
608

609
    xml_path = os.path.join(cache_path, xml_filename)
×
610

611
    # Generate XML
612
    if not use_cached or not os.path.isfile(xml_path):
×
613
        _run_castxml(
×
614
            input_files, xml_path, castxml_cmd, castxml_extra_args, **subprocess_kwargs
615
        )
616

617
    # Parse XML
618
    castxml_parser = _CastXmlParser(xml_path)
×
619
    type_meta = castxml_parser.parse()
×
620

621
    # Generate pycstruct instances
622
    type_meta_parser = _TypeMetaParser(type_meta, byteorder)
×
623
    pycstruct_instances = type_meta_parser.parse()
×
624

625
    return pycstruct_instances
×
626

627

628
def parse_str(
1✔
629
    c_str,
630
    byteorder="native",
631
    castxml_cmd="castxml",
632
    castxml_extra_args=None,
633
    cache_path="",
634
    use_cached=False,
635
):
636
    """Parse a string containing C source code, such as struct or
637
    union defintions. Any valid C code is supported.
638

639
    The result is a dictionary where the keys are the names of the
640
    struct, unions etc. typedef'ed names are also supported.
641

642
    The values of the resulting dictionary are the actual pycstruct
643
    instance connected to the name.
644

645
    This function requires that the external tool
646
    `castxml <https://github.com/CastXML/CastXML>`_ is installed.
647

648
    Alignment will automatically be detected and configured for the pycstruct
649
    instances.
650

651
    Note that following pycstruct types will be used for char arrays:
652

653
    - 'unsigned char []' = uint8 array
654
    - 'signed char []' = int8 array
655
    - 'char []' = utf-8 data (string)
656

657
    :param c_str: A string of C source code.
658
    :type c_str: str
659
    :param byteorder: Byteorder of all elements Valid values are 'native',
660
                      'little' and 'big'. If not specified the 'native'
661
                      byteorder is used.
662
    :type byteorder: str, optional
663
    :param castxml_cmd: Path to the castxml binary. If not specified
664
                        castxml must be within the PATH.
665
    :type castxml_cmd: str, optional
666
    :param castxml_extra_args: Extra arguments to provide to castxml.
667
                               For example definitions etc. Check
668
                               castxml documentation for which
669
                               arguments that are supported.
670
    :type castxml_extra_args: list, optional
671
    :param cache_path: Path where to store temporary files. If not
672
                       provided, the default system temporary
673
                       directory is used.
674
    :type cache_path: str, optional
675
    :param use_cached: If this is True, use previously cached
676
                       output from castxml to avoid re-running
677
                       castxml (since it could be time consuming).
678
                       Default is False.
679
    :type use_cached: boolean, optional
680
    :return: A dictionary keyed on names of the structs, unions
681
             etc. The values are the actual pycstruct instances.
682
    :rtype: dict
683
    """
684
    # pylint: disable=too-many-arguments
685

686
    if castxml_extra_args is None:
×
687
        castxml_extra_args = []
×
688

689
    if cache_path == "":
×
690
        # Use temporary path to store xml
691
        cache_path = tempfile.gettempdir()
×
692

693
    c_filename = _get_hash([c_str]) + ".c"
×
694
    c_path = os.path.join(cache_path, c_filename)
×
695

696
    with open(c_path, "w", encoding="utf-8") as file:
×
697
        file.write(c_str)
×
698

699
    return parse_file(
×
700
        c_path, byteorder, castxml_cmd, castxml_extra_args, cache_path, use_cached
701
    )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc