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, Dict, 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 result = "".join(contents)
103 # Let's make Prettier happy with the amount of trailing newlines in the sections.
104 if result.endswith("\n\n"):
106 if not result.endswith("\n"):
107 result = result + "\n"
111 def get_sections_from_readme() -> List[DocSection]:
112 """Gets the sections from README so they can be processed by process_sections.
114 It opens README and goes down line by line looking for sub-header lines which
115 denotes a section. Once it finds a sub-header line, it will create a DocSection
116 object with all of the information currently available. Then on every line, it will
117 track the ending line index of the section. And it repeats this for every sub-header
120 sections: List[DocSection] = []
121 section: Optional[DocSection] = None
122 with open(README, "r", encoding="utf-8") as f:
123 for lineno, line in enumerate(f, start=1):
124 if line.startswith("## "):
125 filename = make_filename(line)
126 section_name = filename[:-3]
127 section = DocSection(
128 name=str(section_name),
130 src_range=SrcRange(lineno, lineno),
131 out_filename=filename,
132 processors=(fix_headers,),
134 sections.append(section)
135 if section is not None:
136 section.src_range.end_line += 1
140 def fix_headers(contents: str) -> str:
141 """Fixes the headers of sections copied from README.
143 Removes one octothorpe (#) from all headers since the contents are no longer nested
144 in a root document (i.e. the README).
146 lines: List[str] = contents.splitlines()
147 fixed_contents: List[str] = []
149 if line.startswith("##"):
151 fixed_contents.append(line + "\n") # splitlines strips the leading newlines
152 return "".join(fixed_contents)
155 def process_sections(
156 custom_sections: List[DocSection], readme_sections: List[DocSection]
158 """Reads, processes, and writes sections to CURRENT_DIR.
160 For each section, the contents will be fetched, processed by processors
161 required by the section, and written to CURRENT_DIR. If it encounters duplicate
162 sections (i.e. shares the same name attribute), it will skip processing the
165 It processes custom sections before the README generated sections so sections in the
166 README can be overwritten with custom options.
168 processed_sections: Dict[str, DocSection] = {}
169 modified_files: Set[Path] = set()
170 sections: List[DocSection] = custom_sections
171 sections.extend(readme_sections)
172 for section in sections:
173 if section.name in processed_sections:
175 f"Skipping '{section.name}' from '{section.src}' as it is a duplicate"
176 f" of a custom section from '{processed_sections[section.name].src}'"
180 LOG.info(f"Processing '{section.name}' from '{section.src}'")
181 target_path: Path = CURRENT_DIR / section.get_out_filename()
182 if target_path in modified_files:
184 f"{target_path} has been already written to, its contents will be"
185 " OVERWRITTEN and notices will be duplicated"
187 contents: str = get_contents(section)
189 # processors goes here
190 if fix_headers in section.processors:
191 contents = fix_headers(contents)
193 with open(target_path, "w", encoding="utf-8") as f:
194 if section.src.suffix == ".md" and section.src != target_path:
195 rel = section.src.resolve().relative_to(CURRENT_DIR.parent)
196 f.write(f'[//]: # "NOTE: THIS FILE WAS AUTOGENERATED FROM {rel}"\n\n')
198 processed_sections[section.name] = section
199 modified_files.add(target_path)
202 # -- Project information -----------------------------------------------------
205 copyright = "2020, Łukasz Langa and contributors to Black"
206 author = "Łukasz Langa and contributors to Black"
208 # Autopopulate version
209 # The version, including alpha/beta/rc tags, but not commit hash and datestamps
210 release = get_distribution("black").version.split("+")[0]
211 # The short X.Y version.
214 version = version.split(sp)[0]
217 DocSection("the_black_code_style", CURRENT_DIR / "the_black_code_style.md"),
218 DocSection("editor_integration", CURRENT_DIR / "editor_integration.md"),
219 DocSection("blackd", CURRENT_DIR / "blackd.md"),
220 DocSection("black_primer", CURRENT_DIR / "black_primer.md"),
221 DocSection("contributing_to_black", CURRENT_DIR / ".." / "CONTRIBUTING.md"),
222 DocSection("change_log", CURRENT_DIR / ".." / "CHANGES.md"),
225 # Sphinx complains when there is a source file that isn't referenced in any of the docs.
226 # Since some sections autogenerated from the README are unused warnings will appear.
228 # Sections must be listed to what their name is when passed through make_filename().
229 blocklisted_sections_from_readme = {
236 make_pypi_svg(release)
237 readme_sections = get_sections_from_readme()
239 x for x in readme_sections if x.name not in blocklisted_sections_from_readme
242 process_sections(custom_sections, readme_sections)
245 # -- General configuration ---------------------------------------------------
247 # If your documentation needs a minimal Sphinx version, state it here.
250 # Add any Sphinx extension module names here, as strings. They can be
251 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
254 "sphinx.ext.autodoc",
255 "sphinx.ext.intersphinx",
256 "sphinx.ext.napoleon",
260 # If you need extensions of a certain version or higher, list them here.
261 needs_extensions = {"recommonmark": "0.5"}
263 # Add any paths that contain templates here, relative to this directory.
264 templates_path = ["_templates"]
266 # The suffix(es) of source filenames.
267 # You can specify multiple suffix as a list of string:
268 source_suffix = [".rst", ".md"]
270 # The master toctree document.
273 # The language for content autogenerated by Sphinx. Refer to documentation
274 # for a list of supported languages.
276 # This is also used if you do content translation via gettext catalogs.
277 # Usually you set "language" from the command line for these cases.
280 # List of patterns, relative to source directory, that match files and
281 # directories to ignore when looking for source files.
282 # This pattern also affects html_static_path and html_extra_path .
284 exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
286 # The name of the Pygments (syntax highlighting) style to use.
287 pygments_style = "sphinx"
290 # -- Options for HTML output -------------------------------------------------
292 # The theme to use for HTML and HTML Help pages. See the documentation for
293 # a list of builtin themes.
295 html_theme = "alabaster"
307 html_theme_options = {
308 "show_related": False,
309 "description": "“Any color you like.”",
310 "github_button": True,
311 "github_user": "psf",
312 "github_repo": "black",
313 "github_type": "star",
314 "show_powered_by": True,
315 "fixed_sidebar": True,
317 "travis_button": True,
321 # Add any paths that contain custom static files (such as style sheets) here,
322 # relative to this directory. They are copied after the builtin static files,
323 # so a file named "default.css" will overwrite the builtin "default.css".
324 html_static_path = ["_static"]
326 # Custom sidebar templates, must be a dictionary that maps document names
329 # The default sidebars (for documents that don't match any pattern) are
330 # defined by theme itself. Builtin themes are using these templates by
331 # default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
332 # 'searchbox.html']``.
337 # -- Options for HTMLHelp output ---------------------------------------------
339 # Output file base name for HTML help builder.
340 htmlhelp_basename = "blackdoc"
343 # -- Options for LaTeX output ------------------------------------------------
346 # The paper size ('letterpaper' or 'a4paper').
348 # 'papersize': 'letterpaper',
349 # The font size ('10pt', '11pt' or '12pt').
351 # 'pointsize': '10pt',
352 # Additional stuff for the LaTeX preamble.
355 # Latex figure (float) alignment
357 # 'figure_align': 'htbp',
360 # Grouping the document tree into LaTeX files. List of tuples
361 # (source start file, target name, title,
362 # author, documentclass [howto, manual, or own class]).
367 "Documentation for Black",
368 "Łukasz Langa and contributors to Black",
374 # -- Options for manual page output ------------------------------------------
376 # One entry per manual page. List of tuples
377 # (source start file, name, description, authors, manual section).
378 man_pages = [(master_doc, "black", "Documentation for Black", [author], 1)]
381 # -- Options for Texinfo output ----------------------------------------------
383 # Grouping the document tree into Texinfo files. List of tuples
384 # (source start file, target name, title, author,
385 # dir menu entry, description, category)
386 texinfo_documents = [
390 "Documentation for Black",
393 "The uncompromising Python code formatter",
399 # -- Options for Epub output -------------------------------------------------
401 # Bibliographic Dublin Core info.
404 epub_publisher = author
405 epub_copyright = copyright
407 # The unique identifier of the text. This can be a ISBN number
408 # or the project homepage.
410 # epub_identifier = ''
412 # A unique identification for the text.
416 # A list of files that should not be packed into the epub file.
417 epub_exclude_files = ["search.html"]
420 # -- Extension configuration -------------------------------------------------
422 autodoc_member_order = "bysource"
424 # -- Options for intersphinx extension ---------------------------------------
426 # Example configuration for intersphinx: refer to the Python standard library.
427 intersphinx_mapping = {"https://docs.python.org/3/": None}