source: rtems-central/rtemsqual/items.py @ dfca4bc

Last change on this file since dfca4bc was dfca4bc, checked in by Sebastian Huber <sebastian.huber@…>, on Mar 17, 2020 at 1:29:56 PM

content: Add method to register license/copyrights

  • Property mode set to 100644
File size: 5.8 KB
Line 
1# SPDX-License-Identifier: BSD-2-Clause
2""" This module provides specification items and an item cache. """
3
4# Copyright (C) 2019, 2020 embedded brains GmbH (http://www.embedded-brains.de)
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
19# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25# POSSIBILITY OF SUCH DAMAGE.
26
27# pylint: disable=useless-object-inheritance
28
29import os
30import pickle
31import stat
32from typing import Any, List, Dict
33import yaml
34
35from rtemsqual.content import SphinxContent
36
37ItemList = List["Item"]
38ItemMap = Dict[str, "Item"]
39
40
41class Item(object):
42    """ Objects of this class represent a specification item. """
43    def __init__(self, uid: str, data: Any):
44        self._uid = uid
45        self._data = data
46        self._links = []  # type: ItemList
47        self._children = []  # type: ItemList
48
49    def __getitem__(self, name: str) -> Any:
50        return self._data[name]
51
52    @property
53    def uid(self) -> str:
54        """ Returns the UID of the item. """
55        return self._uid
56
57    @property
58    def parents(self) -> ItemList:
59        """ Returns the list of parents of this items. """
60        return self._links
61
62    @property
63    def children(self) -> ItemList:
64        """ Returns the list of children of this items. """
65        return self._children
66
67    def init_parents(self, item_cache: "ItemCache"):
68        """ Initializes the list of parents of this items. """
69        for link in self._data["links"]:
70            self._links.append(item_cache[list(link.keys())[0]])
71
72    def add_child(self, child: "Item"):
73        """ Adds a child to this item. """
74        self._children.append(child)
75
76    def register_license_and_copyrights(self, content: SphinxContent):
77        """ Registers the license and copyrights of this item. """
78        content.register_license(self["SPDX-License-Identifier"])
79        for statement in self["copyrights"]:
80            content.register_copyright(statement)
81
82
83def _is_one_item_newer(path: str, mtime: float) -> bool:
84    for name in os.listdir(path):
85        path2 = os.path.join(path, name)
86        if name.endswith(".yml") and not name.startswith("."):
87            mtime2 = os.path.getmtime(path2)
88            if mtime <= mtime2:
89                return True
90        else:
91            mode = os.lstat(path2).st_mode
92            if stat.S_ISDIR(mode) and _is_one_item_newer(path2, mtime):
93                return True
94    return False
95
96
97def _must_update_item_cache(path: str, cache_file: str) -> bool:
98    try:
99        mtime = os.path.getmtime(cache_file)
100        return _is_one_item_newer(path, mtime)
101    except FileNotFoundError:
102        return True
103
104
105def _load_from_yaml(data_by_uid: Dict[str, Any], path: str) -> None:
106    for name in os.listdir(path):
107        path2 = os.path.join(path, name)
108        if name.endswith(".yml") and not name.startswith("."):
109            uid = os.path.basename(name).replace(".yml", "")
110            with open(path2, "r") as src:
111                data_by_uid[uid] = yaml.safe_load(src.read())
112        else:
113            mode = os.lstat(path2).st_mode
114            if stat.S_ISDIR(mode):
115                _load_from_yaml(data_by_uid, path2)
116
117
118class ItemCache(object):
119    """ This class provides a cache of specification items. """
120    def __init__(self, config: Any):
121        self._items = {}  # type: ItemMap
122        self._top_level = {}  # type: ItemMap
123        self._load_items(config)
124
125    def __getitem__(self, uid: str) -> Item:
126        return self._items[uid]
127
128    @property
129    def top_level(self) -> ItemMap:
130        """ Returns the list of top-level specification items. """
131        return self._top_level
132
133    def _load_items_in_path(self, path: str, cache_file: str) -> None:
134        data_by_uid = {}  # type: Dict[str, Any]
135        if _must_update_item_cache(path, cache_file):
136            _load_from_yaml(data_by_uid, path)
137            with open(cache_file, "wb") as out:
138                pickle.dump(data_by_uid, out)
139        else:
140            with open(cache_file, "rb") as out:
141                data_by_uid = pickle.load(out)
142        for uid, data in data_by_uid.items():
143            item = Item(uid, data)
144            self._items[uid] = item
145            if not item["links"]:
146                self._top_level[uid] = item
147
148    def _init_parents(self) -> None:
149        for item in self._items.values():
150            item.init_parents(self)
151
152    def _init_children(self) -> None:
153        for item in self._items.values():
154            for parent in item.parents:
155                parent.add_child(item)
156
157    def _load_items(self, config: Any) -> None:
158        cache_file = config["cache-file"]
159        for path in config["paths"]:
160            self._load_items_in_path(path, cache_file)
161        self._init_parents()
162        self._init_children()
Note: See TracBrowser for help on using the repository browser.