Skip to content

Gutenberg source

gutenberg_kg.gutenberg

Download books from Project Gutenberg as structured Markdown.

Public API (used by the Click CLI in gutenberg_kg.cli.cmd_download): download_book() — fetch one book by Gutenberg ID run_search() — search the Gutenberg OPDS catalog run_catalog() — download all books from a catalog file run_fetch_genre() — interactive genre fetch (search → confirm → download) run_survey() — scan corpus and print download/ingest status

Entry point: gutenkg download [options]

Output structure (per book): corpus/// <slug>.md — structured Markdown with chapter/section headings reference.md — Gutenberg metadata, subjects, and source info</p> <div class="doc doc-children"> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.download_book" class="doc doc-heading"> <code class="highlight language-python"><span class="n">download_book</span><span class="p">(</span><span class="n">ebook_id</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">genre</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">force</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">dry_run</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.download_book" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Download a book, convert to Markdown, and save with reference.</p> <p><span class="doc-section-title">Parameters:</span></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr class="doc-section-item"> <td> <code>ebook_id</code> </td> <td> <code><span title="int">int</span></code> </td> <td> <div class="doc-md-description"> <p>Project Gutenberg numeric book ID.</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>title</code> </td> <td> <code><span title="str">str</span> | None</code> </td> <td> <div class="doc-md-description"> <p>Optional title override (auto-detected from metadata if omitted).</p> </div> </td> <td> <code>None</code> </td> </tr> <tr class="doc-section-item"> <td> <code>genre</code> </td> <td> <code><span title="str">str</span> | None</code> </td> <td> <div class="doc-md-description"> <p>Optional genre subdirectory name (one of ALL_GENRES). When provided the book is written to <code>REPO_ROOT/<genre>/<Title>/</code>; when omitted, it is written to <code>REPO_ROOT/<Title>/</code> as before.</p> </div> </td> <td> <code>None</code> </td> </tr> <tr class="doc-section-item"> <td> <code>force</code> </td> <td> <code><span title="bool">bool</span></code> </td> <td> <div class="doc-md-description"> <p>Re-download even when the Markdown file already exists.</p> </div> </td> <td> <code>False</code> </td> </tr> <tr class="doc-section-item"> <td> <code>dry_run</code> </td> <td> <code><span title="bool">bool</span></code> </td> <td> <div class="doc-md-description"> <p>Print what would be done without writing any files.</p> </div> </td> <td> <code>False</code> </td> </tr> </tbody> </table> <p><span class="doc-section-title">Returns:</span></p> <table> <thead> <tr> <th>Type</th> <th>Description</th> </tr> </thead> <tbody> <tr class="doc-section-item"> <td> <code><span title="str">str</span></code> </td> <td> <div class="doc-md-description"> <p>Absolute path to the primary Markdown file.</p> </div> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">654</span> <span class="normal">655</span> <span class="normal">656</span> <span class="normal">657</span> <span class="normal">658</span> <span class="normal">659</span> <span class="normal">660</span> <span class="normal">661</span> <span class="normal">662</span> <span class="normal">663</span> <span class="normal">664</span> <span class="normal">665</span> <span class="normal">666</span> <span class="normal">667</span> <span class="normal">668</span> <span class="normal">669</span> <span class="normal">670</span> <span class="normal">671</span> <span class="normal">672</span> <span class="normal">673</span> <span class="normal">674</span> <span class="normal">675</span> <span class="normal">676</span> <span class="normal">677</span> <span class="normal">678</span> <span class="normal">679</span> <span class="normal">680</span> <span class="normal">681</span> <span class="normal">682</span> <span class="normal">683</span> <span class="normal">684</span> <span class="normal">685</span> <span class="normal">686</span> <span class="normal">687</span> <span class="normal">688</span> <span class="normal">689</span> <span class="normal">690</span> <span class="normal">691</span> <span class="normal">692</span> <span class="normal">693</span> <span class="normal">694</span> <span class="normal">695</span> <span class="normal">696</span> <span class="normal">697</span> <span class="normal">698</span> <span class="normal">699</span> <span class="normal">700</span> <span class="normal">701</span> <span class="normal">702</span> <span class="normal">703</span> <span class="normal">704</span> <span class="normal">705</span> <span class="normal">706</span> <span class="normal">707</span> <span class="normal">708</span> <span class="normal">709</span> <span class="normal">710</span> <span class="normal">711</span> <span class="normal">712</span> <span class="normal">713</span> <span class="normal">714</span> <span class="normal">715</span> <span class="normal">716</span> <span class="normal">717</span> <span class="normal">718</span> <span class="normal">719</span> <span class="normal">720</span> <span class="normal">721</span> <span class="normal">722</span> <span class="normal">723</span> <span class="normal">724</span> <span class="normal">725</span> <span class="normal">726</span> <span class="normal">727</span> <span class="normal">728</span> <span class="normal">729</span> <span class="normal">730</span> <span class="normal">731</span> <span class="normal">732</span> <span class="normal">733</span> <span class="normal">734</span> <span class="normal">735</span> <span class="normal">736</span> <span class="normal">737</span> <span class="normal">738</span> <span class="normal">739</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">download_book</span><span class="p">(</span> <span class="n">ebook_id</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">title</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">genre</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">force</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span> <span class="n">dry_run</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span> <span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Download a book, convert to Markdown, and save with reference.</span> <span class="sd"> :param ebook_id: Project Gutenberg numeric book ID.</span> <span class="sd"> :param title: Optional title override (auto-detected from metadata if omitted).</span> <span class="sd"> :param genre: Optional genre subdirectory name (one of ALL_GENRES). When</span> <span class="sd"> provided the book is written to ``REPO_ROOT/<genre>/<Title>/``; when</span> <span class="sd"> omitted, it is written to ``REPO_ROOT/<Title>/`` as before.</span> <span class="sd"> :param force: Re-download even when the Markdown file already exists.</span> <span class="sd"> :param dry_run: Print what would be done without writing any files.</span> <span class="sd"> :returns: Absolute path to the primary Markdown file.</span> <span class="sd"> """</span> <span class="c1"># ID-based idempotence check: a book already present under ANY directory</span> <span class="c1"># name (e.g. downloaded under its OPDS title before a catalog override was</span> <span class="c1"># curated) must not be fetched again. Runs before the metadata fetch so a</span> <span class="c1"># skip costs no network round-trip.</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">force</span><span class="p">:</span> <span class="n">search_root</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">CORPUS_ROOT</span><span class="p">,</span> <span class="n">genre</span><span class="p">)</span> <span class="k">if</span> <span class="n">genre</span> <span class="k">else</span> <span class="n">CORPUS_ROOT</span> <span class="n">existing</span> <span class="o">=</span> <span class="n">_find_book_by_id</span><span class="p">(</span><span class="n">ebook_id</span><span class="p">,</span> <span class="n">search_root</span><span class="p">)</span> <span class="k">if</span> <span class="n">existing</span><span class="p">:</span> <span class="n">existing_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">existing</span><span class="p">))</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" [=] already downloaded (Gutenberg #</span><span class="si">{</span><span class="n">ebook_id</span><span class="si">}</span><span class="s2">): </span><span class="si">{</span><span class="n">existing_dir</span><span class="si">}</span><span class="s2"> — skipping"</span><span class="p">)</span> <span class="c1"># Catalogue on the skip path too: a book already on disk but absent</span> <span class="c1"># from the catalog is exactly the pre-existing drift, and re-running</span> <span class="c1"># the download is the natural way to repair it.</span> <span class="n">record_in_catalog</span><span class="p">(</span><span class="n">genre</span><span class="p">,</span> <span class="n">ebook_id</span><span class="p">,</span> <span class="n">existing_dir</span><span class="p">,</span> <span class="n">dry_run</span><span class="o">=</span><span class="n">dry_run</span><span class="p">)</span> <span class="k">return</span> <span class="n">existing</span> <span class="c1"># Fetch metadata (needed for the title before we can check idempotence)</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" Fetching metadata for Gutenberg #</span><span class="si">{</span><span class="n">ebook_id</span><span class="si">}</span><span class="s2">..."</span><span class="p">)</span> <span class="n">meta</span> <span class="o">=</span> <span class="n">fetch_metadata</span><span class="p">(</span><span class="n">ebook_id</span><span class="p">)</span> <span class="n">title</span> <span class="o">=</span> <span class="n">title</span> <span class="ow">or</span> <span class="n">meta</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"title"</span><span class="p">)</span> <span class="ow">or</span> <span class="sa">f</span><span class="s2">"Book_</span><span class="si">{</span><span class="n">ebook_id</span><span class="si">}</span><span class="s2">"</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"title"</span><span class="p">]</span> <span class="o">=</span> <span class="n">title</span> <span class="c1"># Resolve output directory</span> <span class="k">if</span> <span class="n">genre</span><span class="p">:</span> <span class="n">book_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">CORPUS_ROOT</span><span class="p">,</span> <span class="n">genre</span><span class="p">,</span> <span class="n">title</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="n">book_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">CORPUS_ROOT</span><span class="p">,</span> <span class="n">title</span><span class="p">)</span> <span class="n">slug</span> <span class="o">=</span> <span class="n">slugify</span><span class="p">(</span><span class="n">title</span><span class="p">)</span> <span class="n">out_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">book_dir</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">slug</span><span class="si">}</span><span class="s2">.md"</span><span class="p">)</span> <span class="c1"># Idempotence check</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">force</span> <span class="ow">and</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">out_path</span><span class="p">):</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" [=] already downloaded: </span><span class="si">{</span><span class="n">title</span><span class="si">}</span><span class="s2"> — skipping"</span><span class="p">)</span> <span class="n">record_in_catalog</span><span class="p">(</span><span class="n">genre</span><span class="p">,</span> <span class="n">ebook_id</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="n">dry_run</span><span class="o">=</span><span class="n">dry_run</span><span class="p">)</span> <span class="k">return</span> <span class="n">out_path</span> <span class="k">if</span> <span class="n">dry_run</span><span class="p">:</span> <span class="n">dest</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">genre</span><span class="p">,</span> <span class="n">title</span><span class="p">)</span> <span class="k">if</span> <span class="n">genre</span> <span class="k">else</span> <span class="n">title</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" [dry] would download: </span><span class="si">{</span><span class="n">title</span><span class="si">}</span><span class="s2"> (Gutenberg #</span><span class="si">{</span><span class="n">ebook_id</span><span class="si">}</span><span class="s2">) → </span><span class="si">{</span><span class="n">dest</span><span class="si">}</span><span class="s2">/"</span><span class="p">)</span> <span class="k">return</span> <span class="n">out_path</span> <span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">book_dir</span><span class="p">,</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> <span class="c1"># Download plain text</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" Downloading: </span><span class="si">{</span><span class="n">title</span><span class="si">}</span><span class="s2"> (Gutenberg #</span><span class="si">{</span><span class="n">ebook_id</span><span class="si">}</span><span class="s2">)"</span><span class="p">)</span> <span class="n">url</span> <span class="o">=</span> <span class="n">GUTENBERG_TXT_URL</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">ebook_id</span><span class="o">=</span><span class="n">ebook_id</span><span class="p">)</span> <span class="n">raw_text</span> <span class="o">=</span> <span class="n">fetch_url</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="c1"># Strip boilerplate</span> <span class="n">clean_text</span> <span class="o">=</span> <span class="n">strip_boilerplate</span><span class="p">(</span><span class="n">raw_text</span><span class="p">)</span> <span class="c1"># Convert to structured Markdown</span> <span class="n">markdown</span> <span class="o">=</span> <span class="n">text_to_markdown</span><span class="p">(</span><span class="n">clean_text</span><span class="p">,</span> <span class="n">meta</span><span class="p">)</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">out_path</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">"utf-8"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span> <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">markdown</span><span class="p">)</span> <span class="n">size_kb</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">getsize</span><span class="p">(</span><span class="n">out_path</span><span class="p">)</span> <span class="o">/</span> <span class="mi">1024</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" Saved: </span><span class="si">{</span><span class="n">out_path</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">size_kb</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2"> KB)"</span><span class="p">)</span> <span class="c1"># Write reference file</span> <span class="n">ref_path</span> <span class="o">=</span> <span class="n">write_reference</span><span class="p">(</span><span class="n">book_dir</span><span class="p">,</span> <span class="n">meta</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" Saved: </span><span class="si">{</span><span class="n">ref_path</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="n">record_in_catalog</span><span class="p">(</span><span class="n">genre</span><span class="p">,</span> <span class="n">ebook_id</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="n">dry_run</span><span class="o">=</span><span class="n">dry_run</span><span class="p">)</span> <span class="k">return</span> <span class="n">out_path</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.fetch_metadata" class="doc doc-heading"> <code class="highlight language-python"><span class="n">fetch_metadata</span><span class="p">(</span><span class="n">ebook_id</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.fetch_metadata" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Fetch book metadata from the Gutenberg OPDS feed.</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">132</span> <span class="normal">133</span> <span class="normal">134</span> <span class="normal">135</span> <span class="normal">136</span> <span class="normal">137</span> <span class="normal">138</span> <span class="normal">139</span> <span class="normal">140</span> <span class="normal">141</span> <span class="normal">142</span> <span class="normal">143</span> <span class="normal">144</span> <span class="normal">145</span> <span class="normal">146</span> <span class="normal">147</span> <span class="normal">148</span> <span class="normal">149</span> <span class="normal">150</span> <span class="normal">151</span> <span class="normal">152</span> <span class="normal">153</span> <span class="normal">154</span> <span class="normal">155</span> <span class="normal">156</span> <span class="normal">157</span> <span class="normal">158</span> <span class="normal">159</span> <span class="normal">160</span> <span class="normal">161</span> <span class="normal">162</span> <span class="normal">163</span> <span class="normal">164</span> <span class="normal">165</span> <span class="normal">166</span> <span class="normal">167</span> <span class="normal">168</span> <span class="normal">169</span> <span class="normal">170</span> <span class="normal">171</span> <span class="normal">172</span> <span class="normal">173</span> <span class="normal">174</span> <span class="normal">175</span> <span class="normal">176</span> <span class="normal">177</span> <span class="normal">178</span> <span class="normal">179</span> <span class="normal">180</span> <span class="normal">181</span> <span class="normal">182</span> <span class="normal">183</span> <span class="normal">184</span> <span class="normal">185</span> <span class="normal">186</span> <span class="normal">187</span> <span class="normal">188</span> <span class="normal">189</span> <span class="normal">190</span> <span class="normal">191</span> <span class="normal">192</span> <span class="normal">193</span> <span class="normal">194</span> <span class="normal">195</span> <span class="normal">196</span> <span class="normal">197</span> <span class="normal">198</span> <span class="normal">199</span> <span class="normal">200</span> <span class="normal">201</span> <span class="normal">202</span> <span class="normal">203</span> <span class="normal">204</span> <span class="normal">205</span> <span class="normal">206</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">fetch_metadata</span><span class="p">(</span><span class="n">ebook_id</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-></span> <span class="nb">dict</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Fetch book metadata from the Gutenberg OPDS feed."""</span> <span class="n">url</span> <span class="o">=</span> <span class="n">GUTENBERG_OPDS_URL</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">ebook_id</span><span class="o">=</span><span class="n">ebook_id</span><span class="p">)</span> <span class="n">xml_text</span> <span class="o">=</span> <span class="n">fetch_url</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="n">root</span> <span class="o">=</span> <span class="n">ET</span><span class="o">.</span><span class="n">fromstring</span><span class="p">(</span><span class="n">xml_text</span><span class="p">)</span> <span class="n">entry</span> <span class="o">=</span> <span class="n">root</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"atom:entry"</span><span class="p">,</span> <span class="n">NS</span><span class="p">)</span> <span class="k">if</span> <span class="n">entry</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="k">return</span> <span class="p">{</span><span class="s2">"ebook_id"</span><span class="p">:</span> <span class="n">ebook_id</span><span class="p">}</span> <span class="n">meta</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">object</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"ebook_id"</span><span class="p">:</span> <span class="n">ebook_id</span><span class="p">}</span> <span class="c1"># Title</span> <span class="n">title_el</span> <span class="o">=</span> <span class="n">entry</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"atom:title"</span><span class="p">,</span> <span class="n">NS</span><span class="p">)</span> <span class="k">if</span> <span class="n">title_el</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">title_el</span><span class="o">.</span><span class="n">text</span><span class="p">:</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"title"</span><span class="p">]</span> <span class="o">=</span> <span class="n">title_el</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="c1"># Authors: only a fallback. The feed lists co-authors in the reverse of</span> <span class="c1"># the catalog's order, which is how Auguste Maquet came to be credited</span> <span class="c1"># with The Count of Monte Cristo; the RDF creators below replace these.</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"authors"</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span> <span class="n">display_name</span><span class="p">(</span><span class="n">el</span><span class="o">.</span><span class="n">text</span><span class="p">)</span> <span class="k">for</span> <span class="n">el</span> <span class="ow">in</span> <span class="n">entry</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s2">"atom:author/atom:name"</span><span class="p">,</span> <span class="n">NS</span><span class="p">)</span> <span class="k">if</span> <span class="n">el</span><span class="o">.</span><span class="n">text</span> <span class="ow">and</span> <span class="n">el</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="p">]</span> <span class="c1"># Published date</span> <span class="n">pub_el</span> <span class="o">=</span> <span class="n">entry</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"atom:published"</span><span class="p">,</span> <span class="n">NS</span><span class="p">)</span> <span class="k">if</span> <span class="n">pub_el</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">pub_el</span><span class="o">.</span><span class="n">text</span><span class="p">:</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"published"</span><span class="p">]</span> <span class="o">=</span> <span class="n">pub_el</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">()[:</span><span class="mi">10</span><span class="p">]</span> <span class="c1"># Rights</span> <span class="n">rights_el</span> <span class="o">=</span> <span class="n">entry</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"atom:rights"</span><span class="p">,</span> <span class="n">NS</span><span class="p">)</span> <span class="k">if</span> <span class="n">rights_el</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">rights_el</span><span class="o">.</span><span class="n">text</span><span class="p">:</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"rights"</span><span class="p">]</span> <span class="o">=</span> <span class="n">rights_el</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="c1"># Subjects from <category> elements</span> <span class="n">subjects</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">for</span> <span class="n">cat</span> <span class="ow">in</span> <span class="n">entry</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s2">"atom:category"</span><span class="p">,</span> <span class="n">NS</span><span class="p">):</span> <span class="n">term</span> <span class="o">=</span> <span class="n">cat</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"term"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="k">if</span> <span class="n">term</span><span class="p">:</span> <span class="n">subjects</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">term</span><span class="p">)</span> <span class="k">if</span> <span class="n">subjects</span><span class="p">:</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"subjects"</span><span class="p">]</span> <span class="o">=</span> <span class="n">subjects</span> <span class="c1"># Language</span> <span class="n">lang_el</span> <span class="o">=</span> <span class="n">entry</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"dcterms:language"</span><span class="p">,</span> <span class="n">NS</span><span class="p">)</span> <span class="k">if</span> <span class="n">lang_el</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">lang_el</span><span class="o">.</span><span class="n">text</span><span class="p">:</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"language"</span><span class="p">]</span> <span class="o">=</span> <span class="n">lang_el</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="c1"># Summary and other details from content block</span> <span class="n">content_el</span> <span class="o">=</span> <span class="n">entry</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"atom:content"</span><span class="p">,</span> <span class="n">NS</span><span class="p">)</span> <span class="k">if</span> <span class="n">content_el</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span> <span class="c1"># The content is XHTML — extract text from <p> elements</span> <span class="n">div</span> <span class="o">=</span> <span class="n">content_el</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"xhtml:div"</span><span class="p">,</span> <span class="n">NS</span><span class="p">)</span> <span class="k">if</span> <span class="n">div</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">div</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s2">"xhtml:p"</span><span class="p">,</span> <span class="n">NS</span><span class="p">):</span> <span class="n">text</span> <span class="o">=</span> <span class="s2">""</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">itertext</span><span class="p">())</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="k">if</span> <span class="n">text</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"Summary:"</span><span class="p">):</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"summary"</span><span class="p">]</span> <span class="o">=</span> <span class="n">text</span><span class="p">[</span><span class="nb">len</span><span class="p">(</span><span class="s2">"Summary:"</span><span class="p">)</span> <span class="p">:]</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="k">elif</span> <span class="n">text</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"Note:"</span><span class="p">):</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"note"</span><span class="p">]</span> <span class="o">=</span> <span class="n">text</span><span class="p">[</span><span class="nb">len</span><span class="p">(</span><span class="s2">"Note:"</span><span class="p">)</span> <span class="p">:]</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"gutenberg_url"</span><span class="p">]</span> <span class="o">=</span> <span class="n">GUTENBERG_PAGE_URL</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">ebook_id</span><span class="o">=</span><span class="n">ebook_id</span><span class="p">)</span> <span class="c1"># The RDF catalog's creators and the first creator's provenance</span> <span class="c1"># (birth/death/Wikipedia) replace the feed's authors when it has any.</span> <span class="c1"># A record with no creator at all (scripture, mostly) is credited to</span> <span class="c1"># NO_AUTHOR rather than left blank.</span> <span class="n">meta</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">_fetch_rdf_author</span><span class="p">(</span><span class="n">ebook_id</span><span class="p">))</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"authors"</span><span class="p">]:</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"authors"</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">NO_AUTHOR</span><span class="p">]</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"author"</span><span class="p">]</span> <span class="o">=</span> <span class="n">credit</span><span class="p">(</span><span class="n">meta</span><span class="p">[</span><span class="s2">"authors"</span><span class="p">])</span> <span class="k">return</span> <span class="n">meta</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.fetch_url" class="doc doc-heading"> <code class="highlight language-python"><span class="n">fetch_url</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">retries</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">backoff</span><span class="o">=</span><span class="mf">2.0</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.fetch_url" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Fetch a URL with retries and exponential backoff.</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">111</span> <span class="normal">112</span> <span class="normal">113</span> <span class="normal">114</span> <span class="normal">115</span> <span class="normal">116</span> <span class="normal">117</span> <span class="normal">118</span> <span class="normal">119</span> <span class="normal">120</span> <span class="normal">121</span> <span class="normal">122</span> <span class="normal">123</span> <span class="normal">124</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">fetch_url</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">retries</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span> <span class="n">backoff</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">2.0</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Fetch a URL with retries and exponential backoff."""</span> <span class="k">for</span> <span class="n">attempt</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">retries</span><span class="p">):</span> <span class="k">try</span><span class="p">:</span> <span class="n">req</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">Request</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s2">"User-Agent"</span><span class="p">:</span> <span class="s2">"GreatBooksProject/1.0"</span><span class="p">})</span> <span class="k">with</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span> <span class="k">as</span> <span class="n">resp</span><span class="p">:</span> <span class="k">return</span> <span class="n">resp</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">"utf-8-sig"</span><span class="p">)</span> <span class="k">except</span> <span class="p">(</span><span class="n">urllib</span><span class="o">.</span><span class="n">error</span><span class="o">.</span><span class="n">URLError</span><span class="p">,</span> <span class="n">urllib</span><span class="o">.</span><span class="n">error</span><span class="o">.</span><span class="n">HTTPError</span><span class="p">,</span> <span class="ne">TimeoutError</span><span class="p">)</span> <span class="k">as</span> <span class="n">exc</span><span class="p">:</span> <span class="k">if</span> <span class="n">attempt</span> <span class="o">==</span> <span class="n">retries</span> <span class="o">-</span> <span class="mi">1</span><span class="p">:</span> <span class="k">raise</span> <span class="n">wait</span> <span class="o">=</span> <span class="n">backoff</span> <span class="o">*</span> <span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="n">attempt</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" Retry </span><span class="si">{</span><span class="n">attempt</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">retries</span><span class="si">}</span><span class="s2"> after error: </span><span class="si">{</span><span class="n">exc</span><span class="si">}</span><span class="s2"> (waiting </span><span class="si">{</span><span class="n">wait</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2">s)"</span><span class="p">)</span> <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">wait</span><span class="p">)</span> <span class="k">return</span> <span class="s2">""</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.parse_catalog" class="doc doc-heading"> <code class="highlight language-python"><span class="n">parse_catalog</span><span class="p">(</span><span class="n">catalog_path</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.parse_catalog" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Parse a catalog file. Each line: <ebook_id>[\t<title>]</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">841</span> <span class="normal">842</span> <span class="normal">843</span> <span class="normal">844</span> <span class="normal">845</span> <span class="normal">846</span> <span class="normal">847</span> <span class="normal">848</span> <span class="normal">849</span> <span class="normal">850</span> <span class="normal">851</span> <span class="normal">852</span> <span class="normal">853</span> <span class="normal">854</span> <span class="normal">855</span> <span class="normal">856</span> <span class="normal">857</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">parse_catalog</span><span class="p">(</span><span class="n">catalog_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">list</span><span class="p">[</span><span class="nb">tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">]]:</span> <span class="w"> </span><span class="sd">"""Parse a catalog file. Each line: <ebook_id>[\\t<title>]"""</span> <span class="n">entries</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">catalog_path</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">"utf-8"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span> <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="mi">1</span><span class="p">):</span> <span class="n">line</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">line</span> <span class="ow">or</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"#"</span><span class="p">):</span> <span class="k">continue</span> <span class="n">parts</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">"</span><span class="se">\t</span><span class="s2">"</span><span class="p">)</span> <span class="k">try</span><span class="p">:</span> <span class="n">ebook_id</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">parts</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">())</span> <span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" Warning: skipping non-numeric ID on line </span><span class="si">{</span><span class="n">line_num</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">parts</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">!r}</span><span class="s2">"</span><span class="p">)</span> <span class="k">continue</span> <span class="n">title</span> <span class="o">=</span> <span class="n">parts</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">parts</span><span class="p">)</span> <span class="o">>=</span> <span class="mi">2</span> <span class="k">else</span> <span class="kc">None</span> <span class="n">entries</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">ebook_id</span><span class="p">,</span> <span class="n">title</span><span class="p">))</span> <span class="k">return</span> <span class="n">entries</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.rdf_authors" class="doc doc-heading"> <code class="highlight language-python"><span class="n">rdf_authors</span><span class="p">(</span><span class="n">xml_text</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.rdf_authors" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Read a book's authors from its Gutenberg RDF record.</p> <p>The authors are the <code>dcterms:creator</code> agents, in the record's order, which puts the primary author first: Dumas before Maquet, Polo before Rusticiano. Translators and editors are recorded under relator terms, not as creators, so they are never counted as authors.</p> <p>Provenance describes the first creator only. It used to be read from the first agent anywhere in the record, which could be a translator.</p> <p><span class="doc-section-title">Parameters:</span></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr class="doc-section-item"> <td> <code>xml_text</code> </td> <td> <code><span title="str">str</span></code> </td> <td> <div class="doc-md-description"> <p>The RDF document.</p> </div> </td> <td> <em>required</em> </td> </tr> </tbody> </table> <p><span class="doc-section-title">Returns:</span></p> <table> <thead> <tr> <th>Type</th> <th>Description</th> </tr> </thead> <tbody> <tr class="doc-section-item"> <td> <code><span title="dict">dict</span></code> </td> <td> <div class="doc-md-description"> <p><code>authors</code> (display names) plus whichever of <code>author_birth</code>, <code>author_death</code>, <code>author_url</code> and <code>author_agent_id</code> the first creator has. Empty when the record has no creator or does not parse.</p> </div> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">209</span> <span class="normal">210</span> <span class="normal">211</span> <span class="normal">212</span> <span class="normal">213</span> <span class="normal">214</span> <span class="normal">215</span> <span class="normal">216</span> <span class="normal">217</span> <span class="normal">218</span> <span class="normal">219</span> <span class="normal">220</span> <span class="normal">221</span> <span class="normal">222</span> <span class="normal">223</span> <span class="normal">224</span> <span class="normal">225</span> <span class="normal">226</span> <span class="normal">227</span> <span class="normal">228</span> <span class="normal">229</span> <span class="normal">230</span> <span class="normal">231</span> <span class="normal">232</span> <span class="normal">233</span> <span class="normal">234</span> <span class="normal">235</span> <span class="normal">236</span> <span class="normal">237</span> <span class="normal">238</span> <span class="normal">239</span> <span class="normal">240</span> <span class="normal">241</span> <span class="normal">242</span> <span class="normal">243</span> <span class="normal">244</span> <span class="normal">245</span> <span class="normal">246</span> <span class="normal">247</span> <span class="normal">248</span> <span class="normal">249</span> <span class="normal">250</span> <span class="normal">251</span> <span class="normal">252</span> <span class="normal">253</span> <span class="normal">254</span> <span class="normal">255</span> <span class="normal">256</span> <span class="normal">257</span> <span class="normal">258</span> <span class="normal">259</span> <span class="normal">260</span> <span class="normal">261</span> <span class="normal">262</span> <span class="normal">263</span> <span class="normal">264</span> <span class="normal">265</span> <span class="normal">266</span> <span class="normal">267</span> <span class="normal">268</span> <span class="normal">269</span> <span class="normal">270</span> <span class="normal">271</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">rdf_authors</span><span class="p">(</span><span class="n">xml_text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">dict</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Read a book's authors from its Gutenberg RDF record.</span> <span class="sd"> The authors are the ``dcterms:creator`` agents, in the record's order,</span> <span class="sd"> which puts the primary author first: Dumas before Maquet, Polo before</span> <span class="sd"> Rusticiano. Translators and editors are recorded under relator terms, not</span> <span class="sd"> as creators, so they are never counted as authors.</span> <span class="sd"> Provenance describes the first creator only. It used to be read from the</span> <span class="sd"> first agent anywhere in the record, which could be a translator.</span> <span class="sd"> :param xml_text: The RDF document.</span> <span class="sd"> :return: ``authors`` (display names) plus whichever of ``author_birth``,</span> <span class="sd"> ``author_death``, ``author_url`` and ``author_agent_id`` the first</span> <span class="sd"> creator has. Empty when the record has no creator or does not parse.</span> <span class="sd"> """</span> <span class="k">try</span><span class="p">:</span> <span class="n">root</span> <span class="o">=</span> <span class="n">ET</span><span class="o">.</span><span class="n">fromstring</span><span class="p">(</span><span class="n">xml_text</span><span class="p">)</span> <span class="k">except</span> <span class="n">ET</span><span class="o">.</span><span class="n">ParseError</span><span class="p">:</span> <span class="k">return</span> <span class="p">{}</span> <span class="n">agents</span> <span class="o">=</span> <span class="p">[</span> <span class="n">agent</span> <span class="k">for</span> <span class="n">creator</span> <span class="ow">in</span> <span class="n">root</span><span class="o">.</span><span class="n">iter</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="se">{{</span><span class="si">{</span><span class="n">RDF_NS</span><span class="p">[</span><span class="s1">'dcterms'</span><span class="p">]</span><span class="si">}</span><span class="se">}}</span><span class="s2">creator"</span><span class="p">)</span> <span class="k">if</span> <span class="p">(</span><span class="n">agent</span> <span class="o">:=</span> <span class="n">creator</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"pgterms:agent"</span><span class="p">,</span> <span class="n">RDF_NS</span><span class="p">))</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="p">]</span> <span class="n">names</span> <span class="o">=</span> <span class="p">[</span> <span class="n">display_name</span><span class="p">(</span><span class="n">name</span><span class="o">.</span><span class="n">text</span><span class="p">)</span> <span class="k">for</span> <span class="n">agent</span> <span class="ow">in</span> <span class="n">agents</span> <span class="k">if</span> <span class="p">(</span><span class="n">name</span> <span class="o">:=</span> <span class="n">agent</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"pgterms:name"</span><span class="p">,</span> <span class="n">RDF_NS</span><span class="p">))</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">name</span><span class="o">.</span><span class="n">text</span> <span class="ow">and</span> <span class="n">name</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="p">]</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">names</span><span class="p">:</span> <span class="k">return</span> <span class="p">{}</span> <span class="n">result</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">object</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"authors"</span><span class="p">:</span> <span class="n">names</span><span class="p">}</span> <span class="n">first</span> <span class="o">=</span> <span class="n">agents</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="n">m</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="sa">r</span><span class="s2">"/agents/(\d+)$"</span><span class="p">,</span> <span class="n">first</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">_RDF_ABOUT</span><span class="p">,</span> <span class="s2">""</span><span class="p">))</span> <span class="k">if</span> <span class="n">m</span><span class="p">:</span> <span class="n">result</span><span class="p">[</span><span class="s2">"author_agent_id"</span><span class="p">]</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">m</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span> <span class="n">birth</span> <span class="o">=</span> <span class="n">first</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"pgterms:birthdate"</span><span class="p">,</span> <span class="n">RDF_NS</span><span class="p">)</span> <span class="k">if</span> <span class="n">birth</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">birth</span><span class="o">.</span><span class="n">text</span><span class="p">:</span> <span class="n">result</span><span class="p">[</span><span class="s2">"author_birth"</span><span class="p">]</span> <span class="o">=</span> <span class="n">birth</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="n">death</span> <span class="o">=</span> <span class="n">first</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"pgterms:deathdate"</span><span class="p">,</span> <span class="n">RDF_NS</span><span class="p">)</span> <span class="k">if</span> <span class="n">death</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">death</span><span class="o">.</span><span class="n">text</span><span class="p">:</span> <span class="n">result</span><span class="p">[</span><span class="s2">"author_death"</span><span class="p">]</span> <span class="o">=</span> <span class="n">death</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="c1"># English Wikipedia when the record has it, else whichever language it</span> <span class="c1"># lists; the record's own order is not stable enough to choose by.</span> <span class="n">wikis</span> <span class="o">=</span> <span class="p">[</span> <span class="n">href</span> <span class="k">for</span> <span class="n">webpage</span> <span class="ow">in</span> <span class="n">first</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s2">"pgterms:webpage"</span><span class="p">,</span> <span class="n">RDF_NS</span><span class="p">)</span> <span class="k">if</span> <span class="s2">"wikipedia.org"</span> <span class="ow">in</span> <span class="p">(</span><span class="n">href</span> <span class="o">:=</span> <span class="n">webpage</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">_RDF_RESOURCE</span><span class="p">,</span> <span class="s2">""</span><span class="p">))</span> <span class="p">]</span> <span class="n">english</span> <span class="o">=</span> <span class="p">[</span><span class="n">href</span> <span class="k">for</span> <span class="n">href</span> <span class="ow">in</span> <span class="n">wikis</span> <span class="k">if</span> <span class="s2">"//en.wikipedia.org"</span> <span class="ow">in</span> <span class="n">href</span><span class="p">]</span> <span class="k">if</span> <span class="n">english</span> <span class="ow">or</span> <span class="n">wikis</span><span class="p">:</span> <span class="n">result</span><span class="p">[</span><span class="s2">"author_url"</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">english</span> <span class="ow">or</span> <span class="n">wikis</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="k">return</span> <span class="n">result</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.record_in_catalog" class="doc doc-heading"> <code class="highlight language-python"><span class="n">record_in_catalog</span><span class="p">(</span><span class="n">genre</span><span class="p">,</span> <span class="n">ebook_id</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="n">dry_run</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.record_in_catalog" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Record a downloaded book in its genre catalog, if not already there.</p> <p>The catalog files under <code>scripts/catalogs/</code> were originally batch <em>input</em> manifests: <code>download catalog</code> read them and nothing ever wrote them. That left the three ad-hoc paths -- <code>download book</code>, <code>download fetch-genre</code> and <code>ia download</code> -- adding books to the corpus that no catalog recorded, permanently and silently. The drift is invisible to <code>gutenkg audit</code>, which only checks the catalog-to-corpus direction, and it means a fresh clone replayed through <code>download catalog</code> reproduces a strict subset of the corpus. Calling this on every successful download closes that loop.</p> <p><code>title</code> is written as the override column and must be the book's directory name, since :func:<code>gutenberg_kg.audit.audit_corpus</code> reports a catalog title that differs from the directory as an error.</p> <p>Appending is idempotent on the ID, so re-running a download repairs a missing catalog line rather than duplicating an existing one.</p> <p><span class="doc-section-title">Parameters:</span></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr class="doc-section-item"> <td> <code>genre</code> </td> <td> <code><span title="str">str</span> | None</code> </td> <td> <div class="doc-md-description"> <p>Genre whose catalog to write; <code>None</code> means the book was downloaded to the corpus root, which no catalog covers, so nothing is written.</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>ebook_id</code> </td> <td> <code><span title="int">int</span></code> </td> <td> <div class="doc-md-description"> <p>Project Gutenberg numeric book ID.</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>title</code> </td> <td> <code><span title="str">str</span></code> </td> <td> <div class="doc-md-description"> <p>The book's directory name, written as the title override.</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>dry_run</code> </td> <td> <code><span title="bool">bool</span></code> </td> <td> <div class="doc-md-description"> <p>When true, report what would be written but write nothing.</p> </div> </td> <td> <code>False</code> </td> </tr> </tbody> </table> <p><span class="doc-section-title">Returns:</span></p> <table> <thead> <tr> <th>Type</th> <th>Description</th> </tr> </thead> <tbody> <tr class="doc-section-item"> <td> <code><span title="bool">bool</span></code> </td> <td> <div class="doc-md-description"> <p>True when a line was appended, or would have been under <code>dry_run</code> -- so a caller can count pending repairs without writing.</p> </div> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">742</span> <span class="normal">743</span> <span class="normal">744</span> <span class="normal">745</span> <span class="normal">746</span> <span class="normal">747</span> <span class="normal">748</span> <span class="normal">749</span> <span class="normal">750</span> <span class="normal">751</span> <span class="normal">752</span> <span class="normal">753</span> <span class="normal">754</span> <span class="normal">755</span> <span class="normal">756</span> <span class="normal">757</span> <span class="normal">758</span> <span class="normal">759</span> <span class="normal">760</span> <span class="normal">761</span> <span class="normal">762</span> <span class="normal">763</span> <span class="normal">764</span> <span class="normal">765</span> <span class="normal">766</span> <span class="normal">767</span> <span class="normal">768</span> <span class="normal">769</span> <span class="normal">770</span> <span class="normal">771</span> <span class="normal">772</span> <span class="normal">773</span> <span class="normal">774</span> <span class="normal">775</span> <span class="normal">776</span> <span class="normal">777</span> <span class="normal">778</span> <span class="normal">779</span> <span class="normal">780</span> <span class="normal">781</span> <span class="normal">782</span> <span class="normal">783</span> <span class="normal">784</span> <span class="normal">785</span> <span class="normal">786</span> <span class="normal">787</span> <span class="normal">788</span> <span class="normal">789</span> <span class="normal">790</span> <span class="normal">791</span> <span class="normal">792</span> <span class="normal">793</span> <span class="normal">794</span> <span class="normal">795</span> <span class="normal">796</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">record_in_catalog</span><span class="p">(</span> <span class="n">genre</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">,</span> <span class="n">ebook_id</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">title</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">dry_run</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span> <span class="p">)</span> <span class="o">-></span> <span class="nb">bool</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Record a downloaded book in its genre catalog, if not already there.</span> <span class="sd"> The catalog files under ``scripts/catalogs/`` were originally batch *input*</span> <span class="sd"> manifests: ``download catalog`` read them and nothing ever wrote them. That</span> <span class="sd"> left the three ad-hoc paths -- ``download book``, ``download fetch-genre``</span> <span class="sd"> and ``ia download`` -- adding books to the corpus that no catalog recorded,</span> <span class="sd"> permanently and silently. The drift is invisible to ``gutenkg audit``, which</span> <span class="sd"> only checks the catalog-to-corpus direction, and it means a fresh clone</span> <span class="sd"> replayed through ``download catalog`` reproduces a strict subset of the</span> <span class="sd"> corpus. Calling this on every successful download closes that loop.</span> <span class="sd"> ``title`` is written as the override column and must be the book's directory</span> <span class="sd"> name, since :func:`gutenberg_kg.audit.audit_corpus` reports a catalog title</span> <span class="sd"> that differs from the directory as an error.</span> <span class="sd"> Appending is idempotent on the ID, so re-running a download repairs a</span> <span class="sd"> missing catalog line rather than duplicating an existing one.</span> <span class="sd"> :param genre: Genre whose catalog to write; ``None`` means the book was</span> <span class="sd"> downloaded to the corpus root, which no catalog covers, so nothing is</span> <span class="sd"> written.</span> <span class="sd"> :param ebook_id: Project Gutenberg numeric book ID.</span> <span class="sd"> :param title: The book's directory name, written as the title override.</span> <span class="sd"> :param dry_run: When true, report what would be written but write nothing.</span> <span class="sd"> :returns: True when a line was appended, or would have been under</span> <span class="sd"> ``dry_run`` -- so a caller can count pending repairs without writing.</span> <span class="sd"> """</span> <span class="k">if</span> <span class="n">genre</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="k">return</span> <span class="kc">False</span> <span class="n">catalog_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">CATALOG_ROOT</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">genre</span><span class="si">}</span><span class="s2">.txt"</span><span class="p">)</span> <span class="n">needs_newline</span> <span class="o">=</span> <span class="kc">False</span> <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">catalog_path</span><span class="p">):</span> <span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">eid</span> <span class="o">==</span> <span class="n">ebook_id</span> <span class="k">for</span> <span class="n">eid</span><span class="p">,</span> <span class="n">_</span> <span class="ow">in</span> <span class="n">parse_catalog</span><span class="p">(</span><span class="n">catalog_path</span><span class="p">)):</span> <span class="k">return</span> <span class="kc">False</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">catalog_path</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">"utf-8"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span> <span class="n">needs_newline</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> <span class="k">if</span> <span class="n">dry_run</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" [dry] would catalogue: scripts/catalogs/</span><span class="si">{</span><span class="n">genre</span><span class="si">}</span><span class="s2">.txt <- </span><span class="si">{</span><span class="n">ebook_id</span><span class="si">}</span><span class="se">\t</span><span class="si">{</span><span class="n">title</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">return</span> <span class="kc">True</span> <span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">CATALOG_ROOT</span><span class="p">,</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">catalog_path</span><span class="p">,</span> <span class="s2">"a"</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">"utf-8"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span> <span class="k">if</span> <span class="n">needs_newline</span><span class="p">:</span> <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">ebook_id</span><span class="si">}</span><span class="se">\t</span><span class="si">{</span><span class="n">title</span><span class="si">}</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" Catalogued: scripts/catalogs/</span><span class="si">{</span><span class="n">genre</span><span class="si">}</span><span class="s2">.txt <- </span><span class="si">{</span><span class="n">ebook_id</span><span class="si">}</span><span class="se">\t</span><span class="si">{</span><span class="n">title</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">return</span> <span class="kc">True</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.run_catalog" class="doc doc-heading"> <code class="highlight language-python"><span class="n">run_catalog</span><span class="p">(</span><span class="n">catalog_file</span><span class="p">,</span> <span class="n">genre</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">force</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">dry_run</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.run_catalog" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Download all books from a catalog file.</p> <p><span class="doc-section-title">Parameters:</span></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr class="doc-section-item"> <td> <code>catalog_file</code> </td> <td> <code><span title="str">str</span></code> </td> <td> <div class="doc-md-description"> <p>Path to a tab-separated catalog file (ebook_id [title]).</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>genre</code> </td> <td> <code><span title="str">str</span> | None</code> </td> <td> <div class="doc-md-description"> <p>Genre subdirectory for all books.</p> </div> </td> <td> <code>None</code> </td> </tr> <tr class="doc-section-item"> <td> <code>force</code> </td> <td> <code><span title="bool">bool</span></code> </td> <td> <div class="doc-md-description"> <p>Re-download even if already present.</p> </div> </td> <td> <code>False</code> </td> </tr> <tr class="doc-section-item"> <td> <code>dry_run</code> </td> <td> <code><span title="bool">bool</span></code> </td> <td> <div class="doc-md-description"> <p>Print actions without writing files.</p> </div> </td> <td> <code>False</code> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"> 992</span> <span class="normal"> 993</span> <span class="normal"> 994</span> <span class="normal"> 995</span> <span class="normal"> 996</span> <span class="normal"> 997</span> <span class="normal"> 998</span> <span class="normal"> 999</span> <span class="normal">1000</span> <span class="normal">1001</span> <span class="normal">1002</span> <span class="normal">1003</span> <span class="normal">1004</span> <span class="normal">1005</span> <span class="normal">1006</span> <span class="normal">1007</span> <span class="normal">1008</span> <span class="normal">1009</span> <span class="normal">1010</span> <span class="normal">1011</span> <span class="normal">1012</span> <span class="normal">1013</span> <span class="normal">1014</span> <span class="normal">1015</span> <span class="normal">1016</span> <span class="normal">1017</span> <span class="normal">1018</span> <span class="normal">1019</span> <span class="normal">1020</span> <span class="normal">1021</span> <span class="normal">1022</span> <span class="normal">1023</span> <span class="normal">1024</span> <span class="normal">1025</span> <span class="normal">1026</span> <span class="normal">1027</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">run_catalog</span><span class="p">(</span> <span class="n">catalog_file</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">genre</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">force</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span> <span class="n">dry_run</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span> <span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Download all books from a catalog file.</span> <span class="sd"> :param catalog_file: Path to a tab-separated catalog file (ebook_id [title]).</span> <span class="sd"> :param genre: Genre subdirectory for all books.</span> <span class="sd"> :param force: Re-download even if already present.</span> <span class="sd"> :param dry_run: Print actions without writing files.</span> <span class="sd"> """</span> <span class="k">if</span> <span class="n">genre</span> <span class="ow">and</span> <span class="n">genre</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">ALL_GENRES</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"ERROR: unknown genre </span><span class="si">{</span><span class="n">genre</span><span class="si">!r}</span><span class="s2">."</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span> <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">entries</span> <span class="o">=</span> <span class="n">parse_catalog</span><span class="p">(</span><span class="n">catalog_file</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">entries</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="s2">"No valid entries found in catalog file."</span><span class="p">)</span> <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="s1">'[dry] '</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">dry_run</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="s1">''</span><span class="si">}</span><span class="s2">Catalog loaded: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">entries</span><span class="p">)</span><span class="si">}</span><span class="s2"> book(s)</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> <span class="n">succeeded</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">for</span> <span class="n">ebook_id</span><span class="p">,</span> <span class="n">title</span> <span class="ow">in</span> <span class="n">entries</span><span class="p">:</span> <span class="k">try</span><span class="p">:</span> <span class="n">download_book</span><span class="p">(</span><span class="n">ebook_id</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="n">title</span><span class="p">,</span> <span class="n">genre</span><span class="o">=</span><span class="n">genre</span><span class="p">,</span> <span class="n">force</span><span class="o">=</span><span class="n">force</span><span class="p">,</span> <span class="n">dry_run</span><span class="o">=</span><span class="n">dry_run</span><span class="p">)</span> <span class="n">succeeded</span> <span class="o">+=</span> <span class="mi">1</span> <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">exc</span><span class="p">:</span> <span class="c1"># noqa: BLE001</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" ERROR downloading #</span><span class="si">{</span><span class="n">ebook_id</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">exc</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span> <span class="nb">print</span><span class="p">()</span> <span class="k">if</span> <span class="n">dry_run</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"[dry] </span><span class="si">{</span><span class="n">succeeded</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">entries</span><span class="p">)</span><span class="si">}</span><span class="s2"> book(s) would be downloaded."</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Done. </span><span class="si">{</span><span class="n">succeeded</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">entries</span><span class="p">)</span><span class="si">}</span><span class="s2"> book(s) downloaded."</span><span class="p">)</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.run_catalog_sync" class="doc doc-heading"> <code class="highlight language-python"><span class="n">run_catalog_sync</span><span class="p">(</span><span class="n">genres</span><span class="p">,</span> <span class="n">dry_run</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.run_catalog_sync" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Record every downloaded book in its genre catalog.</p> <p>The repair counterpart to :func:<code>record_in_catalog</code>, for books downloaded before write-back existed. It is the analogue of <code>gutenkg re-register</code>: both rebuild a derived record from what is already on disk, without redoing the expensive work that produced it.</p> <p>No downloading is involved. Each book's <code>reference.md</code> -- written by every download path, Gutenberg and IA alike -- carries the Gutenberg ID, so the catalog can be reconstructed by reading the corpus. The directory name is written as the title override, matching what <code>audit</code> compares against.</p> <p>IA genres are skipped: they have no catalog files, and what an IA catalog should contain is a separate design question.</p> <p><span class="doc-section-title">Parameters:</span></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr class="doc-section-item"> <td> <code>genres</code> </td> <td> <code><span title="list">list</span>[<span title="str">str</span>]</code> </td> <td> <div class="doc-md-description"> <p>Genres to process.</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>dry_run</code> </td> <td> <code><span title="bool">bool</span></code> </td> <td> <div class="doc-md-description"> <p>Report what would be written without writing.</p> </div> </td> <td> <code>False</code> </td> </tr> </tbody> </table> <p><span class="doc-section-title">Returns:</span></p> <table> <thead> <tr> <th>Type</th> <th>Description</th> </tr> </thead> <tbody> <tr class="doc-section-item"> <td> <code><span title="int">int</span></code> </td> <td> <div class="doc-md-description"> <p>Number of catalog entries added (or that would be added).</p> </div> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">799</span> <span class="normal">800</span> <span class="normal">801</span> <span class="normal">802</span> <span class="normal">803</span> <span class="normal">804</span> <span class="normal">805</span> <span class="normal">806</span> <span class="normal">807</span> <span class="normal">808</span> <span class="normal">809</span> <span class="normal">810</span> <span class="normal">811</span> <span class="normal">812</span> <span class="normal">813</span> <span class="normal">814</span> <span class="normal">815</span> <span class="normal">816</span> <span class="normal">817</span> <span class="normal">818</span> <span class="normal">819</span> <span class="normal">820</span> <span class="normal">821</span> <span class="normal">822</span> <span class="normal">823</span> <span class="normal">824</span> <span class="normal">825</span> <span class="normal">826</span> <span class="normal">827</span> <span class="normal">828</span> <span class="normal">829</span> <span class="normal">830</span> <span class="normal">831</span> <span class="normal">832</span> <span class="normal">833</span> <span class="normal">834</span> <span class="normal">835</span> <span class="normal">836</span> <span class="normal">837</span> <span class="normal">838</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">run_catalog_sync</span><span class="p">(</span><span class="n">genres</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span> <span class="n">dry_run</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Record every downloaded book in its genre catalog.</span> <span class="sd"> The repair counterpart to :func:`record_in_catalog`, for books downloaded</span> <span class="sd"> before write-back existed. It is the analogue of ``gutenkg re-register``:</span> <span class="sd"> both rebuild a derived record from what is already on disk, without redoing</span> <span class="sd"> the expensive work that produced it.</span> <span class="sd"> No downloading is involved. Each book's ``reference.md`` -- written by every</span> <span class="sd"> download path, Gutenberg and IA alike -- carries the Gutenberg ID, so the</span> <span class="sd"> catalog can be reconstructed by reading the corpus. The directory name is</span> <span class="sd"> written as the title override, matching what ``audit`` compares against.</span> <span class="sd"> IA genres are skipped: they have no catalog files, and what an IA catalog</span> <span class="sd"> should contain is a separate design question.</span> <span class="sd"> :param genres: Genres to process.</span> <span class="sd"> :param dry_run: Report what would be written without writing.</span> <span class="sd"> :returns: Number of catalog entries added (or that would be added).</span> <span class="sd"> """</span> <span class="kn">from</span><span class="w"> </span><span class="nn">gutenberg_kg.genres</span><span class="w"> </span><span class="kn">import</span> <span class="n">IA_GENRES</span> <span class="n">added</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">for</span> <span class="n">genre</span> <span class="ow">in</span> <span class="n">genres</span><span class="p">:</span> <span class="k">if</span> <span class="n">genre</span> <span class="ow">in</span> <span class="n">IA_GENRES</span><span class="p">:</span> <span class="k">continue</span> <span class="n">genre_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">CORPUS_ROOT</span><span class="p">,</span> <span class="n">genre</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isdir</span><span class="p">(</span><span class="n">genre_dir</span><span class="p">):</span> <span class="k">continue</span> <span class="k">for</span> <span class="n">book</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">genre_dir</span><span class="p">)):</span> <span class="n">book_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">genre_dir</span><span class="p">,</span> <span class="n">book</span><span class="p">)</span> <span class="n">ref</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">book_dir</span><span class="p">,</span> <span class="s2">"reference.md"</span><span class="p">)</span> <span class="k">if</span> <span class="n">book</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"."</span><span class="p">)</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">ref</span><span class="p">):</span> <span class="k">continue</span> <span class="n">ebook_id</span> <span class="o">=</span> <span class="n">parse_reference</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="n">ref</span><span class="p">))</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"ebook_id"</span><span class="p">)</span> <span class="k">if</span> <span class="n">ebook_id</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="k">continue</span> <span class="k">if</span> <span class="n">record_in_catalog</span><span class="p">(</span><span class="n">genre</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="n">ebook_id</span><span class="p">),</span> <span class="n">book</span><span class="p">,</span> <span class="n">dry_run</span><span class="o">=</span><span class="n">dry_run</span><span class="p">):</span> <span class="n">added</span> <span class="o">+=</span> <span class="mi">1</span> <span class="k">return</span> <span class="n">added</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.run_fetch_genre" class="doc doc-heading"> <code class="highlight language-python"><span class="n">run_fetch_genre</span><span class="p">(</span><span class="n">genre</span><span class="p">,</span> <span class="n">query</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">max_results</span><span class="o">=</span><span class="mi">25</span><span class="p">,</span> <span class="n">yes</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">force</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">dry_run</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.run_fetch_genre" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Search, confirm, and download an entire genre in one step.</p> <p><span class="doc-section-title">Parameters:</span></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr class="doc-section-item"> <td> <code>genre</code> </td> <td> <code><span title="str">str</span></code> </td> <td> <div class="doc-md-description"> <p>Genre directory name (e.g. <code>science-fiction</code>).</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>query</code> </td> <td> <code><span title="str">str</span> | None</code> </td> <td> <div class="doc-md-description"> <p>Optional additional keyword refinement.</p> </div> </td> <td> <code>None</code> </td> </tr> <tr class="doc-section-item"> <td> <code>max_results</code> </td> <td> <code><span title="int">int</span></code> </td> <td> <div class="doc-md-description"> <p>Maximum books to search for.</p> </div> </td> <td> <code>25</code> </td> </tr> <tr class="doc-section-item"> <td> <code>yes</code> </td> <td> <code><span title="bool">bool</span></code> </td> <td> <div class="doc-md-description"> <p>Skip interactive confirmation.</p> </div> </td> <td> <code>False</code> </td> </tr> <tr class="doc-section-item"> <td> <code>force</code> </td> <td> <code><span title="bool">bool</span></code> </td> <td> <div class="doc-md-description"> <p>Re-download even if already present.</p> </div> </td> <td> <code>False</code> </td> </tr> <tr class="doc-section-item"> <td> <code>dry_run</code> </td> <td> <code><span title="bool">bool</span></code> </td> <td> <div class="doc-md-description"> <p>Print actions without writing files.</p> </div> </td> <td> <code>False</code> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">1030</span> <span class="normal">1031</span> <span class="normal">1032</span> <span class="normal">1033</span> <span class="normal">1034</span> <span class="normal">1035</span> <span class="normal">1036</span> <span class="normal">1037</span> <span class="normal">1038</span> <span class="normal">1039</span> <span class="normal">1040</span> <span class="normal">1041</span> <span class="normal">1042</span> <span class="normal">1043</span> <span class="normal">1044</span> <span class="normal">1045</span> <span class="normal">1046</span> <span class="normal">1047</span> <span class="normal">1048</span> <span class="normal">1049</span> <span class="normal">1050</span> <span class="normal">1051</span> <span class="normal">1052</span> <span class="normal">1053</span> <span class="normal">1054</span> <span class="normal">1055</span> <span class="normal">1056</span> <span class="normal">1057</span> <span class="normal">1058</span> <span class="normal">1059</span> <span class="normal">1060</span> <span class="normal">1061</span> <span class="normal">1062</span> <span class="normal">1063</span> <span class="normal">1064</span> <span class="normal">1065</span> <span class="normal">1066</span> <span class="normal">1067</span> <span class="normal">1068</span> <span class="normal">1069</span> <span class="normal">1070</span> <span class="normal">1071</span> <span class="normal">1072</span> <span class="normal">1073</span> <span class="normal">1074</span> <span class="normal">1075</span> <span class="normal">1076</span> <span class="normal">1077</span> <span class="normal">1078</span> <span class="normal">1079</span> <span class="normal">1080</span> <span class="normal">1081</span> <span class="normal">1082</span> <span class="normal">1083</span> <span class="normal">1084</span> <span class="normal">1085</span> <span class="normal">1086</span> <span class="normal">1087</span> <span class="normal">1088</span> <span class="normal">1089</span> <span class="normal">1090</span> <span class="normal">1091</span> <span class="normal">1092</span> <span class="normal">1093</span> <span class="normal">1094</span> <span class="normal">1095</span> <span class="normal">1096</span> <span class="normal">1097</span> <span class="normal">1098</span> <span class="normal">1099</span> <span class="normal">1100</span> <span class="normal">1101</span> <span class="normal">1102</span> <span class="normal">1103</span> <span class="normal">1104</span> <span class="normal">1105</span> <span class="normal">1106</span> <span class="normal">1107</span> <span class="normal">1108</span> <span class="normal">1109</span> <span class="normal">1110</span> <span class="normal">1111</span> <span class="normal">1112</span> <span class="normal">1113</span> <span class="normal">1114</span> <span class="normal">1115</span> <span class="normal">1116</span> <span class="normal">1117</span> <span class="normal">1118</span> <span class="normal">1119</span> <span class="normal">1120</span> <span class="normal">1121</span> <span class="normal">1122</span> <span class="normal">1123</span> <span class="normal">1124</span> <span class="normal">1125</span> <span class="normal">1126</span> <span class="normal">1127</span> <span class="normal">1128</span> <span class="normal">1129</span> <span class="normal">1130</span> <span class="normal">1131</span> <span class="normal">1132</span> <span class="normal">1133</span> <span class="normal">1134</span> <span class="normal">1135</span> <span class="normal">1136</span> <span class="normal">1137</span> <span class="normal">1138</span> <span class="normal">1139</span> <span class="normal">1140</span> <span class="normal">1141</span> <span class="normal">1142</span> <span class="normal">1143</span> <span class="normal">1144</span> <span class="normal">1145</span> <span class="normal">1146</span> <span class="normal">1147</span> <span class="normal">1148</span> <span class="normal">1149</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">run_fetch_genre</span><span class="p">(</span> <span class="n">genre</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">query</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">max_results</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">25</span><span class="p">,</span> <span class="n">yes</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span> <span class="n">force</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span> <span class="n">dry_run</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span> <span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Search, confirm, and download an entire genre in one step.</span> <span class="sd"> :param genre: Genre directory name (e.g. ``science-fiction``).</span> <span class="sd"> :param query: Optional additional keyword refinement.</span> <span class="sd"> :param max_results: Maximum books to search for.</span> <span class="sd"> :param yes: Skip interactive confirmation.</span> <span class="sd"> :param force: Re-download even if already present.</span> <span class="sd"> :param dry_run: Print actions without writing files.</span> <span class="sd"> """</span> <span class="kn">import</span><span class="w"> </span><span class="nn">datetime</span> <span class="n">q</span> <span class="o">=</span> <span class="n">query</span> <span class="ow">or</span> <span class="n">genre</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"-"</span><span class="p">,</span> <span class="s2">" "</span><span class="p">)</span> <span class="n">genre_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">CORPUS_ROOT</span><span class="p">,</span> <span class="n">genre</span><span class="p">)</span> <span class="n">already_present</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span> <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isdir</span><span class="p">(</span><span class="n">genre_dir</span><span class="p">):</span> <span class="n">already_present</span> <span class="o">=</span> <span class="p">{</span> <span class="n">e</span><span class="o">.</span><span class="n">name</span> <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">scandir</span><span class="p">(</span><span class="n">genre_dir</span><span class="p">)</span> <span class="k">if</span> <span class="n">e</span><span class="o">.</span><span class="n">is_dir</span><span class="p">()</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">e</span><span class="o">.</span><span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"."</span><span class="p">)</span> <span class="p">}</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Searching Gutenberg for: </span><span class="si">{</span><span class="n">q</span><span class="si">!r}</span><span class="s2"> (max </span><span class="si">{</span><span class="n">max_results</span><span class="si">}</span><span class="s2"> results)</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> <span class="n">results</span> <span class="o">=</span> <span class="n">search_gutenberg</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">max_results</span><span class="o">=</span><span class="n">max_results</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">results</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="s2">"No results found."</span><span class="p">)</span> <span class="k">return</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="s1">''</span><span class="si">:</span><span class="s2">>2</span><span class="si">}{</span><span class="s1">'ID'</span><span class="si">:</span><span class="s2">>6</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">'Title'</span><span class="si">:</span><span class="s2"><50</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">'Author'</span><span class="si">:</span><span class="s2"><30</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">'Status'</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="s2">"-"</span> <span class="o">*</span> <span class="mi">100</span><span class="p">)</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span> <span class="n">title_s</span> <span class="o">=</span> <span class="n">r</span><span class="p">[</span><span class="s2">"title"</span><span class="p">][:</span><span class="mi">48</span><span class="p">]</span> <span class="n">author_s</span> <span class="o">=</span> <span class="p">(</span><span class="n">r</span><span class="p">[</span><span class="s2">"author"</span><span class="p">]</span> <span class="ow">or</span> <span class="s2">"—"</span><span class="p">)[:</span><span class="mi">28</span><span class="p">]</span> <span class="n">status</span> <span class="o">=</span> <span class="s2">"[already downloaded]"</span> <span class="k">if</span> <span class="n">r</span><span class="p">[</span><span class="s2">"title"</span><span class="p">]</span> <span class="ow">in</span> <span class="n">already_present</span> <span class="k">else</span> <span class="s2">""</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" </span><span class="si">{</span><span class="n">r</span><span class="p">[</span><span class="s1">'ebook_id'</span><span class="p">]</span><span class="si">:</span><span class="s2">>6</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">title_s</span><span class="si">:</span><span class="s2"><50</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">author_s</span><span class="si">:</span><span class="s2"><30</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="nb">print</span><span class="p">()</span> <span class="n">to_download</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span> <span class="k">if</span> <span class="n">r</span><span class="p">[</span><span class="s2">"title"</span><span class="p">]</span> <span class="ow">in</span> <span class="n">already_present</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">force</span><span class="p">:</span> <span class="k">continue</span> <span class="k">if</span> <span class="n">yes</span> <span class="ow">or</span> <span class="n">dry_run</span><span class="p">:</span> <span class="n">to_download</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">r</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="k">try</span><span class="p">:</span> <span class="n">ans</span> <span class="o">=</span> <span class="p">(</span> <span class="nb">input</span><span class="p">(</span><span class="sa">f</span><span class="s2">" Download </span><span class="si">{</span><span class="n">r</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span><span class="si">!r}</span><span class="s2"> (#</span><span class="si">{</span><span class="n">r</span><span class="p">[</span><span class="s1">'ebook_id'</span><span class="p">]</span><span class="si">}</span><span class="s2">)? [Y/n/q] "</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="p">)</span> <span class="k">except</span> <span class="p">(</span><span class="ne">EOFError</span><span class="p">,</span> <span class="ne">KeyboardInterrupt</span><span class="p">):</span> <span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2"> Aborted."</span><span class="p">)</span> <span class="k">break</span> <span class="k">if</span> <span class="n">ans</span> <span class="o">==</span> <span class="s2">"q"</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="s2">" Aborted."</span><span class="p">)</span> <span class="k">break</span> <span class="k">if</span> <span class="n">ans</span> <span class="ow">in</span> <span class="p">(</span><span class="s2">""</span><span class="p">,</span> <span class="s2">"y"</span><span class="p">):</span> <span class="n">to_download</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">r</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">to_download</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="s2">"Nothing to download."</span><span class="p">)</span> <span class="k">return</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="se">\n</span><span class="si">{</span><span class="s1">'[dry] '</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">dry_run</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="s1">''</span><span class="si">}</span><span class="s2">Downloading </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">to_download</span><span class="p">)</span><span class="si">}</span><span class="s2"> book(s) into </span><span class="si">{</span><span class="n">genre</span><span class="si">}</span><span class="s2">/</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> <span class="n">report_rows</span> <span class="o">=</span> <span class="p">[]</span> <span class="n">downloaded</span> <span class="o">=</span> <span class="mi">0</span> <span class="n">failed</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">to_download</span><span class="p">:</span> <span class="n">ebook_id</span> <span class="o">=</span> <span class="n">r</span><span class="p">[</span><span class="s2">"ebook_id"</span><span class="p">]</span> <span class="k">try</span><span class="p">:</span> <span class="n">download_book</span><span class="p">(</span><span class="n">ebook_id</span><span class="p">,</span> <span class="n">genre</span><span class="o">=</span><span class="n">genre</span><span class="p">,</span> <span class="n">force</span><span class="o">=</span><span class="n">force</span><span class="p">,</span> <span class="n">dry_run</span><span class="o">=</span><span class="n">dry_run</span><span class="p">)</span> <span class="n">status</span> <span class="o">=</span> <span class="s2">"dry-run"</span> <span class="k">if</span> <span class="n">dry_run</span> <span class="k">else</span> <span class="s2">"downloaded"</span> <span class="n">downloaded</span> <span class="o">+=</span> <span class="mi">1</span> <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">exc</span><span class="p">:</span> <span class="c1"># noqa: BLE001</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" ERROR #</span><span class="si">{</span><span class="n">ebook_id</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">exc</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span> <span class="n">status</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"failed: </span><span class="si">{</span><span class="n">exc</span><span class="si">}</span><span class="s2">"</span> <span class="n">failed</span> <span class="o">+=</span> <span class="mi">1</span> <span class="n">report_rows</span><span class="o">.</span><span class="n">append</span><span class="p">(</span> <span class="p">{</span><span class="s2">"id"</span><span class="p">:</span> <span class="n">ebook_id</span><span class="p">,</span> <span class="s2">"title"</span><span class="p">:</span> <span class="n">r</span><span class="p">[</span><span class="s2">"title"</span><span class="p">],</span> <span class="s2">"author"</span><span class="p">:</span> <span class="n">r</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"author"</span><span class="p">,</span> <span class="s2">""</span><span class="p">),</span> <span class="s2">"status"</span><span class="p">:</span> <span class="n">status</span><span class="p">}</span> <span class="p">)</span> <span class="nb">print</span><span class="p">()</span> <span class="n">reports_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">REPO_ROOT</span><span class="p">,</span> <span class="s2">"reports"</span><span class="p">)</span> <span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">reports_dir</span><span class="p">,</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> <span class="n">ts</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">(</span><span class="n">datetime</span><span class="o">.</span><span class="n">UTC</span><span class="p">)</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">"%Y-%m-</span><span class="si">%d</span><span class="s2">_%H%M%S"</span><span class="p">)</span> <span class="n">report_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">reports_dir</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"fetch_genre_</span><span class="si">{</span><span class="n">genre</span><span class="si">}</span><span class="s2">_</span><span class="si">{</span><span class="n">ts</span><span class="si">}</span><span class="s2">.md"</span><span class="p">)</span> <span class="n">lines</span> <span class="o">=</span> <span class="p">[</span> <span class="sa">f</span><span class="s2">"# Fetch-Genre Report: </span><span class="si">{</span><span class="n">genre</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"**Date:** </span><span class="si">{</span><span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">(</span><span class="n">datetime</span><span class="o">.</span><span class="n">UTC</span><span class="p">)</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s1">'%Y-%m-</span><span class="si">%d</span><span class="s1"> %H:%M:%S UTC'</span><span class="p">)</span><span class="si">}</span><span class="s2"> "</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"**Query:** `</span><span class="si">{</span><span class="n">q</span><span class="si">}</span><span class="s2">` "</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"**Genre:** `</span><span class="si">{</span><span class="n">genre</span><span class="si">}</span><span class="s2">` "</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"**Dry run:** </span><span class="si">{</span><span class="s1">'yes'</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">dry_run</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="s1">'no'</span><span class="si">}</span><span class="s2"> "</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"**Downloaded:** </span><span class="si">{</span><span class="n">downloaded</span><span class="si">}</span><span class="s2"> "</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"**Failed:** </span><span class="si">{</span><span class="n">failed</span><span class="si">}</span><span class="s2"> "</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="s2">"## Results"</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="s2">"| ID | Title | Author | Status |"</span><span class="p">,</span> <span class="s2">"|----|-------|--------|--------|"</span><span class="p">,</span> <span class="p">]</span> <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">report_rows</span><span class="p">:</span> <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"| </span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="s1">'id'</span><span class="p">]</span><span class="si">}</span><span class="s2"> | </span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span><span class="si">}</span><span class="s2"> | </span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="s1">'author'</span><span class="p">]</span><span class="si">}</span><span class="s2"> | </span><span class="si">{</span><span class="n">row</span><span class="p">[</span><span class="s1">'status'</span><span class="p">]</span><span class="si">}</span><span class="s2"> |"</span><span class="p">)</span> <span class="n">lines</span> <span class="o">+=</span> <span class="p">[</span><span class="s2">""</span><span class="p">,</span> <span class="s2">"## Already Present (skipped)"</span><span class="p">,</span> <span class="s2">""</span><span class="p">]</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">already_present</span><span class="p">):</span> <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"- </span><span class="si">{</span><span class="n">t</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">already_present</span><span class="p">:</span> <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">"_(none)_"</span><span class="p">)</span> <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">report_path</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">"utf-8"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span> <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">lines</span><span class="p">))</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="s1">'[dry] '</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">dry_run</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="s1">''</span><span class="si">}</span><span class="s2">Done. </span><span class="si">{</span><span class="n">downloaded</span><span class="si">}</span><span class="s2"> downloaded, </span><span class="si">{</span><span class="n">failed</span><span class="si">}</span><span class="s2"> failed."</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Report saved: </span><span class="si">{</span><span class="n">report_path</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.run_search" class="doc doc-heading"> <code class="highlight language-python"><span class="n">run_search</span><span class="p">(</span><span class="n">query</span><span class="o">=</span><span class="s1">''</span><span class="p">,</span> <span class="n">author</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">subject</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">language</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">max_results</span><span class="o">=</span><span class="mi">25</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.run_search" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Search Gutenberg and print results.</p> <p><span class="doc-section-title">Parameters:</span></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr class="doc-section-item"> <td> <code>query</code> </td> <td> <code><span title="str">str</span></code> </td> <td> <div class="doc-md-description"> <p>General keyword query.</p> </div> </td> <td> <code>''</code> </td> </tr> <tr class="doc-section-item"> <td> <code>author</code> </td> <td> <code><span title="str">str</span> | None</code> </td> <td> <div class="doc-md-description"> <p>Filter by author name.</p> </div> </td> <td> <code>None</code> </td> </tr> <tr class="doc-section-item"> <td> <code>title</code> </td> <td> <code><span title="str">str</span> | None</code> </td> <td> <div class="doc-md-description"> <p>Filter by title keyword.</p> </div> </td> <td> <code>None</code> </td> </tr> <tr class="doc-section-item"> <td> <code>subject</code> </td> <td> <code><span title="str">str</span> | None</code> </td> <td> <div class="doc-md-description"> <p>Filter by subject.</p> </div> </td> <td> <code>None</code> </td> </tr> <tr class="doc-section-item"> <td> <code>language</code> </td> <td> <code><span title="str">str</span> | None</code> </td> <td> <div class="doc-md-description"> <p>Filter by language code.</p> </div> </td> <td> <code>None</code> </td> </tr> <tr class="doc-section-item"> <td> <code>max_results</code> </td> <td> <code><span title="int">int</span></code> </td> <td> <div class="doc-md-description"> <p>Maximum results to display.</p> </div> </td> <td> <code>25</code> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">938</span> <span class="normal">939</span> <span class="normal">940</span> <span class="normal">941</span> <span class="normal">942</span> <span class="normal">943</span> <span class="normal">944</span> <span class="normal">945</span> <span class="normal">946</span> <span class="normal">947</span> <span class="normal">948</span> <span class="normal">949</span> <span class="normal">950</span> <span class="normal">951</span> <span class="normal">952</span> <span class="normal">953</span> <span class="normal">954</span> <span class="normal">955</span> <span class="normal">956</span> <span class="normal">957</span> <span class="normal">958</span> <span class="normal">959</span> <span class="normal">960</span> <span class="normal">961</span> <span class="normal">962</span> <span class="normal">963</span> <span class="normal">964</span> <span class="normal">965</span> <span class="normal">966</span> <span class="normal">967</span> <span class="normal">968</span> <span class="normal">969</span> <span class="normal">970</span> <span class="normal">971</span> <span class="normal">972</span> <span class="normal">973</span> <span class="normal">974</span> <span class="normal">975</span> <span class="normal">976</span> <span class="normal">977</span> <span class="normal">978</span> <span class="normal">979</span> <span class="normal">980</span> <span class="normal">981</span> <span class="normal">982</span> <span class="normal">983</span> <span class="normal">984</span> <span class="normal">985</span> <span class="normal">986</span> <span class="normal">987</span> <span class="normal">988</span> <span class="normal">989</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">run_search</span><span class="p">(</span> <span class="n">query</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">""</span><span class="p">,</span> <span class="n">author</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">title</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">subject</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">language</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">max_results</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">25</span><span class="p">,</span> <span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Search Gutenberg and print results.</span> <span class="sd"> :param query: General keyword query.</span> <span class="sd"> :param author: Filter by author name.</span> <span class="sd"> :param title: Filter by title keyword.</span> <span class="sd"> :param subject: Filter by subject.</span> <span class="sd"> :param language: Filter by language code.</span> <span class="sd"> :param max_results: Maximum results to display.</span> <span class="sd"> """</span> <span class="n">parts</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">if</span> <span class="n">author</span><span class="p">:</span> <span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">author</span><span class="p">)</span> <span class="k">if</span> <span class="n">title</span><span class="p">:</span> <span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">title</span><span class="p">)</span> <span class="k">if</span> <span class="n">subject</span><span class="p">:</span> <span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">subject</span><span class="p">)</span> <span class="k">if</span> <span class="n">language</span><span class="p">:</span> <span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"l.</span><span class="si">{</span><span class="n">language</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">if</span> <span class="n">query</span><span class="p">:</span> <span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">query</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">parts</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="s2">"Error: Provide at least one search term (--author, --title, --subject, or keyword)."</span><span class="p">)</span> <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">q</span> <span class="o">=</span> <span class="s2">" "</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">parts</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Searching Gutenberg for: </span><span class="si">{</span><span class="n">q</span><span class="si">}</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> <span class="n">results</span> <span class="o">=</span> <span class="n">search_gutenberg</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">max_results</span><span class="o">=</span><span class="n">max_results</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">results</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="s2">"No results found."</span><span class="p">)</span> <span class="k">return</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="s1">'ID'</span><span class="si">:</span><span class="s2">>6</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">'Title'</span><span class="si">:</span><span class="s2"><55</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">'Author'</span><span class="si">:</span><span class="s2"><30</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="s2">"-"</span> <span class="o">*</span> <span class="mi">95</span><span class="p">)</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span> <span class="n">title_s</span> <span class="o">=</span> <span class="n">r</span><span class="p">[</span><span class="s2">"title"</span><span class="p">][:</span><span class="mi">53</span><span class="p">]</span> <span class="n">author_s</span> <span class="o">=</span> <span class="p">(</span><span class="n">r</span><span class="p">[</span><span class="s2">"author"</span><span class="p">]</span> <span class="ow">or</span> <span class="s2">"—"</span><span class="p">)[:</span><span class="mi">28</span><span class="p">]</span> <span class="n">subjects_s</span> <span class="o">=</span> <span class="s2">", "</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"subjects"</span><span class="p">,</span> <span class="p">[])[:</span><span class="mi">3</span><span class="p">])</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">r</span><span class="p">[</span><span class="s1">'ebook_id'</span><span class="p">]</span><span class="si">:</span><span class="s2">>6</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">title_s</span><span class="si">:</span><span class="s2"><55</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">author_s</span><span class="si">:</span><span class="s2"><30</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">if</span> <span class="n">subjects_s</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="s1">''</span><span class="si">:</span><span class="s2">>8</span><span class="si">}</span><span class="s2">Subjects: </span><span class="si">{</span><span class="n">subjects_s</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="se">\n</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span><span class="si">}</span><span class="s2"> result(s). Download with: gutenkg download book <ID>"</span><span class="p">)</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.run_survey" class="doc doc-heading"> <code class="highlight language-python"><span class="n">run_survey</span><span class="p">(</span><span class="n">genre</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.run_survey" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Scan the corpus and print download/ingest status by genre.</p> <p><span class="doc-section-title">Parameters:</span></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr class="doc-section-item"> <td> <code>genre</code> </td> <td> <code><span title="str">str</span> | None</code> </td> <td> <div class="doc-md-description"> <p>Optional genre filter.</p> </div> </td> <td> <code>None</code> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">1152</span> <span class="normal">1153</span> <span class="normal">1154</span> <span class="normal">1155</span> <span class="normal">1156</span> <span class="normal">1157</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">run_survey</span><span class="p">(</span><span class="n">genre</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Scan the corpus and print download/ingest status by genre.</span> <span class="sd"> :param genre: Optional genre filter.</span> <span class="sd"> """</span> <span class="n">survey_repo</span><span class="p">(</span><span class="n">genre_filter</span><span class="o">=</span><span class="n">genre</span><span class="p">)</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.search_gutenberg" class="doc doc-heading"> <code class="highlight language-python"><span class="n">search_gutenberg</span><span class="p">(</span><span class="n">query</span><span class="p">,</span> <span class="n">max_results</span><span class="o">=</span><span class="mi">25</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.search_gutenberg" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Search Gutenberg via the OPDS feed. Returns list of book dicts.</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">288</span> <span class="normal">289</span> <span class="normal">290</span> <span class="normal">291</span> <span class="normal">292</span> <span class="normal">293</span> <span class="normal">294</span> <span class="normal">295</span> <span class="normal">296</span> <span class="normal">297</span> <span class="normal">298</span> <span class="normal">299</span> <span class="normal">300</span> <span class="normal">301</span> <span class="normal">302</span> <span class="normal">303</span> <span class="normal">304</span> <span class="normal">305</span> <span class="normal">306</span> <span class="normal">307</span> <span class="normal">308</span> <span class="normal">309</span> <span class="normal">310</span> <span class="normal">311</span> <span class="normal">312</span> <span class="normal">313</span> <span class="normal">314</span> <span class="normal">315</span> <span class="normal">316</span> <span class="normal">317</span> <span class="normal">318</span> <span class="normal">319</span> <span class="normal">320</span> <span class="normal">321</span> <span class="normal">322</span> <span class="normal">323</span> <span class="normal">324</span> <span class="normal">325</span> <span class="normal">326</span> <span class="normal">327</span> <span class="normal">328</span> <span class="normal">329</span> <span class="normal">330</span> <span class="normal">331</span> <span class="normal">332</span> <span class="normal">333</span> <span class="normal">334</span> <span class="normal">335</span> <span class="normal">336</span> <span class="normal">337</span> <span class="normal">338</span> <span class="normal">339</span> <span class="normal">340</span> <span class="normal">341</span> <span class="normal">342</span> <span class="normal">343</span> <span class="normal">344</span> <span class="normal">345</span> <span class="normal">346</span> <span class="normal">347</span> <span class="normal">348</span> <span class="normal">349</span> <span class="normal">350</span> <span class="normal">351</span> <span class="normal">352</span> <span class="normal">353</span> <span class="normal">354</span> <span class="normal">355</span> <span class="normal">356</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">search_gutenberg</span><span class="p">(</span><span class="n">query</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">max_results</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">25</span><span class="p">)</span> <span class="o">-></span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span> <span class="w"> </span><span class="sd">"""Search Gutenberg via the OPDS feed. Returns list of book dicts."""</span> <span class="n">params</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">parse</span><span class="o">.</span><span class="n">quote</span><span class="p">(</span><span class="n">query</span><span class="p">)</span> <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">GUTENBERG_SEARCH_URL</span><span class="si">}</span><span class="s2">?query=</span><span class="si">{</span><span class="n">params</span><span class="si">}</span><span class="s2">"</span> <span class="n">xml_text</span> <span class="o">=</span> <span class="n">fetch_url</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="n">root</span> <span class="o">=</span> <span class="n">ET</span><span class="o">.</span><span class="n">fromstring</span><span class="p">(</span><span class="n">xml_text</span><span class="p">)</span> <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">for</span> <span class="n">entry</span> <span class="ow">in</span> <span class="n">root</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s2">"atom:entry"</span><span class="p">,</span> <span class="n">NS</span><span class="p">):</span> <span class="n">entry_id</span> <span class="o">=</span> <span class="n">entry</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"atom:id"</span><span class="p">,</span> <span class="n">NS</span><span class="p">)</span> <span class="k">if</span> <span class="n">entry_id</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">or</span> <span class="n">entry_id</span><span class="o">.</span><span class="n">text</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="k">continue</span> <span class="c1"># Skip non-book entries (Authors, Subjects facets)</span> <span class="n">id_text</span> <span class="o">=</span> <span class="n">entry_id</span><span class="o">.</span><span class="n">text</span> <span class="k">if</span> <span class="s2">"/authors/"</span> <span class="ow">in</span> <span class="n">id_text</span> <span class="ow">or</span> <span class="s2">"/subjects/"</span> <span class="ow">in</span> <span class="n">id_text</span><span class="p">:</span> <span class="k">continue</span> <span class="c1"># Extract ebook ID from the entry id URL</span> <span class="n">m</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="sa">r</span><span class="s2">"/ebooks/(\d+)"</span><span class="p">,</span> <span class="n">id_text</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">m</span><span class="p">:</span> <span class="c1"># Try the urn format</span> <span class="n">m</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="sa">r</span><span class="s2">"gutenberg:(\d+)"</span><span class="p">,</span> <span class="n">id_text</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">m</span><span class="p">:</span> <span class="k">continue</span> <span class="n">ebook_id</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">m</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span> <span class="n">title_el</span> <span class="o">=</span> <span class="n">entry</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"atom:title"</span><span class="p">,</span> <span class="n">NS</span><span class="p">)</span> <span class="n">title</span> <span class="o">=</span> <span class="p">(</span> <span class="n">title_el</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="k">if</span> <span class="n">title_el</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">title_el</span><span class="o">.</span><span class="n">text</span> <span class="k">else</span> <span class="sa">f</span><span class="s2">"Unknown (#</span><span class="si">{</span><span class="n">ebook_id</span><span class="si">}</span><span class="s2">)"</span> <span class="p">)</span> <span class="c1"># In search results, author is in <content type="text">, not <author></span> <span class="n">author</span> <span class="o">=</span> <span class="s2">""</span> <span class="n">author_el</span> <span class="o">=</span> <span class="n">entry</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"atom:author/atom:name"</span><span class="p">,</span> <span class="n">NS</span><span class="p">)</span> <span class="k">if</span> <span class="n">author_el</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">author_el</span><span class="o">.</span><span class="n">text</span><span class="p">:</span> <span class="n">name</span> <span class="o">=</span> <span class="n">author_el</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="n">parts</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">name</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">","</span><span class="p">,</span> <span class="mi">1</span><span class="p">)]</span> <span class="n">author</span> <span class="o">=</span> <span class="s2">" "</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">reversed</span><span class="p">(</span><span class="n">parts</span><span class="p">))</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">parts</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span> <span class="k">else</span> <span class="n">name</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">author</span><span class="p">:</span> <span class="n">content_el</span> <span class="o">=</span> <span class="n">entry</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">"atom:content"</span><span class="p">,</span> <span class="n">NS</span><span class="p">)</span> <span class="k">if</span> <span class="n">content_el</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">content_el</span><span class="o">.</span><span class="n">text</span><span class="p">:</span> <span class="n">text</span> <span class="o">=</span> <span class="n">content_el</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="c1"># Filter out download count strings like "996 downloads"</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">re</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="sa">r</span><span class="s2">"^\d+\s+downloads?$"</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">IGNORECASE</span><span class="p">):</span> <span class="n">author</span> <span class="o">=</span> <span class="n">text</span> <span class="c1"># Subjects</span> <span class="n">subjects</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">for</span> <span class="n">cat</span> <span class="ow">in</span> <span class="n">entry</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s2">"atom:category"</span><span class="p">,</span> <span class="n">NS</span><span class="p">):</span> <span class="n">term</span> <span class="o">=</span> <span class="n">cat</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"term"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="k">if</span> <span class="n">term</span><span class="p">:</span> <span class="n">subjects</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">term</span><span class="p">)</span> <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span> <span class="p">{</span> <span class="s2">"ebook_id"</span><span class="p">:</span> <span class="n">ebook_id</span><span class="p">,</span> <span class="s2">"title"</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span> <span class="s2">"author"</span><span class="p">:</span> <span class="n">author</span><span class="p">,</span> <span class="s2">"subjects"</span><span class="p">:</span> <span class="n">subjects</span><span class="p">,</span> <span class="p">}</span> <span class="p">)</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span> <span class="o">>=</span> <span class="n">max_results</span><span class="p">:</span> <span class="k">break</span> <span class="k">return</span> <span class="n">results</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.slugify" class="doc doc-heading"> <code class="highlight language-python"><span class="n">slugify</span><span class="p">(</span><span class="n">title</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.slugify" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Convert a title to a filesystem-friendly slug.</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">103</span> <span class="normal">104</span> <span class="normal">105</span> <span class="normal">106</span> <span class="normal">107</span> <span class="normal">108</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">slugify</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Convert a title to a filesystem-friendly slug."""</span> <span class="n">slug</span> <span class="o">=</span> <span class="n">title</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="n">slug</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sa">r</span><span class="s2">"[^\w\s-]"</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="n">slug</span><span class="p">)</span> <span class="n">slug</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sa">r</span><span class="s2">"[\s-]+"</span><span class="p">,</span> <span class="s2">"_"</span><span class="p">,</span> <span class="n">slug</span><span class="p">)</span> <span class="k">return</span> <span class="n">slug</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.strip_boilerplate" class="doc doc-heading"> <code class="highlight language-python"><span class="n">strip_boilerplate</span><span class="p">(</span><span class="n">text</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.strip_boilerplate" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Remove Project Gutenberg header and footer boilerplate.</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">364</span> <span class="normal">365</span> <span class="normal">366</span> <span class="normal">367</span> <span class="normal">368</span> <span class="normal">369</span> <span class="normal">370</span> <span class="normal">371</span> <span class="normal">372</span> <span class="normal">373</span> <span class="normal">374</span> <span class="normal">375</span> <span class="normal">376</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">strip_boilerplate</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Remove Project Gutenberg header and footer boilerplate."""</span> <span class="n">start</span> <span class="o">=</span> <span class="n">START_MARKER</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">text</span><span class="p">)</span> <span class="n">end</span> <span class="o">=</span> <span class="n">END_MARKER</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">text</span><span class="p">)</span> <span class="k">if</span> <span class="n">start</span><span class="p">:</span> <span class="n">text</span> <span class="o">=</span> <span class="n">text</span><span class="p">[</span><span class="n">start</span><span class="o">.</span><span class="n">end</span><span class="p">()</span> <span class="p">:]</span> <span class="k">if</span> <span class="n">end</span><span class="p">:</span> <span class="n">end2</span> <span class="o">=</span> <span class="n">END_MARKER</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">text</span><span class="p">)</span> <span class="k">if</span> <span class="n">end2</span><span class="p">:</span> <span class="n">text</span> <span class="o">=</span> <span class="n">text</span><span class="p">[:</span> <span class="n">end2</span><span class="o">.</span><span class="n">start</span><span class="p">()]</span> <span class="k">return</span> <span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="o">+</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.survey_repo" class="doc doc-heading"> <code class="highlight language-python"><span class="n">survey_repo</span><span class="p">(</span><span class="n">genre_filter</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.survey_repo" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Print a survey table of downloaded books organized by genre.</p> <p><span class="doc-section-title">Parameters:</span></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr class="doc-section-item"> <td> <code>genre_filter</code> </td> <td> <code><span title="str">str</span> | None</code> </td> <td> <div class="doc-md-description"> <p>When provided, only the matching genre is shown.</p> </div> </td> <td> <code>None</code> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">883</span> <span class="normal">884</span> <span class="normal">885</span> <span class="normal">886</span> <span class="normal">887</span> <span class="normal">888</span> <span class="normal">889</span> <span class="normal">890</span> <span class="normal">891</span> <span class="normal">892</span> <span class="normal">893</span> <span class="normal">894</span> <span class="normal">895</span> <span class="normal">896</span> <span class="normal">897</span> <span class="normal">898</span> <span class="normal">899</span> <span class="normal">900</span> <span class="normal">901</span> <span class="normal">902</span> <span class="normal">903</span> <span class="normal">904</span> <span class="normal">905</span> <span class="normal">906</span> <span class="normal">907</span> <span class="normal">908</span> <span class="normal">909</span> <span class="normal">910</span> <span class="normal">911</span> <span class="normal">912</span> <span class="normal">913</span> <span class="normal">914</span> <span class="normal">915</span> <span class="normal">916</span> <span class="normal">917</span> <span class="normal">918</span> <span class="normal">919</span> <span class="normal">920</span> <span class="normal">921</span> <span class="normal">922</span> <span class="normal">923</span> <span class="normal">924</span> <span class="normal">925</span> <span class="normal">926</span> <span class="normal">927</span> <span class="normal">928</span> <span class="normal">929</span> <span class="normal">930</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">survey_repo</span><span class="p">(</span><span class="n">genre_filter</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Print a survey table of downloaded books organized by genre.</span> <span class="sd"> :param genre_filter: When provided, only the matching genre is shown.</span> <span class="sd"> """</span> <span class="n">genres_to_show</span> <span class="o">=</span> <span class="p">[</span><span class="n">genre_filter</span><span class="p">]</span> <span class="k">if</span> <span class="n">genre_filter</span> <span class="k">else</span> <span class="n">ALL_GENRES</span> <span class="n">genre_set</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">ALL_GENRES</span><span class="p">)</span> <span class="k">for</span> <span class="n">genre</span> <span class="ow">in</span> <span class="n">genres_to_show</span><span class="p">:</span> <span class="n">genre_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">CORPUS_ROOT</span><span class="p">,</span> <span class="n">genre</span><span class="p">)</span> <span class="n">books</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isdir</span><span class="p">(</span><span class="n">genre_dir</span><span class="p">):</span> <span class="k">for</span> <span class="n">entry</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">scandir</span><span class="p">(</span><span class="n">genre_dir</span><span class="p">),</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">e</span><span class="p">:</span> <span class="n">e</span><span class="o">.</span><span class="n">name</span><span class="p">):</span> <span class="k">if</span> <span class="n">entry</span><span class="o">.</span><span class="n">is_dir</span><span class="p">()</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">entry</span><span class="o">.</span><span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"."</span><span class="p">):</span> <span class="n">books</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">_survey_book_dir</span><span class="p">(</span><span class="n">entry</span><span class="o">.</span><span class="n">path</span><span class="p">,</span> <span class="n">entry</span><span class="o">.</span><span class="n">name</span><span class="p">))</span> <span class="n">count</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">books</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="se">\n</span><span class="s2">=== </span><span class="si">{</span><span class="n">genre</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">count</span><span class="si">}</span><span class="s2"> book</span><span class="si">{</span><span class="s1">'s'</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">count</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="s1">''</span><span class="si">}</span><span class="s2">) ==="</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">books</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="s2">" (none)"</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="n">books</span><span class="p">:</span> <span class="n">title_col</span> <span class="o">=</span> <span class="n">b</span><span class="p">[</span><span class="s2">"title"</span><span class="p">][:</span><span class="mi">50</span><span class="p">]</span><span class="o">.</span><span class="n">ljust</span><span class="p">(</span><span class="mi">50</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span> <span class="sa">f</span><span class="s2">" </span><span class="si">{</span><span class="n">title_col</span><span class="si">}</span><span class="s2">"</span> <span class="sa">f</span><span class="s2">" md=</span><span class="si">{</span><span class="n">_check_mark</span><span class="p">(</span><span class="n">b</span><span class="p">[</span><span class="s1">'md'</span><span class="p">])</span><span class="si">}</span><span class="s2">"</span> <span class="sa">f</span><span class="s2">" ref=</span><span class="si">{</span><span class="n">_check_mark</span><span class="p">(</span><span class="n">b</span><span class="p">[</span><span class="s1">'ref'</span><span class="p">])</span><span class="si">}</span><span class="s2">"</span> <span class="sa">f</span><span class="s2">" kg=</span><span class="si">{</span><span class="n">_check_mark</span><span class="p">(</span><span class="n">b</span><span class="p">[</span><span class="s1">'kg'</span><span class="p">])</span><span class="si">}</span><span class="s2">"</span> <span class="p">)</span> <span class="c1"># Ungrouped books (corpus-level dirs that aren't genre dirs and don't start with '.')</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">genre_filter</span><span class="p">:</span> <span class="n">ungrouped</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">for</span> <span class="n">entry</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">scandir</span><span class="p">(</span><span class="n">CORPUS_ROOT</span><span class="p">),</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">e</span><span class="p">:</span> <span class="n">e</span><span class="o">.</span><span class="n">name</span><span class="p">):</span> <span class="k">if</span> <span class="n">entry</span><span class="o">.</span><span class="n">is_dir</span><span class="p">()</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">entry</span><span class="o">.</span><span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"."</span><span class="p">)</span> <span class="ow">and</span> <span class="n">entry</span><span class="o">.</span><span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">genre_set</span><span class="p">:</span> <span class="n">ungrouped</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">_survey_book_dir</span><span class="p">(</span><span class="n">entry</span><span class="o">.</span><span class="n">path</span><span class="p">,</span> <span class="n">entry</span><span class="o">.</span><span class="n">name</span><span class="p">))</span> <span class="k">if</span> <span class="n">ungrouped</span><span class="p">:</span> <span class="n">count</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">ungrouped</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="se">\n</span><span class="s2">=== (ungrouped) (</span><span class="si">{</span><span class="n">count</span><span class="si">}</span><span class="s2"> book</span><span class="si">{</span><span class="s1">'s'</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">count</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="s1">''</span><span class="si">}</span><span class="s2">) ==="</span><span class="p">)</span> <span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="n">ungrouped</span><span class="p">:</span> <span class="n">title_col</span> <span class="o">=</span> <span class="n">b</span><span class="p">[</span><span class="s2">"title"</span><span class="p">][:</span><span class="mi">50</span><span class="p">]</span><span class="o">.</span><span class="n">ljust</span><span class="p">(</span><span class="mi">50</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span> <span class="sa">f</span><span class="s2">" </span><span class="si">{</span><span class="n">title_col</span><span class="si">}</span><span class="s2">"</span> <span class="sa">f</span><span class="s2">" md=</span><span class="si">{</span><span class="n">_check_mark</span><span class="p">(</span><span class="n">b</span><span class="p">[</span><span class="s1">'md'</span><span class="p">])</span><span class="si">}</span><span class="s2">"</span> <span class="sa">f</span><span class="s2">" ref=</span><span class="si">{</span><span class="n">_check_mark</span><span class="p">(</span><span class="n">b</span><span class="p">[</span><span class="s1">'ref'</span><span class="p">])</span><span class="si">}</span><span class="s2">"</span> <span class="sa">f</span><span class="s2">" kg=</span><span class="si">{</span><span class="n">_check_mark</span><span class="p">(</span><span class="n">b</span><span class="p">[</span><span class="s1">'kg'</span><span class="p">])</span><span class="si">}</span><span class="s2">"</span> <span class="p">)</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.text_to_markdown" class="doc doc-heading"> <code class="highlight language-python"><span class="n">text_to_markdown</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">meta</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.text_to_markdown" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Convert plain Gutenberg text to structured Markdown.</p> <p>Strategy: 1. Add YAML-style front matter from metadata 2. Detect and convert chapter/section headings to Markdown headings 3. Preserve paragraph structure 4. Skip table of contents (redundant with heading structure)</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">408</span> <span class="normal">409</span> <span class="normal">410</span> <span class="normal">411</span> <span class="normal">412</span> <span class="normal">413</span> <span class="normal">414</span> <span class="normal">415</span> <span class="normal">416</span> <span class="normal">417</span> <span class="normal">418</span> <span class="normal">419</span> <span class="normal">420</span> <span class="normal">421</span> <span class="normal">422</span> <span class="normal">423</span> <span class="normal">424</span> <span class="normal">425</span> <span class="normal">426</span> <span class="normal">427</span> <span class="normal">428</span> <span class="normal">429</span> <span class="normal">430</span> <span class="normal">431</span> <span class="normal">432</span> <span class="normal">433</span> <span class="normal">434</span> <span class="normal">435</span> <span class="normal">436</span> <span class="normal">437</span> <span class="normal">438</span> <span class="normal">439</span> <span class="normal">440</span> <span class="normal">441</span> <span class="normal">442</span> <span class="normal">443</span> <span class="normal">444</span> <span class="normal">445</span> <span class="normal">446</span> <span class="normal">447</span> <span class="normal">448</span> <span class="normal">449</span> <span class="normal">450</span> <span class="normal">451</span> <span class="normal">452</span> <span class="normal">453</span> <span class="normal">454</span> <span class="normal">455</span> <span class="normal">456</span> <span class="normal">457</span> <span class="normal">458</span> <span class="normal">459</span> <span class="normal">460</span> <span class="normal">461</span> <span class="normal">462</span> <span class="normal">463</span> <span class="normal">464</span> <span class="normal">465</span> <span class="normal">466</span> <span class="normal">467</span> <span class="normal">468</span> <span class="normal">469</span> <span class="normal">470</span> <span class="normal">471</span> <span class="normal">472</span> <span class="normal">473</span> <span class="normal">474</span> <span class="normal">475</span> <span class="normal">476</span> <span class="normal">477</span> <span class="normal">478</span> <span class="normal">479</span> <span class="normal">480</span> <span class="normal">481</span> <span class="normal">482</span> <span class="normal">483</span> <span class="normal">484</span> <span class="normal">485</span> <span class="normal">486</span> <span class="normal">487</span> <span class="normal">488</span> <span class="normal">489</span> <span class="normal">490</span> <span class="normal">491</span> <span class="normal">492</span> <span class="normal">493</span> <span class="normal">494</span> <span class="normal">495</span> <span class="normal">496</span> <span class="normal">497</span> <span class="normal">498</span> <span class="normal">499</span> <span class="normal">500</span> <span class="normal">501</span> <span class="normal">502</span> <span class="normal">503</span> <span class="normal">504</span> <span class="normal">505</span> <span class="normal">506</span> <span class="normal">507</span> <span class="normal">508</span> <span class="normal">509</span> <span class="normal">510</span> <span class="normal">511</span> <span class="normal">512</span> <span class="normal">513</span> <span class="normal">514</span> <span class="normal">515</span> <span class="normal">516</span> <span class="normal">517</span> <span class="normal">518</span> <span class="normal">519</span> <span class="normal">520</span> <span class="normal">521</span> <span class="normal">522</span> <span class="normal">523</span> <span class="normal">524</span> <span class="normal">525</span> <span class="normal">526</span> <span class="normal">527</span> <span class="normal">528</span> <span class="normal">529</span> <span class="normal">530</span> <span class="normal">531</span> <span class="normal">532</span> <span class="normal">533</span> <span class="normal">534</span> <span class="normal">535</span> <span class="normal">536</span> <span class="normal">537</span> <span class="normal">538</span> <span class="normal">539</span> <span class="normal">540</span> <span class="normal">541</span> <span class="normal">542</span> <span class="normal">543</span> <span class="normal">544</span> <span class="normal">545</span> <span class="normal">546</span> <span class="normal">547</span> <span class="normal">548</span> <span class="normal">549</span> <span class="normal">550</span> <span class="normal">551</span> <span class="normal">552</span> <span class="normal">553</span> <span class="normal">554</span> <span class="normal">555</span> <span class="normal">556</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">text_to_markdown</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">meta</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Convert plain Gutenberg text to structured Markdown.</span> <span class="sd"> Strategy:</span> <span class="sd"> 1. Add YAML-style front matter from metadata</span> <span class="sd"> 2. Detect and convert chapter/section headings to Markdown headings</span> <span class="sd"> 3. Preserve paragraph structure</span> <span class="sd"> 4. Skip table of contents (redundant with heading structure)</span> <span class="sd"> """</span> <span class="n">lines</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> <span class="n">total</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span> <span class="c1"># Skip producer credits at the very start</span> <span class="n">start_idx</span> <span class="o">=</span> <span class="n">_skip_front_matter</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="c1"># Detect and skip table of contents</span> <span class="c1"># Where the contents list ends comes from the structural profile, not</span> <span class="c1"># from counting blank lines; detect_toc anchors it to the marker line.</span> <span class="n">toc</span> <span class="o">=</span> <span class="n">_detect_toc</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="n">start_idx</span><span class="p">,</span> <span class="nb">min</span><span class="p">(</span><span class="n">start_idx</span> <span class="o">+</span> <span class="mi">200</span><span class="p">,</span> <span class="n">total</span><span class="p">),</span> <span class="n">contents_regions</span><span class="p">(</span><span class="n">lines</span><span class="p">))</span> <span class="n">toc_range</span> <span class="o">=</span> <span class="nb">range</span><span class="p">(</span><span class="n">toc</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">toc</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="k">if</span> <span class="n">toc</span> <span class="k">else</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="c1"># Title-Case work titles, honoured only where they recur (see</span> <span class="c1"># _repeated_title_lines). File-level knowledge, so it cannot live in the</span> <span class="c1"># per-line _is_heading.</span> <span class="n">title_lines</span> <span class="o">=</span> <span class="n">_repeated_title_lines</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="n">start_idx</span><span class="p">,</span> <span class="n">toc_range</span><span class="p">)</span> <span class="n">title_lines</span> <span class="o">|=</span> <span class="n">_bare_bible_title_lines</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="n">start_idx</span><span class="p">,</span> <span class="n">toc_range</span><span class="p">)</span> <span class="n">roman_lines</span> <span class="o">=</span> <span class="n">bare_roman_heading_lines</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="n">start_idx</span><span class="p">,</span> <span class="n">toc_range</span><span class="p">)</span> <span class="c1"># Build the markdown</span> <span class="n">md_lines</span> <span class="o">=</span> <span class="p">[]</span> <span class="c1"># Front matter: title as h1</span> <span class="n">title</span> <span class="o">=</span> <span class="n">meta</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"title"</span><span class="p">,</span> <span class="s2">"Untitled"</span><span class="p">)</span> <span class="n">author</span> <span class="o">=</span> <span class="n">meta</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"author"</span><span class="p">,</span> <span class="s2">"Unknown"</span><span class="p">)</span> <span class="n">md_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"# </span><span class="si">{</span><span class="n">title</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="n">md_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span> <span class="n">md_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"**</span><span class="si">{</span><span class="n">author</span><span class="si">}</span><span class="s2">**"</span><span class="p">)</span> <span class="n">md_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span> <span class="n">md_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">"---"</span><span class="p">)</span> <span class="n">md_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span> <span class="n">prev_blank</span> <span class="o">=</span> <span class="kc">True</span> <span class="n">prev_line</span> <span class="o">=</span> <span class="s2">""</span> <span class="n">i</span> <span class="o">=</span> <span class="n">start_idx</span> <span class="k">while</span> <span class="n">i</span> <span class="o"><</span> <span class="n">total</span><span class="p">:</span> <span class="c1"># Skip TOC region</span> <span class="k">if</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">toc_range</span><span class="p">:</span> <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span> <span class="k">continue</span> <span class="n">line</span> <span class="o">=</span> <span class="n">lines</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="n">stripped</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="c1"># Blank line</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">stripped</span><span class="p">:</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">prev_blank</span><span class="p">:</span> <span class="n">md_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span> <span class="n">prev_blank</span> <span class="o">=</span> <span class="kc">True</span> <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span> <span class="k">continue</span> <span class="c1"># Strip illustration markers</span> <span class="n">stripped</span> <span class="o">=</span> <span class="n">ILLUSTRATION_RE</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="s2">""</span><span class="p">,</span> <span class="n">stripped</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">stripped</span><span class="p">:</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">prev_blank</span><span class="p">:</span> <span class="n">md_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span> <span class="n">prev_blank</span> <span class="o">=</span> <span class="kc">True</span> <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span> <span class="k">continue</span> <span class="c1"># Check for heading</span> <span class="n">heading</span> <span class="o">=</span> <span class="n">_is_heading</span><span class="p">(</span><span class="n">stripped</span><span class="p">)</span> <span class="k">if</span> <span class="n">heading</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">title_lines</span><span class="p">:</span> <span class="n">heading</span> <span class="o">=</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="n">stripped</span><span class="p">)</span> <span class="c1"># Level 3, the level ALL-CAPS already gives III and XII in the same books.</span> <span class="k">if</span> <span class="n">heading</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">roman_lines</span><span class="p">:</span> <span class="n">heading</span> <span class="o">=</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="n">stripped</span><span class="p">)</span> <span class="k">if</span> <span class="n">heading</span> <span class="ow">and</span> <span class="p">(</span><span class="n">prev_blank</span> <span class="ow">or</span> <span class="n">_breaks_before_heading</span><span class="p">(</span><span class="n">prev_line</span><span class="p">)):</span> <span class="n">level</span><span class="p">,</span> <span class="n">heading_text</span> <span class="o">=</span> <span class="n">heading</span> <span class="c1"># Look ahead for subtitle / multi-line chapter title:</span> <span class="c1"># "CHAPTER I." followed by "HOW MANY KINDS OF..." on next line(s)</span> <span class="n">subtitle_lines</span> <span class="o">=</span> <span class="p">[]</span> <span class="n">j</span> <span class="o">=</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span> <span class="c1"># Skip blank lines between heading and potential subtitle</span> <span class="k">while</span> <span class="n">j</span> <span class="o"><</span> <span class="n">total</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">lines</span><span class="p">[</span><span class="n">j</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span> <span class="n">j</span> <span class="o">+=</span> <span class="mi">1</span> <span class="k">if</span> <span class="n">j</span> <span class="o"><</span> <span class="n">total</span><span class="p">:</span> <span class="n">next_line</span> <span class="o">=</span> <span class="n">lines</span><span class="p">[</span><span class="n">j</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="n">next_line_clean</span> <span class="o">=</span> <span class="n">ILLUSTRATION_RE</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="s2">""</span><span class="p">,</span> <span class="n">next_line</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="c1"># Multi-line ALL CAPS title continuation (e.g. The Prince)</span> <span class="c1"># But NOT bare roman numerals like "I." — those are sub-sections</span> <span class="n">is_bare_roman</span> <span class="o">=</span> <span class="nb">bool</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="sa">r</span><span class="s2">"^[IVXLCDM]+\.\s*$"</span><span class="p">,</span> <span class="n">next_line_clean</span><span class="p">))</span> <span class="k">if</span> <span class="p">(</span> <span class="n">next_line_clean</span> <span class="ow">and</span> <span class="n">next_line_clean</span><span class="o">.</span><span class="n">isupper</span><span class="p">()</span> <span class="ow">and</span> <span class="n">heading_text</span><span class="o">.</span><span class="n">isupper</span><span class="p">()</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">next_line_clean</span><span class="p">)</span> <span class="o"><</span> <span class="mi">100</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">is_bare_roman</span> <span class="p">):</span> <span class="n">subtitle_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">next_line_clean</span><span class="p">)</span> <span class="n">k</span> <span class="o">=</span> <span class="n">j</span> <span class="o">+</span> <span class="mi">1</span> <span class="k">while</span> <span class="n">k</span> <span class="o"><</span> <span class="n">total</span> <span class="ow">and</span> <span class="n">lines</span><span class="p">[</span><span class="n">k</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="ow">and</span> <span class="n">lines</span><span class="p">[</span><span class="n">k</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">isupper</span><span class="p">():</span> <span class="n">subtitle_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">lines</span><span class="p">[</span><span class="n">k</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">())</span> <span class="n">k</span> <span class="o">+=</span> <span class="mi">1</span> <span class="n">heading_text</span> <span class="o">=</span> <span class="n">heading_text</span> <span class="o">+</span> <span class="s2">" "</span> <span class="o">+</span> <span class="s2">" "</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">subtitle_lines</span><span class="p">)</span> <span class="n">j</span> <span class="o">=</span> <span class="n">k</span> <span class="c1"># Short, Title-Case subtitle (not a paragraph start)</span> <span class="c1"># Must be very short (< 50 chars) and look like a title</span> <span class="k">elif</span> <span class="p">(</span> <span class="n">next_line_clean</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">next_line_clean</span><span class="p">)</span> <span class="o"><</span> <span class="mi">50</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">_is_heading</span><span class="p">(</span><span class="n">next_line_clean</span><span class="p">)</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">next_line_clean</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">islower</span><span class="p">()</span> <span class="ow">and</span> <span class="n">next_line_clean</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">isupper</span><span class="p">()</span> <span class="ow">and</span> <span class="n">next_line_clean</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="ow">not</span> <span class="ow">in</span> <span class="s2">".!?;,"</span> <span class="p">):</span> <span class="n">subtitle_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">next_line_clean</span><span class="p">)</span> <span class="n">j</span> <span class="o">=</span> <span class="n">j</span> <span class="o">+</span> <span class="mi">1</span> <span class="c1"># A subtitle ending in a colon introduces the next line.</span> <span class="c1"># When that line looks like a heading it is still the</span> <span class="c1"># subtitle's: the KJV's "Otherwise Called:" / "The First</span> <span class="c1"># Book of the Kings" under 1 Samuel used to open a</span> <span class="c1"># spurious section. Prose after a colon stays body text.</span> <span class="k">if</span> <span class="n">next_line_clean</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">":"</span><span class="p">):</span> <span class="n">k</span> <span class="o">=</span> <span class="n">j</span> <span class="k">while</span> <span class="n">k</span> <span class="o"><</span> <span class="n">total</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">lines</span><span class="p">[</span><span class="n">k</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span> <span class="n">k</span> <span class="o">+=</span> <span class="mi">1</span> <span class="k">if</span> <span class="n">k</span> <span class="o"><</span> <span class="n">total</span> <span class="ow">and</span> <span class="n">_is_heading</span><span class="p">(</span><span class="n">lines</span><span class="p">[</span><span class="n">k</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">()):</span> <span class="n">subtitle_lines</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+=</span> <span class="s2">" "</span> <span class="o">+</span> <span class="n">lines</span><span class="p">[</span><span class="n">k</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="n">j</span> <span class="o">=</span> <span class="n">k</span> <span class="o">+</span> <span class="mi">1</span> <span class="n">md_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="s1">'#'</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">level</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">heading_text</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">if</span> <span class="n">subtitle_lines</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">subtitle_lines</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">isupper</span><span class="p">():</span> <span class="n">md_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span> <span class="n">md_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"*</span><span class="si">{</span><span class="n">subtitle_lines</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="s2">*"</span><span class="p">)</span> <span class="n">md_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span> <span class="n">i</span> <span class="o">=</span> <span class="n">j</span> <span class="k">if</span> <span class="n">subtitle_lines</span> <span class="k">else</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span> <span class="n">prev_blank</span> <span class="o">=</span> <span class="kc">True</span> <span class="k">continue</span> <span class="c1"># Normal text line</span> <span class="n">md_lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">stripped</span><span class="p">)</span> <span class="n">prev_blank</span> <span class="o">=</span> <span class="kc">False</span> <span class="n">prev_line</span> <span class="o">=</span> <span class="n">stripped</span> <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span> <span class="k">return</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">md_lines</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="o">+</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.gutenberg.write_reference" class="doc doc-heading"> <code class="highlight language-python"><span class="n">write_reference</span><span class="p">(</span><span class="n">book_dir</span><span class="p">,</span> <span class="n">meta</span><span class="p">)</span></code> <a href="#gutenberg_kg.gutenberg.write_reference" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Write a reference.md file with Gutenberg metadata.</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/gutenberg.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">564</span> <span class="normal">565</span> <span class="normal">566</span> <span class="normal">567</span> <span class="normal">568</span> <span class="normal">569</span> <span class="normal">570</span> <span class="normal">571</span> <span class="normal">572</span> <span class="normal">573</span> <span class="normal">574</span> <span class="normal">575</span> <span class="normal">576</span> <span class="normal">577</span> <span class="normal">578</span> <span class="normal">579</span> <span class="normal">580</span> <span class="normal">581</span> <span class="normal">582</span> <span class="normal">583</span> <span class="normal">584</span> <span class="normal">585</span> <span class="normal">586</span> <span class="normal">587</span> <span class="normal">588</span> <span class="normal">589</span> <span class="normal">590</span> <span class="normal">591</span> <span class="normal">592</span> <span class="normal">593</span> <span class="normal">594</span> <span class="normal">595</span> <span class="normal">596</span> <span class="normal">597</span> <span class="normal">598</span> <span class="normal">599</span> <span class="normal">600</span> <span class="normal">601</span> <span class="normal">602</span> <span class="normal">603</span> <span class="normal">604</span> <span class="normal">605</span> <span class="normal">606</span> <span class="normal">607</span> <span class="normal">608</span> <span class="normal">609</span> <span class="normal">610</span> <span class="normal">611</span> <span class="normal">612</span> <span class="normal">613</span> <span class="normal">614</span> <span class="normal">615</span> <span class="normal">616</span> <span class="normal">617</span> <span class="normal">618</span> <span class="normal">619</span> <span class="normal">620</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">write_reference</span><span class="p">(</span><span class="n">book_dir</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">meta</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Write a reference.md file with Gutenberg metadata."""</span> <span class="n">ref_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">book_dir</span><span class="p">,</span> <span class="s2">"reference.md"</span><span class="p">)</span> <span class="n">lines</span> <span class="o">=</span> <span class="p">[</span> <span class="sa">f</span><span class="s2">"# Reference: </span><span class="si">{</span><span class="n">meta</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'title'</span><span class="p">,</span><span class="w"> </span><span class="s1">'Unknown'</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="s2">"## Source"</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"- **Project Gutenberg ID**: </span><span class="si">{</span><span class="n">meta</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'ebook_id'</span><span class="p">,</span><span class="w"> </span><span class="s1">'?'</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"- **URL**: </span><span class="si">{</span><span class="n">meta</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'gutenberg_url'</span><span class="p">,</span><span class="w"> </span><span class="s1">'N/A'</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"- **Rights**: </span><span class="si">{</span><span class="n">meta</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'rights'</span><span class="p">,</span><span class="w"> </span><span class="s1">'Public domain in the USA.'</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="p">]</span> <span class="n">authored</span> <span class="o">=</span> <span class="n">author_lines</span><span class="p">(</span><span class="n">meta</span><span class="p">)</span> <span class="k">if</span> <span class="n">authored</span><span class="p">:</span> <span class="n">lines</span> <span class="o">+=</span> <span class="p">[</span><span class="s2">"## Author"</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="o">*</span><span class="n">authored</span><span class="p">,</span> <span class="s2">""</span><span class="p">]</span> <span class="k">if</span> <span class="n">meta</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"published"</span><span class="p">):</span> <span class="n">lines</span> <span class="o">+=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">"- **Gutenberg Published**: </span><span class="si">{</span><span class="n">meta</span><span class="p">[</span><span class="s1">'published'</span><span class="p">]</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="s2">""</span><span class="p">]</span> <span class="k">if</span> <span class="n">meta</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"language"</span><span class="p">):</span> <span class="n">lines</span> <span class="o">+=</span> <span class="p">[</span> <span class="s2">"## Language"</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"- </span><span class="si">{</span><span class="n">meta</span><span class="p">[</span><span class="s1">'language'</span><span class="p">]</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="p">]</span> <span class="k">if</span> <span class="n">meta</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"subjects"</span><span class="p">):</span> <span class="n">lines</span> <span class="o">+=</span> <span class="p">[</span><span class="s2">"## Subjects"</span><span class="p">,</span> <span class="s2">""</span><span class="p">]</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"subjects"</span><span class="p">]:</span> <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">"- </span><span class="si">{</span><span class="n">s</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span> <span class="k">if</span> <span class="n">meta</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"summary"</span><span class="p">):</span> <span class="n">lines</span> <span class="o">+=</span> <span class="p">[</span> <span class="s2">"## Summary"</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"summary"</span><span class="p">],</span> <span class="s2">""</span><span class="p">,</span> <span class="p">]</span> <span class="k">if</span> <span class="n">meta</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"note"</span><span class="p">):</span> <span class="n">lines</span> <span class="o">+=</span> <span class="p">[</span> <span class="s2">"## Notes"</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"note"</span><span class="p">],</span> <span class="s2">""</span><span class="p">,</span> <span class="p">]</span> <span class="n">content</span> <span class="o">=</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">ref_path</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">"utf-8"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span> <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">content</span><span class="p">)</span> <span class="k">return</span> <span class="n">ref_path</span> </code></pre></div></td></tr></table></div> </details> </div> </div> </div> </div> </div> </article> </div> <script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script> </div> <button type="button" class="md-top md-icon" data-md-component="top" hidden> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg> Back to top </button> </main> <footer class="md-footer"> <div class="md-footer-meta md-typeset"> <div class="md-footer-meta__inner md-grid"> <div class="md-copyright"> Made with <a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener"> Material for MkDocs </a> </div> </div> </div> </footer> </div> <div class="md-dialog" data-md-component="dialog"> <div class="md-dialog__inner md-typeset"></div> </div> <script id="__config" type="application/json">{"annotate": null, "base": "../..", "features": ["navigation.tabs", "navigation.top", "content.code.copy", "content.action.edit"], "search": "../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script> <script src="../../assets/javascripts/bundle.d7400e89.min.js"></script> </body> </html>