66import urllib .parse
77from collections import defaultdict
88from copy import deepcopy
9- from dataclasses import asdict , dataclass
9+ from dataclasses import asdict , dataclass , field
1010from typing import (
1111 Any ,
1212 Callable ,
3333 DuplicateDirective ,
3434 ExpectedPathArg ,
3535 ExpectedTabs ,
36+ GuideAlreadyHasChapter ,
3637 InvalidChapter ,
3738 InvalidChild ,
3839 InvalidContextError ,
@@ -723,19 +724,52 @@ class ChapterData:
723724 description : Optional [str ]
724725 guides : List [str ]
725726
727+ @dataclass
728+ class GuideData :
729+ chapter_name : str = ""
730+ completion_time : int = 0
731+ description : MutableSequence [n .Node ] = field (default_factory = list )
732+ title : Sequence [n .InlineNode ] = field (default_factory = list )
733+
734+ def serialize (self ) -> n .SerializedNode :
735+ result : n .SerializedNode = {
736+ "chapter_name" : self .chapter_name ,
737+ "completion_time" : self .completion_time ,
738+ "description" : [node .serialize () for node in self .description ],
739+ "title" : [node .serialize () for node in self .title ],
740+ }
741+ return result
742+
743+ def add_guides_metadata (self , document : Dict [str , SerializableType ]) -> None :
744+ """Adds the guides-related metadata to the project's metadata document"""
745+ if self .chapters :
746+ document ["chapters" ] = {k : asdict (v ) for k , v in self .chapters .items ()}
747+
748+ if self .guides :
749+ slug_title_mapping = self .context [HeadingHandler ].slug_title_mapping
750+ for slug , title in slug_title_mapping .items ():
751+ if slug in self .guides :
752+ self .guides [slug ].title = title
753+ document ["guides" ] = {k : v .serialize () for k , v in self .guides .items ()}
754+
726755 def __init__ (self , context : Context ) -> None :
727756 super ().__init__ (context )
728757 self .chapters : Dict [str , GuidesHandler .ChapterData ] = {}
758+ self .guides : Dict [str , GuidesHandler .GuideData ] = defaultdict (
759+ GuidesHandler .GuideData
760+ )
729761
730- def __handle_chapter (self , chapter : n .Directive , current_file : FileId ) -> None :
731- """Saves a chapter's data into the handler's dictionary of chapters"""
762+ def __get_guides (
763+ self , chapter : n .Directive , chapter_title : str , current_file : FileId
764+ ) -> List [str ]:
765+ """Returns the eligible guides that belong to a given chapter"""
732766
733767 guides : List [str ] = []
734768
735769 for child in chapter .get_child_of_type (n .Directive ):
770+ line = child .span [0 ]
771+
736772 if child .name != "guide" :
737- # Chapter directives should contain only guide directives
738- line = chapter .span [0 ]
739773 self .context .diagnostics [current_file ].append (
740774 InvalidChild (child .name , "chapter" , "guide" , line , None )
741775 )
@@ -744,26 +778,40 @@ def __handle_chapter(self, chapter: n.Directive, current_file: FileId) -> None:
744778 guide_argument = child .argument
745779 if not guide_argument :
746780 self .context .diagnostics [current_file ].append (
747- ExpectedPathArg (child .name , child . span [ 0 ] )
781+ ExpectedPathArg (child .name , line )
748782 )
749783 continue
750784
751785 guide_slug = clean_slug (guide_argument [0 ].get_text ())
786+
787+ current_guide_data = self .guides [guide_slug ]
788+ if current_guide_data .chapter_name :
789+ self .context .diagnostics [current_file ].append (
790+ GuideAlreadyHasChapter (
791+ guide_slug ,
792+ current_guide_data .chapter_name ,
793+ chapter_title ,
794+ line ,
795+ )
796+ )
797+ continue
798+ else :
799+ current_guide_data .chapter_name = chapter_title
800+
752801 guides .append (guide_slug )
753802
754- line = chapter . span [ 0 ]
803+ return guides
755804
756- # A chapter should always have at least one guide
757- if not guides :
758- self .context .diagnostics [current_file ].append (
759- MissingChild ("chapter" , "guide" , line )
760- )
761- return
805+ def __handle_chapter (self , chapter : n .Directive , current_file : FileId ) -> None :
806+ """Saves a chapter's data into the handler's dictionary of chapters"""
762807
808+ line = chapter .span [0 ]
763809 title_argument = chapter .argument
764- if not title_argument :
810+ if len ( title_argument ) != 1 :
765811 self .context .diagnostics [current_file ].append (
766- InvalidChapter ("Title argument is empty." , line )
812+ InvalidChapter (
813+ "Invalid title argument. The title should be plain text." , line
814+ )
767815 )
768816 return
769817
@@ -781,6 +829,14 @@ def __handle_chapter(self, chapter: n.Directive, current_file: FileId) -> None:
781829 if not description :
782830 return
783831
832+ guides : List [str ] = self .__get_guides (chapter , title , current_file )
833+ # A chapter should always have at least one guide
834+ if not guides :
835+ self .context .diagnostics [current_file ].append (
836+ MissingChild ("chapter" , "guide" , line )
837+ )
838+ return
839+
784840 if not self .chapters .get (title ):
785841 self .chapters [title ] = GuidesHandler .ChapterData (
786842 len (self .chapters ) + 1 , description , guides
@@ -824,17 +880,26 @@ def __handle_chapters(
824880 )
825881
826882 def enter_node (self , fileid_stack : FileIdStack , node : n .Node ) -> None :
883+ if not isinstance (node , n .Directive ):
884+ return
885+
827886 current_file : FileId = fileid_stack .current
887+ current_slug = clean_slug (current_file .without_known_suffix )
828888
829- if (
830- isinstance (node , n .Directive )
831- and node .name == "chapters"
832- and current_file .as_posix () == "index.txt"
833- ):
889+ if node .name == "chapters" and current_file == FileId ("index.txt" ):
834890 if self .chapters :
835891 return
836-
837892 self .__handle_chapters (node , current_file )
893+ elif node .name == "time" :
894+ if not node .argument :
895+ return
896+ try :
897+ completion_time = int (node .argument [0 ].get_text ())
898+ self .guides [current_slug ].completion_time = completion_time
899+ except ValueError :
900+ pass
901+ elif node .name == "short-description" :
902+ self .guides [current_slug ].description = node .children
838903
839904
840905class IAHandler (Handler ):
@@ -1391,9 +1456,7 @@ def generate_metadata(cls, context: Context) -> n.SerializedNode:
13911456 if iatree :
13921457 document ["iatree" ] = iatree
13931458
1394- chapters = context [GuidesHandler ].chapters
1395- if chapters :
1396- document ["chapters" ] = {k : asdict (v ) for k , v in chapters .items ()}
1459+ context [GuidesHandler ].add_guides_metadata (document )
13971460
13981461 return document
13991462
0 commit comments