blob: fa9bf693bc2ae14dc98fa2887fb36520d78f6724 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright 2020 SkyWater PDK Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
import argparse
import json
import os
import pathlib
import pprint
import sys
import textwrap
from docutils import nodes
from docutils.parsers.rst import Directive
from docutils.statemachine import ViewList
from sphinx.util.nodes import nested_parse_with_titles
from typing import Tuple, List, Dict
verbose = False
# using a list-table here to allow for easier line breaks in description
rst_header_line_char = '-'
rst_header = 'Cells in libraries cross-index'
rst_template ="""\
{header_line}
{header_underline}
.. list-table::
:header-rows: 1
* - Cell name
- {lib_suffixes}
- Number of libraries
{cell_list}
"""
cell_template = """\
* - {cell_name}
- {lib_suffixes_match}
- {lib_count}
"""
tab_entry = '\n - '
def collect(library_dir) -> Tuple[str, List[str]]:
"""Collect the available definitions for cells in a library
Parameters
----------
library_dir: str or pathlib.Path
Path to a library.
Returns
-------
lib : str
Library name
cells : list of pathlib.Path
definition files for cells in the library.
"""
if not isinstance(library_dir, pathlib.Path):
library_dir = pathlib.Path(library_dir)
libname = None
cells = set()
for p in library_dir.rglob("definition.json"):
if not p.is_file():
continue
define_data = json.load(open(p))
if not define_data['type'] == 'cell':
continue
cells.add(p)
if libname is None:
libname = define_data['library']
cells = list(sorted(cells))
if not len(cells):
raise FileNotFoundError("No cell definitions found")
assert len(libname) > 0
return libname, cells
def get_cell_names(cells):
"""Get cell names from definition filess
Parameters
----------
cells: list of pathlib.Path
List of paths to JSON description files
Returns
-------
cell_list: list of str
List of cell names
"""
cell_list = []
for cell in cells:
with open(str(cell), "r") as c:
cell_json = json.load(c)
cell_list.append( cell_json['name'] )
return cell_list
def generate_crosstable (cells_lib, link_template=''):
"""Generate the RST paragraph containing cell cross reference table
Parameters:
cells_lib: dictionary with list of libraries per cell name [dict]
link_template: cell README generic path (with {lib} and {cell} tags) [str]
Returns:
paragraph: Generated paragraph [str]
"""
assert isinstance (cells_lib, dict)
paragraph = ""
cell_list = ""
lib_suffixes = set()
for v in cells_lib.values():
lib_suffixes.update( [lib.rpartition('_')[2] for lib in v] )
lib_suffixes = list(lib_suffixes)
lib_suffixes.sort()
#print (lib_suffixes)
for c in sorted(cells_lib):
ls = {} # dictionary of cell library shorts (suffixes)
for lib in cells_lib[c]:
ls [lib.rpartition('_')[2]] = lib
mark = ' :doc:`x <' + link_template + '>`' # lib match mark with link
suff_match = [ mark.format(cell=c,lib=ls[s]) if s in ls else '' for s in lib_suffixes ]
cell_list += cell_template.format(
cell_name = c,
lib_suffixes_match = tab_entry.join(suff_match),
lib_count = str (len(ls))
)
paragraph = rst_template.format(
header_line = rst_header,
header_underline = rst_header_line_char * len(rst_header),
lib_suffixes = tab_entry.join(lib_suffixes),
cell_list = cell_list
)
return paragraph
def cells_in_libs (libpaths):
"""Generate the RST paragraph containing cell cross reference table
Parameters:
libpaths: list of cell library paths [list of pathlib.Path]
Returns:
cells_lib: dictionary with list of libraries containing each cell name [dict]
"""
lib_dirs = [pathlib.Path(d) for d in libpaths]
lib_dirs = [d for d in lib_dirs if d.is_dir()]
libs_toc = dict()
for lib in lib_dirs:
try:
libname, cells = collect(lib)
if verbose:
print(f"{lib} \tLibrary name: {libname}, found {len(cells)} cells")
libs_toc[libname] = get_cell_names(cells)
except FileNotFoundError:
if verbose:
print (f'{lib} \t- no cells found')
all_cells = set()
cells_lib = {}
for lib,cells in libs_toc.items():
all_cells.update(set(cells))
for c in cells:
cells_lib[c] = cells_lib.get(c, []) + [lib]
return cells_lib
# --- Sphinx extension wrapper ---
class CellCrossIndex(Directive):
required_arguments = 1
optional_arguments = 1
has_content = True
def run(self):
env = self.state.document.settings.env
dirname = env.docname.rpartition('/')[0]
arg = self.arguments[0]
arg = dirname + '/' + arg
output = dirname + '/' + self.arguments[1] if len(self.arguments)>2 else None
path = pathlib.Path(arg).expanduser()
parts = path.parts[1:] if path.is_absolute() else path.parts
paths = pathlib.Path(path.root).glob(str(pathlib.Path("").joinpath(*parts)))
paths = list(paths)
paths = [d.resolve() for d in paths if d.is_dir()]
cells_lib = cells_in_libs ( list(paths) )
celllink = self.arguments[0].replace('*','{lib}') + '/cells/{cell}/README'
paragraph = generate_crosstable (cells_lib,celllink)
if output is None: # dynamic output
# parse rst string to docutils nodes
rst = ViewList()
for i,line in enumerate(paragraph.split('\n')):
rst.append(line, "cell-index-tmp.rst", i+1)
node = nodes.section()
node.document = self.state.document
nested_parse_with_titles(self.state, rst, node)
return node.children
else: # file output
if not output.endswith('.rst'):
output += '.rst'
with open(str(output),'w') as f:
f.write(paragraph)
paragraph_node = nodes.paragraph()
return [paragraph_node]
def setup(app):
app.add_directive("cross_index", CellCrossIndex)
return {
'version': '0.1',
'parallel_read_safe': True,
'parallel_write_safe': True,
}
# --- stand alone, command line operation ---
def main():
global verbose
parser = argparse.ArgumentParser()
alllibpath = '../../../libraries/*/latest'
celllink = 'libraries/{lib}/cells/{cell}/README'
parser.add_argument(
"-v",
"--verbose",
help="increase verbosity",
action="store_true"
)
parser.add_argument(
"--all_libs",
help="process all libs in "+alllibpath,
action="store_true")
parser.add_argument(
"libraries_dirs",
help="Paths to the library directories. Eg. " + alllibpath,
type=pathlib.Path,
nargs="*")
parser.add_argument(
"-o",
"--outfile",
help="Output file name",
type=pathlib.Path,
default=pathlib.Path('./cell-index.rst'))
parser.add_argument(
"-c",
"--celllink",
help="Specify cell link template. Default: '" + celllink +"'",
type=str,
default=celllink)
args = parser.parse_args()
verbose = args.verbose
if args.all_libs:
path = pathlib.Path(alllibpath).expanduser()
parts = path.parts[1:] if path.is_absolute() else path.parts
paths = pathlib.Path(path.root).glob(str(pathlib.Path("").joinpath(*parts)))
args.libraries_dirs = list(paths)
cells_lib = cells_in_libs (args.libraries_dirs)
par = generate_crosstable (cells_lib,args.celllink)
with open(str(args.outfile),'w') as f:
f.write(par)
if __name__ == "__main__":
sys.exit(main())