All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
1 # -*- coding: utf-8 -*-
3 # Configuration file for the Sphinx documentation builder.
5 # This file does only contain a selection of the most common options. For a
6 # full list see the documentation:
7 # http://www.sphinx-doc.org/en/stable/config
9 # -- Path setup --------------------------------------------------------------
11 # If extensions (or modules to document with autodoc) are in another directory,
12 # add these directories to sys.path here. If the directory is relative to the
13 # documentation root, use os.path.abspath to make it absolute, like shown here.
15 from pathlib import Path
18 from typing import Callable, List, Optional, Pattern, Tuple, Set
19 from dataclasses import dataclass
22 from pkg_resources import get_distribution
24 logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.INFO)
26 LOG = logging.getLogger(__name__)
28 CURRENT_DIR = Path(__file__).parent
29 README = CURRENT_DIR / ".." / "README.md"
30 REFERENCE_DIR = CURRENT_DIR / "reference"
31 STATIC_DIR = CURRENT_DIR / "_static"
36 """Tracks which part of a file to get a section's content.
39 start_line: The line where the section starts (i.e. its sub-header) (inclusive).
40 end_line: The line where the section ends (usually next sub-header) (exclusive).
49 """Tracks information about a section of documentation.
52 name: The section's name. This will used to detect duplicate sections.
53 src: The filepath to get its contents.
54 processors: The processors to run before writing the section to CURRENT_DIR.
55 out_filename: The filename to use when writing the section to CURRENT_DIR.
56 src_range: The line range of SRC to gets its contents.
61 src_range: SrcRange = SrcRange(0, 1_000_000)
62 out_filename: str = ""
63 processors: Tuple[Callable, ...] = ()
65 def get_out_filename(self) -> str:
66 if not self.out_filename:
67 return self.name + ".md"
69 return self.out_filename
72 def make_pypi_svg(version: str) -> None:
73 template: Path = CURRENT_DIR / "_static" / "pypi_template.svg"
74 target: Path = CURRENT_DIR / "_static" / "pypi.svg"
75 with open(str(template), "r", encoding="utf8") as f:
76 svg: str = string.Template(f.read()).substitute(version=version)
77 with open(str(target), "w", encoding="utf8") as f:
81 def make_filename(line: str) -> str:
82 non_letters: Pattern = re.compile(r"[^a-z]+")
83 filename: str = line[3:].rstrip().lower()
84 filename = non_letters.sub("_", filename)
85 if filename.startswith("_"):
86 filename = filename[1:]
87 if filename.endswith("_"):
88 filename = filename[:-1]
89 return filename + ".md"
92 def get_contents(section: DocSection) -> str:
93 """Gets the contents for the DocSection."""
94 contents: List[str] = []
95 src: Path = section.src
96 start_line: int = section.src_range.start_line
97 end_line: int = section.src_range.end_line
98 with open(src, "r", encoding="utf-8") as f:
99 for lineno, line in enumerate(f, start=1):
100 if lineno >= start_line and lineno < end_line:
101 contents.append(line)
102 return "".join(contents)
105 def get_sections_from_readme() -> List[DocSection]:
106 """Gets the sections from README so they can be processed by process_sections.
108 It opens README and goes down line by line looking for sub-header lines which
109 denotes a section. Once it finds a sub-header line, it will create a DocSection
110 object with all of the information currently available. Then on every line, it will
111 track the ending line index of the section. And it repeats this for every sub-header
114 sections: List[DocSection] = []
115 section: Optional[DocSection] = None
116 with open(README, "r", encoding="utf-8") as f:
117 for lineno, line in enumerate(f, start=1):
118 if line.startswith("## "):
119 filename = make_filename(line)
120 section_name = filename[:-3]
121 section = DocSection(
122 name=str(section_name),
124 src_range=SrcRange(lineno, lineno),
125 out_filename=filename,
126 processors=(fix_headers,),
128 sections.append(section)
129 if section is not None:
130 section.src_range.end_line += 1
134 def fix_headers(contents: str) -> str:
135 """Fixes the headers of sections copied from README.
137 Removes one octothorpe (#) from all headers since the contents are no longer nested
138 in a root document (i.e. the README).
140 lines: List[str] = contents.splitlines()
141 fixed_contents: List[str] = []
143 if line.startswith("##"):
145 fixed_contents.append(line + "\n") # splitlines strips the leading newlines
146 return "".join(fixed_contents)
149 def process_sections(
150 custom_sections: List[DocSection], readme_sections: List[DocSection]
152 """Reads, processes, and writes sections to CURRENT_DIR.
154 For each section, the contents will be fetched, processed by processors
155 required by the section, and written to CURRENT_DIR. If it encounters duplicate
156 sections (i.e. shares the same name attribute), it will skip processing the
159 It processes custom sections before the README generated sections so sections in the
160 README can be overwritten with custom options.
162 processed_sections: Set[str] = set()
163 modified_files: Set[Path] = set()
164 sections: List[DocSection] = custom_sections
165 sections.extend(readme_sections)
166 for section in sections:
167 LOG.info(f"Processing '{section.name}' from {section.src}")
168 if section.name in processed_sections:
170 f"Skipping '{section.name}' from '{section.src}' as it is a duplicate"
174 target_path: Path = CURRENT_DIR / section.get_out_filename()
175 if target_path in modified_files:
177 f"{target_path} has been already written to, its contents will be"
178 " OVERWRITTEN and notices will be duplicated"
180 contents: str = get_contents(section)
182 # processors goes here
183 if fix_headers in section.processors:
184 contents = fix_headers(contents)
186 with open(target_path, "w", encoding="utf-8") as f:
187 if section.src.suffix == ".md" and section.src != target_path:
188 rel = section.src.resolve().relative_to(CURRENT_DIR.parent)
189 f.write(f'[//]: # "NOTE: THIS FILE WAS AUTOGENERATED FROM {rel}"\n\n')
191 processed_sections.add(section.name)
192 modified_files.add(target_path)
195 # -- Project information -----------------------------------------------------
198 copyright = "2020, Łukasz Langa and contributors to Black"
199 author = "Łukasz Langa and contributors to Black"
201 # Autopopulate version
202 # The version, including alpha/beta/rc tags, but not commit hash and datestamps
203 release = get_distribution("black").version.split("+")[0]
204 # The short X.Y version.
207 version = version.split(sp)[0]
210 DocSection("the_black_code_style", CURRENT_DIR / "the_black_code_style.md"),
211 DocSection("editor_integration", CURRENT_DIR / "editor_integration.md"),
212 DocSection("blackd", CURRENT_DIR / "blackd.md"),
213 DocSection("black_primer", CURRENT_DIR / "black_primer.md"),
214 DocSection("contributing_to_black", CURRENT_DIR / ".." / "CONTRIBUTING.md"),
215 DocSection("change_log", CURRENT_DIR / ".." / "CHANGES.md"),
218 # Sphinx complains when there is a source file that isn't referenced in any of the docs.
219 # Since some sections autogenerated from the README are unused warnings will appear.
221 # Sections must be listed to what their name is when passed through make_filename().
222 blocklisted_sections_from_readme = {
229 make_pypi_svg(release)
230 readme_sections = get_sections_from_readme()
232 x for x in readme_sections if x.name not in blocklisted_sections_from_readme
235 process_sections(custom_sections, readme_sections)
238 # -- General configuration ---------------------------------------------------
240 # If your documentation needs a minimal Sphinx version, state it here.
243 # Add any Sphinx extension module names here, as strings. They can be
244 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
247 "sphinx.ext.autodoc",
248 "sphinx.ext.intersphinx",
249 "sphinx.ext.napoleon",
253 # If you need extensions of a certain version or higher, list them here.
254 needs_extensions = {"recommonmark": "0.5"}
256 # Add any paths that contain templates here, relative to this directory.
257 templates_path = ["_templates"]
259 # The suffix(es) of source filenames.
260 # You can specify multiple suffix as a list of string:
261 source_suffix = [".rst", ".md"]
263 # The master toctree document.
266 # The language for content autogenerated by Sphinx. Refer to documentation
267 # for a list of supported languages.
269 # This is also used if you do content translation via gettext catalogs.
270 # Usually you set "language" from the command line for these cases.
273 # List of patterns, relative to source directory, that match files and
274 # directories to ignore when looking for source files.
275 # This pattern also affects html_static_path and html_extra_path .
277 exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
279 # The name of the Pygments (syntax highlighting) style to use.
280 pygments_style = "sphinx"
283 # -- Options for HTML output -------------------------------------------------
285 # The theme to use for HTML and HTML Help pages. See the documentation for
286 # a list of builtin themes.
288 html_theme = "alabaster"
300 html_theme_options = {
301 "show_related": False,
302 "description": "“Any color you like.”",
303 "github_button": True,
304 "github_user": "psf",
305 "github_repo": "black",
306 "github_type": "star",
307 "show_powered_by": True,
308 "fixed_sidebar": True,
310 "travis_button": True,
314 # Add any paths that contain custom static files (such as style sheets) here,
315 # relative to this directory. They are copied after the builtin static files,
316 # so a file named "default.css" will overwrite the builtin "default.css".
317 html_static_path = ["_static"]
319 # Custom sidebar templates, must be a dictionary that maps document names
322 # The default sidebars (for documents that don't match any pattern) are
323 # defined by theme itself. Builtin themes are using these templates by
324 # default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
325 # 'searchbox.html']``.
330 # -- Options for HTMLHelp output ---------------------------------------------
332 # Output file base name for HTML help builder.
333 htmlhelp_basename = "blackdoc"
336 # -- Options for LaTeX output ------------------------------------------------
339 # The paper size ('letterpaper' or 'a4paper').
341 # 'papersize': 'letterpaper',
342 # The font size ('10pt', '11pt' or '12pt').
344 # 'pointsize': '10pt',
345 # Additional stuff for the LaTeX preamble.
348 # Latex figure (float) alignment
350 # 'figure_align': 'htbp',
353 # Grouping the document tree into LaTeX files. List of tuples
354 # (source start file, target name, title,
355 # author, documentclass [howto, manual, or own class]).
360 "Documentation for Black",
361 "Łukasz Langa and contributors to Black",
367 # -- Options for manual page output ------------------------------------------
369 # One entry per manual page. List of tuples
370 # (source start file, name, description, authors, manual section).
371 man_pages = [(master_doc, "black", "Documentation for Black", [author], 1)]
374 # -- Options for Texinfo output ----------------------------------------------
376 # Grouping the document tree into Texinfo files. List of tuples
377 # (source start file, target name, title, author,
378 # dir menu entry, description, category)
379 texinfo_documents = [
383 "Documentation for Black",
386 "The uncompromising Python code formatter",
392 # -- Options for Epub output -------------------------------------------------
394 # Bibliographic Dublin Core info.
397 epub_publisher = author
398 epub_copyright = copyright
400 # The unique identifier of the text. This can be a ISBN number
401 # or the project homepage.
403 # epub_identifier = ''
405 # A unique identification for the text.
409 # A list of files that should not be packed into the epub file.
410 epub_exclude_files = ["search.html"]
413 # -- Extension configuration -------------------------------------------------
415 autodoc_member_order = "bysource"
417 # -- Options for intersphinx extension ---------------------------------------
419 # Example configuration for intersphinx: refer to the Python standard library.
420 intersphinx_mapping = {"https://docs.python.org/3/": None}