1+ from __future__ import annotations
2+
13import json
24import sys
35import tomllib
46import typing as t
57from collections import defaultdict
8+ from contextlib import suppress
69from dataclasses import dataclass
710from pathlib import Path
811
912import httpx
1013import yaml
1114from git import GitCommandError , Repo
12- from mkdocs .config import Config , config_options
15+ from mkdocs .config import Config , config_options , load_config
1316from mkdocs .config .defaults import MkDocsConfig
1417from mkdocs .exceptions import PluginError
1518from mkdocs .plugins import BasePlugin , get_plugin_logger
3942
4043
4144@config_options .SubConfig
42- class ComponentOption (Config ):
45+ class ComponentDefinition (Config ):
4346 title = config_options .Type (str )
4447 path = config_options .Type (str )
4548 kind = config_options .Type (str )
4649 git_url = config_options .Type (str , default = "" )
4750 rest_api = config_options .Type (str , default = "" )
4851
4952 @property
50- def name (self ) -> str :
53+ def component_name (self ) -> str :
5154 return self .path .rpartition ("/" )[- 1 ]
5255
56+ @property
57+ def repository_name (self ) -> str :
58+ return self .path .split ("/" )[0 ]
59+
5360 @property
5461 def label (self ) -> str :
5562 return self .rest_api
5663
5764
65+ class ComponentFinder :
66+ def __init__ (
67+ self , component_defs : list [ComponentDefinition ] | None , lookup_paths : list [str ] | None
68+ ):
69+ # maps component names to its definition and lookup paths
70+ self .name_to_comp_def : dict [str , ComponentDefinition ] = {}
71+ self .name_to_lookup_dirs : dict [str , list [Path ]] = defaultdict (list )
72+ # initial population
73+ for comp_def in component_defs or []:
74+ self .add_comp_def (comp_def )
75+ for lookup_path in lookup_paths or []:
76+ self .add_lookup_path (lookup_path )
77+
78+ def add_lookup_path (self , lookup_path : str ):
79+ component_name , _ , lookup_dir = lookup_path .rpartition ("@" )
80+ self .name_to_lookup_dirs [component_name ].append (lookup_dir )
81+
82+ def add_comp_def (self , comp_def : ComponentDefinition ):
83+ self .name_to_comp_def [comp_def .component_name ] = comp_def
84+
85+ def load_component (self , comp_name : str ) -> Component | None :
86+ comp_def = self .name_to_comp_def [comp_name ]
87+ comp_data = dict (comp_def )
88+ lookup_dirs = self .name_to_lookup_dirs [comp_def .component_name ]
89+ repository_name = comp_def .repository_name
90+ for lookup_dir in lookup_dirs :
91+ comp_dir = lookup_dir / comp_def .path
92+ if comp_dir .exists ():
93+ comp_data ["version" ] = self ._get_comp_version (comp_dir )
94+ comp_data ["repository_dir" ] = lookup_dir / repository_name
95+ comp_data ["component_dir" ] = comp_dir
96+ return Component (** comp_data )
97+ return None
98+
99+ def load_all (self ) -> tuple [list [Component ], list [ComponentDefinition ]]:
100+ loaded_comps = []
101+ missing_comps = []
102+ for comp_name , comp_opt in self .name_to_comp_def .items ():
103+ component = self .load_component (comp_name )
104+ if component :
105+ loaded_comps .append (component )
106+ else :
107+ missing_comps .append (component )
108+ return missing_comps , loaded_comps
109+
110+ def _get_comp_version (self , comp_dir : Path ) -> str :
111+ with suppress (Exception ):
112+ pyproject = comp_dir / "pyproject.toml"
113+ return tomllib .loads (pyproject .read_text ())["project" ]["version" ]
114+ return "unknown"
115+
116+
117+ def load_components_from (
118+ config_file : Path | None = None ,
119+ pulpdocs_plugin : PulpDocsPlugin | None = None ,
120+ lookup_paths : list [str ] | None = None ,
121+ ) -> tuple [list [Component ], list [ComponentDefinition ]]:
122+ """Load all components defined by pulp-docs using lookup_paths."""
123+ if bool (config_file ) is bool (pulpdocs_plugin ):
124+ raise ValueError ("Provide exactly one of 'config_file' or 'pulpdocs_plugin'." )
125+ _lookup_paths = lookup_paths or [str (Path ().cwd ().parent )]
126+ if config_file :
127+ _pulpdocs_plugin = load_config (str (config_file )).plugins ["PulpDocs" ]
128+ else :
129+ _pulpdocs_plugin = pulpdocs_plugin
130+
131+ component_defs = _pulpdocs_plugin .config .components
132+ comp_finder = ComponentFinder (component_defs , _lookup_paths )
133+ loaded , missing = comp_finder .load_all ()
134+ return loaded , missing
135+
136+
58137@dataclass (frozen = True )
59138class Component :
60139 title : str
@@ -67,32 +146,9 @@ class Component:
67146 repository_dir : Path
68147 component_dir : Path
69148
70- @classmethod
71- def build (cls , find_path : list [str ], component_opt : ComponentOption ):
72- body = dict (component_opt )
73- repository_name = component_opt .path .split ("/" )[0 ]
74- for dir_spec in find_path :
75- repo_filter , _ , basedir = dir_spec .rpartition ("@" )
76- if repo_filter and repo_filter != repository_name :
77- continue
78- basedir = Path (basedir )
79- component_dir = basedir / component_opt .path
80- if component_dir .exists ():
81- version = "unknown"
82- try :
83- pyproject = component_dir / "pyproject.toml"
84- version = tomllib .loads (pyproject .read_text ())["project" ]["version" ]
85- except Exception :
86- pass
87- body ["version" ] = version
88- body ["repository_dir" ] = basedir / repository_name
89- body ["component_dir" ] = component_dir
90- return cls (** body )
91- return None
92-
93149
94150class PulpDocsPluginConfig (Config ):
95- components = config_options .ListOfItems (ComponentOption , default = [])
151+ components = config_options .ListOfItems (ComponentDefinition , default = [])
96152
97153
98154class ComponentNav :
@@ -272,23 +328,6 @@ def rss_items() -> list:
272328 return rss_feed ["items" ][:20 ]
273329
274330
275- def load_components (find_path : list [str ], config : PulpDocsPluginConfig , draft : bool ):
276- loaded_components = []
277- for component_opt in config .components :
278- component = Component .build (find_path , component_opt )
279- if component :
280- loaded_components .append (component )
281- all_components = {o .path for o in config .components }
282- missing_components = all_components .difference ({o .path for o in loaded_components })
283- if not missing_components :
284- return loaded_components
285- # handle missing_components case
286- missing_components = sorted (missing_components )
287- if not draft :
288- raise PluginError (f"Components missing: { missing_components } ." )
289- return loaded_components
290-
291-
292331def log_pulp_config (
293332 mkdocs_file : str , path : list [str ], loaded_components : list [Component ], site_dir : str
294333):
@@ -317,25 +356,32 @@ def get_pulpdocs_git_url(config: PulpDocsPluginConfig):
317356class PulpDocsPlugin (BasePlugin [PulpDocsPluginConfig ]):
318357 def on_config (self , config : MkDocsConfig ) -> MkDocsConfig | None :
319358 # mkdocs may default to the installation dir
359+ self .mkdocs_yml_dir = Path (config .docs_dir ).parent
320360 if "site-packages" in config .site_dir :
321361 config .site_dir = str (Path .cwd () / "site" )
322362
323363 self .blog = ctx_blog .get ()
324364 self .docstrings = ctx_docstrings .get ()
325365 self .draft = ctx_draft .get ()
326366 self .dryrun = ctx_dryrun .get ()
327-
328- self .mkdocs_yml_dir = Path (config .docs_dir ).parent
329- self .find_path = ctx_path .get () or [str (Path ().cwd ().parent )]
330- self .loaded_components = load_components (self .find_path , self .config , self .draft )
331367 self .pulpdocs_git_url = get_pulpdocs_git_url (self .config )
332368
369+ # Load components
370+ lookup_paths = ctx_path .get () or None
371+ loaded_comps , missing_comps = load_components_from (
372+ pulpdocs_plugin = self , lookup_paths = lookup_paths
373+ )
374+ if missing_comps and not self .draft :
375+ missing_comps = sorted (missing_comps )
376+ raise PluginError (f"Components missing: { missing_comps } ." )
377+ self .loaded_comps = loaded_comps
378+
333379 mkdocs_file = self .mkdocs_yml_dir / "mkdocs.yml"
334- log_pulp_config (mkdocs_file , self .find_path , self .loaded_components , config .site_dir )
380+ log_pulp_config (mkdocs_file , self .find_path , self .loaded_comps , config .site_dir )
335381
336382 mkdocstrings_config = config .plugins ["mkdocstrings" ].config
337383 components_var = []
338- for component in self .loaded_components :
384+ for component in self .loaded_comps :
339385 components_var .append (get_component_data (component ))
340386 config .watch .append (str (component .component_dir / "docs" ))
341387 component_dir = component .component_dir .resolve ()
@@ -359,8 +405,8 @@ def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None:
359405 return config
360406
361407 def on_files (self , files : Files , / , * , config : MkDocsConfig ) -> Files | None :
362- log .info (f"Loading Pulp components: { self .loaded_components } " )
363- pulp_docs_component = [c for c in self .loaded_components if c .path == "pulp-docs" ]
408+ log .info (f"Loading Pulp components: { self .loaded_comps } " )
409+ pulp_docs_component = [c for c in self .loaded_comps if c .path == "pulp-docs" ]
364410 if pulp_docs_component :
365411 pulp_docs_git = Repo (pulp_docs_component [0 ].repository_dir )
366412 else :
@@ -369,7 +415,7 @@ def on_files(self, files: Files, /, *, config: MkDocsConfig) -> Files | None:
369415
370416 user_nav : dict [str , t .Any ] = {}
371417 dev_nav : dict [str , t .Any ] = {}
372- for component in self .loaded_components :
418+ for component in self .loaded_comps :
373419 component_dir = component .component_dir
374420
375421 log .info (f"Fetching docs from '{ component .title } '." )
0 commit comments