Skip to content

Internet Archive source

gutenberg_kg.ia

Download books from Internet Archive as structured Markdown.

Public API (used by the Click CLI in gutenberg_kg.cli.cmd_ia): download_book() — fetch one item by IA identifier run_catalog() — download all items from a catalog file run_survey() — scan corpus and print download/ingest status search_ia() — search the IA full-text search API format_search_results() — print search results as a table

Entry point: gutenkg ia [options]

Output structure (per book): corpus/// <slug>.md — structured Markdown with section headings reference.md — Internet Archive metadata sidecar</p> <div class="doc doc-children"> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.ia.clean_ocr" class="doc doc-heading"> <code class="highlight language-python"><span class="n">clean_ocr</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">report</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span></code> <a href="#gutenberg_kg.ia.clean_ocr" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Clean common OCR artifacts from Internet Archive DjVu text.</p> <p>Steps (in order): 1. Strip trailing whitespace from every line. The Archive's Tesseract text ends each line with a space, which kept step 3 from ever joining a hyphenated word. 2. Normalize unicode ligatures and common smart-quote variants 3. Join hyphenated line-breaks (OCR word-wrap artifact) 4. Strip bare page-number lines, including "4,047" / "4.047" 5. Strip the back-of-book index section (must happen before running-header removal so the threshold is measured against the original body length) 6. Remove running headers/footers: lines that repeat 4+ times, and running titles that repeat 4+ times with a changing page number 7. Remove figure/illustration markers 8. Blank lines of OCR debris (:func:<code>is_ocr_debris</code>): drawings and rotated text read as characters. Blanked rather than deleted, so the text either side of a figure is not run together. 9. Drop marks standing alone at the edge of a line of text 10. Collapse excessive blank lines</p> <p>Nothing restores what the scan itself lacks: a word cut off at the page edge stays cut off.</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>text</code> </td> <td> <code><span title="str">str</span></code> </td> <td> <div class="doc-md-description"> <p>DjVu text as the Archive serves it.</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>report</code> </td> <td> <code><span title="dict">dict</span> | None</code> </td> <td> <div class="doc-md-description"> <p>Optional dict that receives a count per step: <code>hyphens_joined</code>, <code>page_numbers</code>, <code>page_headers</code>, <code>debris_lines</code> and <code>margin_marks</code>.</p> </div> </td> <td> <code>None</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>The cleaned text.</p> </div> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></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> <span class="normal">557</span> <span class="normal">558</span> <span class="normal">559</span> <span class="normal">560</span> <span class="normal">561</span> <span class="normal">562</span> <span class="normal">563</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></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">clean_ocr</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">report</span><span class="p">:</span> <span class="nb">dict</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="nb">str</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Clean common OCR artifacts from Internet Archive DjVu text.</span> <span class="sd"> Steps (in order):</span> <span class="sd"> 1. Strip trailing whitespace from every line. The Archive's Tesseract</span> <span class="sd"> text ends each line with a space, which kept step 3 from ever joining</span> <span class="sd"> a hyphenated word.</span> <span class="sd"> 2. Normalize unicode ligatures and common smart-quote variants</span> <span class="sd"> 3. Join hyphenated line-breaks (OCR word-wrap artifact)</span> <span class="sd"> 4. Strip bare page-number lines, including "4,047" / "4.047"</span> <span class="sd"> 5. Strip the back-of-book index section (must happen before running-header</span> <span class="sd"> removal so the threshold is measured against the original body length)</span> <span class="sd"> 6. Remove running headers/footers: lines that repeat 4+ times, and</span> <span class="sd"> running titles that repeat 4+ times with a changing page number</span> <span class="sd"> 7. Remove figure/illustration markers</span> <span class="sd"> 8. Blank lines of OCR debris (:func:`is_ocr_debris`): drawings and</span> <span class="sd"> rotated text read as characters. Blanked rather than deleted, so the</span> <span class="sd"> text either side of a figure is not run together.</span> <span class="sd"> 9. Drop marks standing alone at the edge of a line of text</span> <span class="sd"> 10. Collapse excessive blank lines</span> <span class="sd"> Nothing restores what the scan itself lacks: a word cut off at the page</span> <span class="sd"> edge stays cut off.</span> <span class="sd"> :param text: DjVu text as the Archive serves it.</span> <span class="sd"> :param report: Optional dict that receives a count per step:</span> <span class="sd"> ``hyphens_joined``, ``page_numbers``, ``page_headers``,</span> <span class="sd"> ``debris_lines`` and ``margin_marks``.</span> <span class="sd"> :returns: The cleaned text.</span> <span class="sd"> """</span> <span class="n">counts</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"hyphens_joined"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s2">"page_numbers"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s2">"page_headers"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s2">"debris_lines"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s2">"margin_marks"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="p">}</span> <span class="c1"># 1. Trailing whitespace</span> <span class="n">text</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">line</span><span class="o">.</span><span class="n">rstrip</span><span class="p">()</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">text</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"</span><span class="se">\r\n</span><span class="s2">"</span><span class="p">,</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</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="c1"># 2. Ligatures and smart quotes</span> <span class="k">for</span> <span class="n">ligature</span><span class="p">,</span> <span class="n">replacement</span> <span class="ow">in</span> <span class="n">LIGATURES</span><span class="o">.</span><span class="n">items</span><span class="p">():</span> <span class="n">text</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="n">ligature</span><span class="p">,</span> <span class="n">replacement</span><span class="p">)</span> <span class="n">text</span> <span class="o">=</span> <span class="p">(</span> <span class="n">text</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"</span><span class="se">\u2019</span><span class="s2">"</span><span class="p">,</span> <span class="s2">"'"</span><span class="p">)</span> <span class="c1"># right single quote</span> <span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"</span><span class="se">\u2018</span><span class="s2">"</span><span class="p">,</span> <span class="s2">"'"</span><span class="p">)</span> <span class="c1"># left single quote</span> <span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"</span><span class="se">\u201c</span><span class="s2">"</span><span class="p">,</span> <span class="s1">'"'</span><span class="p">)</span> <span class="c1"># left double quote</span> <span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"</span><span class="se">\u201d</span><span class="s2">"</span><span class="p">,</span> <span class="s1">'"'</span><span class="p">)</span> <span class="c1"># right double quote</span> <span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"</span><span class="se">\u00ad</span><span class="s2">"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="p">)</span> <span class="c1"># soft hyphen (remove entirely)</span> <span class="c1"># 3. Join hyphenated line-breaks: "mag-\nnetism" -> "magnetism"</span> <span class="n">text</span><span class="p">,</span> <span class="n">counts</span><span class="p">[</span><span class="s2">"hyphens_joined"</span><span class="p">]</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">subn</span><span class="p">(</span><span class="sa">r</span><span class="s2">"(\w)-\n(\w)"</span><span class="p">,</span> <span class="sa">r</span><span class="s2">"\1\2"</span><span class="p">,</span> <span class="n">text</span><span class="p">)</span> <span class="c1"># 4. Bare page-number lines</span> <span class="n">text</span><span class="p">,</span> <span class="n">counts</span><span class="p">[</span><span class="s2">"page_numbers"</span><span class="p">]</span> <span class="o">=</span> <span class="n">PAGE_NUMBER_RE</span><span class="o">.</span><span class="n">subn</span><span class="p">(</span><span class="s2">""</span><span class="p">,</span> <span class="n">text</span><span class="p">)</span> <span class="c1"># 5. Strip the index section before running-header removal so the 70%</span> <span class="c1"># threshold is measured against the full (uncollapsed) body.</span> <span class="n">last_index_match</span><span class="p">:</span> <span class="n">re</span><span class="o">.</span><span class="n">Match</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span> <span class="k">for</span> <span class="n">m</span> <span class="ow">in</span> <span class="n">INDEX_HEADING_RE</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">text</span><span class="p">):</span> <span class="n">last_index_match</span> <span class="o">=</span> <span class="n">m</span> <span class="k">if</span> <span class="n">last_index_match</span> <span class="ow">and</span> <span class="n">last_index_match</span><span class="o">.</span><span class="n">start</span><span class="p">()</span> <span class="o">></span> <span class="nb">len</span><span class="p">(</span><span class="n">text</span><span class="p">)</span> <span class="o">*</span> <span class="mf">0.70</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">last_index_match</span><span class="o">.</span><span class="n">start</span><span class="p">()]</span><span class="o">.</span><span class="n">rstrip</span><span class="p">()</span> <span class="o">+</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span> <span class="c1"># 6. Running headers / footers, exact and page-numbered</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">running</span> <span class="o">=</span> <span class="n">_detect_running_headers</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span> <span class="n">numbered</span> <span class="o">=</span> <span class="n">_numbered_header_keys</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span> <span class="n">kept</span> <span class="o">=</span> <span class="p">[</span> <span class="n">ln</span> <span class="k">for</span> <span class="n">ln</span> <span class="ow">in</span> <span class="n">lines</span> <span class="k">if</span> <span class="n">ln</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">running</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">_is_numbered_header</span><span class="p">(</span><span class="n">ln</span><span class="p">,</span> <span class="n">numbered</span><span class="p">)</span> <span class="p">]</span> <span class="n">counts</span><span class="p">[</span><span class="s2">"page_headers"</span><span class="p">]</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="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">kept</span><span class="p">)</span> <span class="n">lines</span> <span class="o">=</span> <span class="n">kept</span> <span class="c1"># 7. Figure / illustration markers</span> <span class="n">lines</span> <span class="o">=</span> <span class="p">[</span><span class="n">FIGURE_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">ln</span><span class="p">)</span> <span class="k">for</span> <span class="n">ln</span> <span class="ow">in</span> <span class="n">lines</span><span class="p">]</span> <span class="c1"># 8-9. Debris lines and margin marks</span> <span class="n">vocab</span> <span class="o">=</span> <span class="n">recurring_words</span><span class="p">(</span><span class="n">text</span><span class="p">)</span> <span class="n">cleaned</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">for</span> <span class="n">ln</span> <span class="ow">in</span> <span class="n">lines</span><span class="p">:</span> <span class="k">if</span> <span class="n">is_ocr_debris</span><span class="p">(</span><span class="n">ln</span><span class="p">,</span> <span class="n">vocab</span><span class="p">):</span> <span class="n">counts</span><span class="p">[</span><span class="s2">"debris_lines"</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span> <span class="n">cleaned</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">continue</span> <span class="n">ln</span><span class="p">,</span> <span class="n">marks</span> <span class="o">=</span> <span class="n">_strip_margin_marks</span><span class="p">(</span><span class="n">ln</span><span class="p">)</span> <span class="n">counts</span><span class="p">[</span><span class="s2">"margin_marks"</span><span class="p">]</span> <span class="o">+=</span> <span class="n">marks</span> <span class="n">cleaned</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ln</span><span class="p">)</span> <span class="n">text</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">cleaned</span><span class="p">)</span> <span class="c1"># 10. Collapse 3+ consecutive blank lines to 2</span> <span class="n">text</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">"\n{4,}"</span><span class="p">,</span> <span class="s2">"</span><span class="se">\n\n\n</span><span class="s2">"</span><span class="p">,</span> <span class="n">text</span><span class="p">)</span> <span class="k">if</span> <span class="n">report</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span> <span class="n">report</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">counts</span><span class="p">)</span> <span class="k">return</span> <span class="n">text</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.ia.download_book" class="doc doc-heading"> <code class="highlight language-python"><span class="n">download_book</span><span class="p">(</span><span class="n">identifier</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.ia.download_book" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Download an IA item, convert to Markdown, write reference.md.</p> <p>Returns path to the .md file on success, None on failure.</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"> 934</span> <span class="normal"> 935</span> <span class="normal"> 936</span> <span class="normal"> 937</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> <span class="normal"> 990</span> <span class="normal"> 991</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></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">identifier</span><span class="p">:</span> <span class="nb">str</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="o">|</span> <span class="kc">None</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Download an IA item, convert to Markdown, write reference.md.</span> <span class="sd"> Returns path to the .md file on success, None on failure.</span> <span class="sd"> """</span> <span class="c1"># Identifier-based idempotence, ahead of the metadata fetch so a skip costs</span> <span class="c1"># no network round-trip. See _find_item_by_id for why the path check below</span> <span class="c1"># is not sufficient on its own.</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">CORPUS_ROOT</span> <span class="o">/</span> <span class="n">genre</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_item_by_id</span><span class="p">(</span><span class="n">identifier</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">Path</span><span class="p">(</span><span class="n">existing</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">name</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" [=] already downloaded (IA </span><span class="si">{</span><span class="n">identifier</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: an item on disk but uncatalogued</span> <span class="c1"># is exactly the pre-existing drift, and re-running is the repair.</span> <span class="n">record_in_catalog</span><span class="p">(</span><span class="n">genre</span><span class="p">,</span> <span class="n">identifier</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="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" Fetching metadata for </span><span class="si">{</span><span class="n">identifier</span><span class="si">!r}</span><span class="s2">..."</span><span class="p">)</span> <span class="k">try</span><span class="p">:</span> <span class="n">meta</span> <span class="o">=</span> <span class="n">fetch_ia_metadata</span><span class="p">(</span><span class="n">identifier</span><span class="p">)</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">" [!] Metadata fetch failed: </span><span class="si">{</span><span class="n">exc</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">return</span> <span class="kc">None</span> <span class="k">if</span> <span class="n">title</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</span> <span class="n">item_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="ow">or</span> <span class="n">identifier</span> <span class="n">files</span> <span class="o">=</span> <span class="n">meta</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="s2">"_files"</span><span class="p">,</span> <span class="p">[])</span> <span class="c1"># Resolve output directory</span> <span class="n">base</span> <span class="o">=</span> <span class="n">CORPUS_ROOT</span> <span class="o">/</span> <span class="n">genre</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">book_dir</span> <span class="o">=</span> <span class="n">base</span> <span class="o">/</span> <span class="n">item_title</span> <span class="n">slug</span> <span class="o">=</span> <span class="n">slugify</span><span class="p">(</span><span class="n">item_title</span><span class="p">)</span> <span class="n">md_path</span> <span class="o">=</span> <span class="n">book_dir</span> <span class="o">/</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="k">if</span> <span class="n">md_path</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">force</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" [=] Already exists: </span><span class="si">{</span><span class="n">md_path</span><span class="si">}</span><span class="s2"> (use --force to re-download)"</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">identifier</span><span class="p">,</span> <span class="n">item_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="nb">str</span><span class="p">(</span><span class="n">md_path</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 write: </span><span class="si">{</span><span class="n">md_path</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">" [dry] Would write: </span><span class="si">{</span><span class="n">book_dir</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="s1">'reference.md'</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="n">md_path</span><span class="p">)</span> <span class="c1"># Download text</span> <span class="n">text</span> <span class="o">=</span> <span class="n">fetch_text</span><span class="p">(</span><span class="n">identifier</span><span class="p">,</span> <span class="n">files</span><span class="p">)</span> <span class="k">if</span> <span class="n">text</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="k">return</span> <span class="kc">None</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" Converting </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">text</span><span class="p">)</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2"> chars to Markdown..."</span><span class="p">)</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"cleanup"</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span> <span class="n">markdown</span> <span class="o">=</span> <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> <span class="n">report</span><span class="o">=</span><span class="n">meta</span><span class="p">[</span><span class="s2">"cleanup"</span><span class="p">])</span> <span class="c1"># Write files</span> <span class="n">book_dir</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</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">md_path</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span><span class="n">markdown</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="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" [✓] Markdown: </span><span class="si">{</span><span class="n">md_path</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">markdown</span><span class="p">)</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2"> chars)"</span><span class="p">)</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">" [✓] Reference: </span><span class="si">{</span><span class="n">book_dir</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="s1">'reference.md'</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">identifier</span><span class="p">,</span> <span class="n">item_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="nb">str</span><span class="p">(</span><span class="n">md_path</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.ia.export_draft_catalog" class="doc doc-heading"> <code class="highlight language-python"><span class="n">export_draft_catalog</span><span class="p">(</span><span class="n">query</span><span class="p">,</span> <span class="n">results</span><span class="p">,</span> <span class="n">path</span><span class="p">)</span></code> <a href="#gutenberg_kg.ia.export_draft_catalog" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Write search results to a catalog file as commented-out draft entries.</p> <p>The curation step between "search returned 25 things" and "download these six". Every line is written commented out, so an un-reviewed draft downloads nothing if handed straight to <code>gutenkg ia catalog</code> -- IA search is noisy and returns modern reprints alongside period scans, which is exactly what needs a human pass before anything is fetched.</p> <p>Appending several searches into one file before curating is the intended workflow; see <code>docs/IA_CATALOG_WORKFLOW.md</code>.</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>The search query, recorded in the file header for provenance.</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>results</code> </td> <td> <code><span title="list">list</span>[<span title="dict">dict</span>]</code> </td> <td> <div class="doc-md-description"> <p>Search result dicts from :func:<code>search_ia</code>.</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>path</code> </td> <td> <code><span title="str">str</span> | <span title="pathlib.Path">Path</span></code> </td> <td> <div class="doc-md-description"> <p>Destination catalog file; parent directories are created.</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="int">int</span></code> </td> <td> <div class="doc-md-description"> <p>Number of entries written.</p> </div> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></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></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">export_draft_catalog</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">results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">],</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="n">Path</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">"""Write search results to a catalog file as commented-out draft entries.</span> <span class="sd"> The curation step between "search returned 25 things" and "download these</span> <span class="sd"> six". Every line is written commented out, so an un-reviewed draft downloads</span> <span class="sd"> nothing if handed straight to ``gutenkg ia catalog`` -- IA search is noisy</span> <span class="sd"> and returns modern reprints alongside period scans, which is exactly what</span> <span class="sd"> needs a human pass before anything is fetched.</span> <span class="sd"> Appending several searches into one file before curating is the intended</span> <span class="sd"> workflow; see ``docs/IA_CATALOG_WORKFLOW.md``.</span> <span class="sd"> :param query: The search query, recorded in the file header for provenance.</span> <span class="sd"> :param results: Search result dicts from :func:`search_ia`.</span> <span class="sd"> :param path: Destination catalog file; parent directories are created.</span> <span class="sd"> :returns: Number of entries written.</span> <span class="sd"> """</span> <span class="n">out</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="n">out</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</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="n">out</span><span class="o">.</span><span class="n">open</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="sa">f</span><span class="s2">"# Catalog draft — IA search: </span><span class="si">{</span><span class="n">query</span><span class="si">}</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="s2">"# Every line is commented out. Uncomment the ones you want.</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="s2">"# Format: <identifier>[TAB<title>] (comment lines start with #)</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="s2">"# Check rights before uncommenting: an IA hit's year is the edition</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="s2">"# scanned, not evidence the text is free.</span><span class="se">\n\n</span><span class="s2">"</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</span> <span class="o">=</span> <span class="n">r</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="s2">""</span> <span class="n">year</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"date"</span><span class="p">)</span> <span class="ow">or</span> <span class="s2">"?"</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">r</span><span class="p">[</span><span class="s1">'identifier'</span><span class="p">]</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">\t</span><span class="s2"># </span><span class="si">{</span><span class="n">year</span><span class="si">}</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">results</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.ia.fetch_ia_metadata" class="doc doc-heading"> <code class="highlight language-python"><span class="n">fetch_ia_metadata</span><span class="p">(</span><span class="n">identifier</span><span class="p">)</span></code> <a href="#gutenberg_kg.ia.fetch_ia_metadata" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Fetch item metadata from the Internet Archive JSON API.</p> <p>Returns a flat dict with keys: identifier, title, author, publisher, date, subjects, language, description, volume, series, edition, rights, plus '_files' (raw file list for text discovery).</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></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_ia_metadata</span><span class="p">(</span><span class="n">identifier</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">"""Fetch item metadata from the Internet Archive JSON API.</span> <span class="sd"> Returns a flat dict with keys: identifier, title, author, publisher,</span> <span class="sd"> date, subjects, language, description, volume, series, edition, rights,</span> <span class="sd"> plus '_files' (raw file list for text discovery).</span> <span class="sd"> """</span> <span class="n">url</span> <span class="o">=</span> <span class="n">IA_METADATA_URL</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">identifier</span><span class="o">=</span><span class="n">identifier</span><span class="p">)</span> <span class="n">raw</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">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">raw</span><span class="p">)</span> <span class="n">ia</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"metadata"</span><span class="p">,</span> <span class="p">{})</span> <span class="n">files</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"files"</span><span class="p">,</span> <span class="p">[])</span> <span class="n">meta</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"identifier"</span><span class="p">:</span> <span class="n">identifier</span><span class="p">,</span> <span class="s2">"_files"</span><span class="p">:</span> <span class="n">files</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">_coerce_str</span><span class="p">(</span><span class="n">ia</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">""</span><span class="p">))</span> <span class="ow">or</span> <span class="n">identifier</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">_coerce_str</span><span class="p">(</span><span class="n">ia</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"creator"</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">"publisher"</span><span class="p">]</span> <span class="o">=</span> <span class="n">_coerce_str</span><span class="p">(</span><span class="n">ia</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"publisher"</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">"volume"</span><span class="p">]</span> <span class="o">=</span> <span class="n">_coerce_str</span><span class="p">(</span><span class="n">ia</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"volume"</span><span class="p">)</span> <span class="ow">or</span> <span class="n">ia</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"volumenumber"</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">"series"</span><span class="p">]</span> <span class="o">=</span> <span class="n">_coerce_str</span><span class="p">(</span><span class="n">ia</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"series"</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">"edition"</span><span class="p">]</span> <span class="o">=</span> <span class="n">_coerce_str</span><span class="p">(</span><span class="n">ia</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"edition"</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">"language"</span><span class="p">]</span> <span class="o">=</span> <span class="n">_coerce_str</span><span class="p">(</span><span class="n">ia</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="s2">"eng"</span><span class="p">))</span> <span class="ow">or</span> <span class="s2">"eng"</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">_coerce_list</span><span class="p">(</span><span class="n">ia</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"subject"</span><span class="p">,</span> <span class="p">[]))</span> <span class="c1"># The engine the Archive ran over the scan, e.g. "tesseract 5.0.0-1-g862e".</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"ocr"</span><span class="p">]</span> <span class="o">=</span> <span class="n">_coerce_str</span><span class="p">(</span><span class="n">ia</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"ocr"</span><span class="p">,</span> <span class="s2">""</span><span class="p">))</span> <span class="c1"># Date: keep only the year</span> <span class="n">raw_date</span> <span class="o">=</span> <span class="n">_coerce_str</span><span class="p">(</span><span class="n">ia</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"date"</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">"date"</span><span class="p">]</span> <span class="o">=</span> <span class="n">raw_date</span><span class="p">[:</span><span class="mi">4</span><span class="p">]</span> <span class="k">if</span> <span class="n">raw_date</span> <span class="k">else</span> <span class="s2">""</span> <span class="c1"># Description: may contain HTML</span> <span class="n">raw_desc</span> <span class="o">=</span> <span class="n">ia</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"description"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">raw_desc</span><span class="p">,</span> <span class="nb">list</span><span class="p">):</span> <span class="n">raw_desc</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">raw_desc</span><span class="p">)</span> <span class="n">raw_desc</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">"<[^>]+>"</span><span class="p">,</span> <span class="s2">" "</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">raw_desc</span><span class="p">))</span> <span class="n">meta</span><span class="p">[</span><span class="s2">"description"</span><span class="p">]</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">raw_desc</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="c1"># Rights</span> <span class="n">access</span> <span class="o">=</span> <span class="n">_coerce_str</span><span class="p">(</span><span class="n">ia</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"identifier-access"</span><span class="p">)</span> <span class="ow">or</span> <span class="n">ia</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"licenseurl"</span><span class="p">,</span> <span class="s2">""</span><span class="p">))</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">access</span> <span class="ow">or</span> <span class="s2">"publicdomain"</span> <span class="ow">in</span> <span class="n">access</span><span class="o">.</span><span class="n">lower</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="s2">"Public domain"</span> <span class="k">else</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">access</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.ia.fetch_text" class="doc doc-heading"> <code class="highlight language-python"><span class="n">fetch_text</span><span class="p">(</span><span class="n">identifier</span><span class="p">,</span> <span class="n">files</span><span class="p">)</span></code> <a href="#gutenberg_kg.ia.fetch_text" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Download the best available text for an IA item. Returns raw text or None.</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></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></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">fetch_text</span><span class="p">(</span><span class="n">identifier</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">files</span><span class="p">:</span> <span class="nb">list</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="o">|</span> <span class="kc">None</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Download the best available text for an IA item. Returns raw text or None."""</span> <span class="n">result</span> <span class="o">=</span> <span class="n">find_text_file</span><span class="p">(</span><span class="n">identifier</span><span class="p">,</span> <span class="n">files</span><span class="p">)</span> <span class="k">if</span> <span class="n">result</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" [!] No text file found for </span><span class="si">{</span><span class="n">identifier</span><span class="si">!r}</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">" [!] Available formats: </span><span class="si">{</span><span class="nb">sorted</span><span class="p">({</span><span class="n">f</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'format'</span><span class="p">,</span><span class="w"> </span><span class="s1">'?'</span><span class="p">)</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">f</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">files</span><span class="p">})</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">return</span> <span class="kc">None</span> <span class="n">filename</span><span class="p">,</span> <span class="n">fmt</span> <span class="o">=</span> <span class="n">result</span> <span class="n">url</span> <span class="o">=</span> <span class="n">IA_DOWNLOAD_URL</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">identifier</span><span class="o">=</span><span class="n">identifier</span><span class="p">,</span> <span class="n">filename</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">filename</span><span class="p">))</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">fmt</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">try</span><span class="p">:</span> <span class="k">return</span> <span class="n">fetch_url</span><span class="p">(</span><span class="n">url</span><span class="p">)</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">" [!] Download failed: </span><span class="si">{</span><span class="n">exc</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">return</span> <span class="kc">None</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.ia.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.ia.fetch_url" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Fetch URL with retries and exponential backoff.</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">122</span> <span class="normal">123</span> <span class="normal">124</span> <span class="normal">125</span> <span class="normal">126</span> <span class="normal">127</span> <span class="normal">128</span> <span class="normal">129</span> <span class="normal">130</span> <span class="normal">131</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></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 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">"AudelKG/1.0 (archive.org research)"</span><span class="p">},</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">60</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="n">errors</span><span class="o">=</span><span class="s2">"replace"</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.ia.find_text_file" class="doc doc-heading"> <code class="highlight language-python"><span class="n">find_text_file</span><span class="p">(</span><span class="n">identifier</span><span class="p">,</span> <span class="n">files</span><span class="p">)</span></code> <a href="#gutenberg_kg.ia.find_text_file" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Find the best available plain-text file in an IA item.</p> <p>Priority: DjVu Text (_djvu.txt) > plain .txt Returns (filename, format_label) or None.</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.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></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">find_text_file</span><span class="p">(</span><span class="n">identifier</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">])</span> <span class="o">-></span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</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">"""Find the best available plain-text file in an IA item.</span> <span class="sd"> Priority: DjVu Text (_djvu.txt) > plain .txt</span> <span class="sd"> Returns (filename, format_label) or None.</span> <span class="sd"> """</span> <span class="c1"># DjVu text layer — cleanest OCR output from scanned books</span> <span class="n">djvu</span> <span class="o">=</span> <span class="p">[</span><span class="n">f</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">files</span> <span class="k">if</span> <span class="n">f</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"name"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">"_djvu.txt"</span><span class="p">)]</span> <span class="k">if</span> <span class="n">djvu</span><span class="p">:</span> <span class="k">return</span> <span class="p">(</span><span class="nb">min</span><span class="p">(</span><span class="n">djvu</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="nb">len</span><span class="p">),</span> <span class="s2">"DjVu Text"</span><span class="p">)</span> <span class="c1"># Fallback: any plain text file that isn't metadata/readme</span> <span class="n">txt</span> <span class="o">=</span> <span class="p">[</span> <span class="n">f</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">files</span> <span class="k">if</span> <span class="p">(</span> <span class="n">f</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"name"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">".txt"</span><span class="p">)</span> <span class="ow">and</span> <span class="s2">"readme"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">f</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"name"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="ow">and</span> <span class="s2">"_meta"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">f</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"name"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="ow">and</span> <span class="s2">"_files"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">f</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"name"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="p">)</span> <span class="p">]</span> <span class="k">if</span> <span class="n">txt</span><span class="p">:</span> <span class="k">return</span> <span class="p">(</span><span class="nb">min</span><span class="p">(</span><span class="n">txt</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="nb">len</span><span class="p">),</span> <span class="s2">"Plain Text"</span><span class="p">)</span> <span class="k">return</span> <span class="kc">None</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.ia.format_search_results" class="doc doc-heading"> <code class="highlight language-python"><span class="n">format_search_results</span><span class="p">(</span><span class="n">results</span><span class="p">)</span></code> <a href="#gutenberg_kg.ia.format_search_results" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Print IA search results as a formatted table.</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></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></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">format_search_results</span><span class="p">(</span><span class="n">results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</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 IA search results as a formatted table."""</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."</span><span class="p">)</span> <span class="k">return</span> <span class="n">id_w</span> <span class="o">=</span> <span class="nb">max</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">r</span><span class="p">[</span><span class="s2">"identifier"</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="o">+</span> <span class="mi">2</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">'Identifier'</span><span class="si">:</span><span class="s2"><</span><span class="si">{</span><span class="n">id_w</span><span class="si">}}</span><span class="s2"> </span><span class="si">{</span><span class="s1">'Year'</span><span class="si">:</span><span class="s2"><6</span><span class="si">}</span><span class="s2"> Title"</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="s2">"-"</span> <span class="o">*</span> <span class="p">(</span><span class="n">id_w</span> <span class="o">+</span> <span class="mi">60</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="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">'identifier'</span><span class="p">]</span><span class="si">:</span><span class="s2"><</span><span class="si">{</span><span class="n">id_w</span><span class="si">}}</span><span class="s2"> </span><span class="si">{</span><span class="n">r</span><span class="p">[</span><span class="s1">'date'</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">r</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</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)."</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.ia.is_ocr_debris" class="doc doc-heading"> <code class="highlight language-python"><span class="n">is_ocr_debris</span><span class="p">(</span><span class="n">line</span><span class="p">,</span> <span class="n">vocab</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span></code> <a href="#gutenberg_kg.ia.is_ocr_debris" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Whether a line of OCR text is debris rather than text.</p> <p>Drawings, rotated or mirrored captions, and specks come out of OCR as lines like "s Cs ve ig" or "—t | x . Y \N —". A line is debris when fewer than half its characters are letters or digits, or fewer than half its tokens read as words, numbers or abbreviations. Dot leaders, pipes, dashes and underscores are ignored, so a table row such as "Buff limestone........ 40-60 20" survives.</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>line</code> </td> <td> <code><span title="str">str</span></code> </td> <td> <div class="doc-md-description"> <p>One line, any whitespace.</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>vocab</code> </td> <td> <code><span title="frozenset">frozenset</span>[<span title="str">str</span>] | None</code> </td> <td> <div class="doc-md-description"> <p>Words known to recur in the text, from :func:<code>recurring_words</code>. Without it only the shape of each token is judged, which lets garbage such as "Carmoog" pass as a word.</p> </div> </td> <td> <code>None</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 for debris; False for text and for a blank line.</p> </div> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></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></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">is_ocr_debris</span><span class="p">(</span><span class="n">line</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">vocab</span><span class="p">:</span> <span class="nb">frozenset</span><span class="p">[</span><span class="nb">str</span><span class="p">]</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="nb">bool</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Whether a line of OCR text is debris rather than text.</span> <span class="sd"> Drawings, rotated or mirrored captions, and specks come out of OCR as</span> <span class="sd"> lines like "s Cs ve ig" or "—t | x . Y \\N —". A line is debris when</span> <span class="sd"> fewer than half its characters are letters or digits, or fewer than half</span> <span class="sd"> its tokens read as words, numbers or abbreviations. Dot leaders, pipes,</span> <span class="sd"> dashes and underscores are ignored, so a table row such as</span> <span class="sd"> "Buff limestone........ 40-60 20" survives.</span> <span class="sd"> :param line: One line, any whitespace.</span> <span class="sd"> :param vocab: Words known to recur in the text, from :func:`recurring_words`.</span> <span class="sd"> Without it only the shape of each token is judged, which lets</span> <span class="sd"> garbage such as "Carmoog" pass as a word.</span> <span class="sd"> :returns: True for debris; False for text and for a blank line.</span> <span class="sd"> """</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="k">if</span> <span class="ow">not</span> <span class="n">stripped</span><span class="p">:</span> <span class="k">return</span> <span class="kc">False</span> <span class="c1"># A structural heading is text however rare its words ("CHAPTER XLII").</span> <span class="k">if</span> <span class="n">_STRUCTURAL_HEADING_RE</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">stripped</span><span class="p">)</span> <span class="ow">or</span> <span class="n">QUES_PATTERN</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">stripped</span><span class="p">):</span> <span class="k">return</span> <span class="kc">False</span> <span class="n">body</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|\.{3,}"</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="n">stripped</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">body</span> <span class="ow">or</span> <span class="nb">sum</span><span class="p">(</span><span class="n">c</span><span class="o">.</span><span class="n">isalnum</span><span class="p">()</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">body</span><span class="p">)</span> <span class="o"><</span> <span class="mf">0.5</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">body</span><span class="p">):</span> <span class="k">return</span> <span class="kc">True</span> <span class="n">tokens</span> <span class="o">=</span> <span class="p">[</span><span class="n">t</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">_TOKEN_SPLIT_RE</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">stripped</span><span class="p">)</span> <span class="k">if</span> <span class="n">t</span><span class="p">]</span> <span class="n">verdicts</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="p">(</span><span class="n">_token_is_wordlike</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">vocab</span><span class="p">)</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">tokens</span><span class="p">)</span> <span class="k">if</span> <span class="n">v</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">]</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">verdicts</span><span class="p">:</span> <span class="k">return</span> <span class="kc">True</span> <span class="k">if</span> <span class="n">vocab</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">tokens</span><span class="p">)</span> <span class="o"><=</span> <span class="mi">3</span><span class="p">:</span> <span class="c1"># A short line of ordinary words ("thing seriously amiss.",</span> <span class="c1"># "Inductance reactance.") can fail recurrence in one volume. Keeping</span> <span class="c1"># an occasional stray word costs less than losing a sentence, so a</span> <span class="c1"># short line survives when every token is a well-formed word of three</span> <span class="c1"># or more letters in ordinary case. All-caps junk ("WZZLLLN") does not.</span> <span class="n">bares</span> <span class="o">=</span> <span class="p">[</span><span class="n">t</span><span class="o">.</span><span class="n">strip</span><span class="p">(</span><span class="n">_EDGE_PUNCT</span><span class="p">)</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">tokens</span><span class="p">]</span> <span class="k">if</span> <span class="nb">all</span><span class="p">(</span> <span class="nb">len</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="o">>=</span> <span class="mi">3</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">b</span><span class="o">.</span><span class="n">isupper</span><span class="p">()</span> <span class="ow">and</span> <span class="n">_token_is_wordlike</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="k">for</span> <span class="n">b</span><span class="p">,</span> <span class="n">t</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">bares</span><span class="p">,</span> <span class="n">tokens</span><span class="p">,</span> <span class="n">strict</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> <span class="p">):</span> <span class="k">return</span> <span class="kc">False</span> <span class="k">if</span> <span class="n">vocab</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span> <span class="c1"># Prose made of rare words ("lumbago, sciatica, brachial and</span> <span class="c1"># trigeminal neuralgia") fails the recurrence test word by word, but</span> <span class="c1"># reads as clean type. Keep a line whose tokens are four-fifths or</span> <span class="c1"># more well-formed words, anchored by one word that does recur.</span> <span class="n">shapes</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="p">(</span><span class="n">_token_is_wordlike</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">tokens</span><span class="p">)</span> <span class="k">if</span> <span class="n">v</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">]</span> <span class="k">if</span> <span class="p">(</span> <span class="nb">len</span><span class="p">(</span><span class="n">shapes</span><span class="p">)</span> <span class="o">>=</span> <span class="mi">3</span> <span class="ow">and</span> <span class="nb">sum</span><span class="p">(</span><span class="n">shapes</span><span class="p">)</span> <span class="o">>=</span> <span class="mf">0.8</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">shapes</span><span class="p">)</span> <span class="ow">and</span> <span class="nb">any</span><span class="p">(</span><span class="n">t</span><span class="o">.</span><span class="n">strip</span><span class="p">(</span><span class="n">_EDGE_PUNCT</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="ow">in</span> <span class="n">vocab</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">tokens</span><span class="p">)</span> <span class="p">):</span> <span class="k">return</span> <span class="kc">False</span> <span class="k">if</span> <span class="nb">sum</span><span class="p">(</span><span class="n">verdicts</span><span class="p">)</span> <span class="o"><</span> <span class="mf">0.5</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">verdicts</span><span class="p">):</span> <span class="k">return</span> <span class="kc">True</span> <span class="c1"># A short line needs one real word: "7 ke } A -: AM \\ 7" passes the</span> <span class="c1"># ratio on its digits alone. A formula ("E = IR") is exempt.</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">body</span><span class="p">)</span> <span class="o"><</span> <span class="mi">25</span> <span class="ow">and</span> <span class="s2">"="</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">stripped</span><span class="p">:</span> <span class="k">return</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span> <span class="n">_token_is_wordlike</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">vocab</span><span class="p">)</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">t</span><span class="o">.</span><span class="n">strip</span><span class="p">(</span><span class="n">_EDGE_PUNCT</span><span class="p">))</span> <span class="o">>=</span> <span class="mi">3</span> <span class="ow">and</span> <span class="n">re</span><span class="o">.</span><span class="n">fullmatch</span><span class="p">(</span><span class="sa">r</span><span class="s2">"[A-Za-z]+(?:-[A-Za-z]+)*"</span><span class="p">,</span> <span class="n">t</span><span class="o">.</span><span class="n">strip</span><span class="p">(</span><span class="n">_EDGE_PUNCT</span><span class="p">))</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">_TOKEN_SPLIT_RE</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">stripped</span><span class="p">)</span> <span class="k">if</span> <span class="n">t</span> <span class="p">)</span> <span class="k">return</span> <span class="kc">False</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.ia.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.ia.parse_catalog" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Parse an IA catalog file. Each line: <code><identifier>[\t<title>]</code>.</p> <p>The same shape as a Gutenberg catalog, with a string identifier where that one has a numeric ID. Genres are either Gutenberg or IA and never both, so which parser reads a given <code>scripts/catalogs/<genre>.txt</code> is unambiguous.</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_path</code> </td> <td> <code><span title="str">str</span> | <span title="pathlib.Path">Path</span></code> </td> <td> <div class="doc-md-description"> <p>Path to the catalog file.</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="list">list</span>[<span title="tuple">tuple</span>[<span title="str">str</span>, <span title="str">str</span> | None]]</code> </td> <td> <div class="doc-md-description"> <p><code>(identifier, title_override_or_None)</code> per entry.</p> </div> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></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></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="o">|</span> <span class="n">Path</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">str</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 an IA catalog file. Each line: ``<identifier>[\\t<title>]``.</span> <span class="sd"> The same shape as a Gutenberg catalog, with a string identifier where that</span> <span class="sd"> one has a numeric ID. Genres are either Gutenberg or IA and never both, so</span> <span class="sd"> which parser reads a given ``scripts/catalogs/<genre>.txt`` is unambiguous.</span> <span class="sd"> :param catalog_path: Path to the catalog file.</span> <span class="sd"> :returns: ``(identifier, title_override_or_None)`` per entry.</span> <span class="sd"> """</span> <span class="n">entries</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">tuple</span><span class="p">[</span><span class="nb">str</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="o">=</span> <span class="p">[]</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">Path</span><span class="p">(</span><span class="n">catalog_path</span><span class="p">)</span><span class="o">.</span><span class="n">read_text</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="o">.</span><span class="n">splitlines</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="mi">1</span><span class="p">)</span> <span class="n">identifier</span> <span class="o">=</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="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">1</span> <span class="k">else</span> <span class="kc">None</span> <span class="k">if</span> <span class="n">identifier</span><span class="p">:</span> <span class="n">entries</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">identifier</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.ia.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">identifier</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.ia.record_in_catalog" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Record a downloaded IA item in its genre catalog, if not already there.</p> <p>The IA counterpart of :func:<code>gutenberg_kg.gutenberg.record_in_catalog</code>. IA genres had no catalogs at all, so <code>ia download</code> recorded nothing anywhere and every catalog-facing check had to exempt them.</p> <p>The title override matters more here than on the Gutenberg side: IA titles are uncurated and often unusable as directory names, so the override column is where the curation lives.</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> writes nothing.</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>identifier</code> </td> <td> <code><span title="str">str</span></code> </td> <td> <div class="doc-md-description"> <p>Internet Archive item identifier.</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 item'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>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>.</p> </div> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></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> <span class="normal">1150</span> <span class="normal">1151</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> <span class="normal">1158</span> <span class="normal">1159</span> <span class="normal">1160</span> <span class="normal">1161</span> <span class="normal">1162</span> <span class="normal">1163</span> <span class="normal">1164</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">identifier</span><span class="p">:</span> <span class="nb">str</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 IA item in its genre catalog, if not already there.</span> <span class="sd"> The IA counterpart of :func:`gutenberg_kg.gutenberg.record_in_catalog`. IA</span> <span class="sd"> genres had no catalogs at all, so ``ia download`` recorded nothing anywhere</span> <span class="sd"> and every catalog-facing check had to exempt them.</span> <span class="sd"> The title override matters more here than on the Gutenberg side: IA titles</span> <span class="sd"> are uncurated and often unusable as directory names, so the override column</span> <span class="sd"> is where the curation lives.</span> <span class="sd"> :param genre: Genre whose catalog to write; ``None`` writes nothing.</span> <span class="sd"> :param identifier: Internet Archive item identifier.</span> <span class="sd"> :param title: The item's directory name, written as the title override.</span> <span class="sd"> :param dry_run: Report what would be written but write nothing.</span> <span class="sd"> :returns: True when a line was appended, or would have been under ``dry_run``.</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">CATALOG_ROOT</span> <span class="o">/</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="n">needs_newline</span> <span class="o">=</span> <span class="kc">False</span> <span class="k">if</span> <span class="n">catalog_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span> <span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">ident</span> <span class="o">==</span> <span class="n">identifier</span> <span class="k">for</span> <span class="n">ident</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="n">needs_newline</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">catalog_path</span><span class="o">.</span><span class="n">read_text</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="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">identifier</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">CATALOG_ROOT</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</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">identifier</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">identifier</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.ia.recurring_words" class="doc doc-heading"> <code class="highlight language-python"><span class="n">recurring_words</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">min_count</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span></code> <a href="#gutenberg_kg.ia.recurring_words" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Lowercased words that recur in the text's prose.</p> <p>OCR garbage ("Carmoog", "WZZLLLN", mirrored "UOTYPOSOUAIP") almost never repeats letter for letter, and real words, even technical ones, almost always do somewhere in a book. Only prose-like lines are counted (six or more tokens, nearly all well-formed, half of them lowercase words), because debris does repeat within debris: "eee", "sss" and "nnn" recur across a book's drawings, and so do capital pairs such as "SU" and "ES". It stands in for a dictionary, which would also reject the book's own jargon.</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>text</code> </td> <td> <code><span title="str">str</span></code> </td> <td> <div class="doc-md-description"> <p>The whole text.</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>min_count</code> </td> <td> <code><span title="int">int</span></code> </td> <td> <div class="doc-md-description"> <p>Occurrences in prose needed to count as a word.</p> </div> </td> <td> <code>2</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="frozenset">frozenset</span>[<span title="str">str</span>] | None</code> </td> <td> <div class="doc-md-description"> <p>The recurring words, of two or more letters, or None for a text with fewer than :data:<code>MIN_WORDS_FOR_VOCAB</code> words, where recurrence means nothing.</p> </div> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">391</span> <span class="normal">392</span> <span class="normal">393</span> <span class="normal">394</span> <span class="normal">395</span> <span class="normal">396</span> <span class="normal">397</span> <span class="normal">398</span> <span class="normal">399</span> <span class="normal">400</span> <span class="normal">401</span> <span class="normal">402</span> <span class="normal">403</span> <span class="normal">404</span> <span class="normal">405</span> <span class="normal">406</span> <span class="normal">407</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></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">recurring_words</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">min_count</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">2</span><span class="p">)</span> <span class="o">-></span> <span class="nb">frozenset</span><span class="p">[</span><span class="nb">str</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">"""Lowercased words that recur in the text's prose.</span> <span class="sd"> OCR garbage ("Carmoog", "WZZLLLN", mirrored "UOTYPOSOUAIP") almost never</span> <span class="sd"> repeats letter for letter, and real words, even technical ones, almost</span> <span class="sd"> always do somewhere in a book. Only prose-like lines are counted (six or</span> <span class="sd"> more tokens, nearly all well-formed, half of them lowercase words),</span> <span class="sd"> because debris does repeat within debris: "eee", "sss" and "nnn" recur</span> <span class="sd"> across a book's drawings, and so do capital pairs such as "SU" and "ES". It stands</span> <span class="sd"> in for a dictionary, which would also reject the book's own jargon.</span> <span class="sd"> :param text: The whole text.</span> <span class="sd"> :param min_count: Occurrences in prose needed to count as a word.</span> <span class="sd"> :returns: The recurring words, of two or more letters, or None for a text</span> <span class="sd"> with fewer than :data:`MIN_WORDS_FOR_VOCAB` words, where recurrence</span> <span class="sd"> means nothing.</span> <span class="sd"> """</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="sa">r</span><span class="s2">"[A-Za-z]{3,}"</span><span class="p">,</span> <span class="n">text</span><span class="p">))</span> <span class="o"><</span> <span class="n">MIN_WORDS_FOR_VOCAB</span><span class="p">:</span> <span class="k">return</span> <span class="kc">None</span> <span class="n">counts</span><span class="p">:</span> <span class="n">Counter</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">Counter</span><span class="p">()</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</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">tokens</span> <span class="o">=</span> <span class="p">[</span><span class="n">t</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">_TOKEN_SPLIT_RE</span><span class="o">.</span><span class="n">split</span><span class="p">(</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="n">t</span><span class="p">]</span> <span class="n">shapes</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="p">(</span><span class="n">_token_is_wordlike</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">tokens</span><span class="p">)</span> <span class="k">if</span> <span class="n">v</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">]</span> <span class="n">lower_words</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">tokens</span> <span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">fullmatch</span><span class="p">(</span><span class="sa">r</span><span class="s2">"[a-z]{3,}"</span><span class="p">,</span> <span class="n">t</span><span class="o">.</span><span class="n">strip</span><span class="p">(</span><span class="n">_EDGE_PUNCT</span><span class="p">)))</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">shapes</span><span class="p">)</span> <span class="o">>=</span> <span class="mi">6</span> <span class="ow">and</span> <span class="nb">sum</span><span class="p">(</span><span class="n">shapes</span><span class="p">)</span> <span class="o">>=</span> <span class="mf">0.8</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">shapes</span><span class="p">)</span> <span class="ow">and</span> <span class="n">lower_words</span> <span class="o">>=</span> <span class="nb">len</span><span class="p">(</span><span class="n">tokens</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">:</span> <span class="n">counts</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">w</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="k">for</span> <span class="n">w</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="sa">r</span><span class="s2">"[A-Za-z]{2,}"</span><span class="p">,</span> <span class="n">line</span><span class="p">))</span> <span class="k">return</span> <span class="nb">frozenset</span><span class="p">(</span><span class="n">w</span> <span class="k">for</span> <span class="n">w</span><span class="p">,</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">counts</span><span class="o">.</span><span class="n">items</span><span class="p">()</span> <span class="k">if</span> <span class="n">n</span> <span class="o">>=</span> <span class="n">min_count</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.ia.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.ia.run_catalog" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Download all items 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 (identifier [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 items; when omitted it is inferred from the catalog's filename stem, since <code>scripts/catalogs/<genre>.txt</code> is the layout every catalog already follows. Without inference an omitted <code>--genre</code> silently writes books to the corpus root, where no catalog covers them.</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> <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>0 on full success, 1 if any item failed.</p> </div> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">1194</span> <span class="normal">1195</span> <span class="normal">1196</span> <span class="normal">1197</span> <span class="normal">1198</span> <span class="normal">1199</span> <span class="normal">1200</span> <span class="normal">1201</span> <span class="normal">1202</span> <span class="normal">1203</span> <span class="normal">1204</span> <span class="normal">1205</span> <span class="normal">1206</span> <span class="normal">1207</span> <span class="normal">1208</span> <span class="normal">1209</span> <span class="normal">1210</span> <span class="normal">1211</span> <span class="normal">1212</span> <span class="normal">1213</span> <span class="normal">1214</span> <span class="normal">1215</span> <span class="normal">1216</span> <span class="normal">1217</span> <span class="normal">1218</span> <span class="normal">1219</span> <span class="normal">1220</span> <span class="normal">1221</span> <span class="normal">1222</span> <span class="normal">1223</span> <span class="normal">1224</span> <span class="normal">1225</span> <span class="normal">1226</span> <span class="normal">1227</span> <span class="normal">1228</span> <span class="normal">1229</span> <span class="normal">1230</span> <span class="normal">1231</span> <span class="normal">1232</span> <span class="normal">1233</span> <span class="normal">1234</span> <span class="normal">1235</span> <span class="normal">1236</span> <span class="normal">1237</span> <span class="normal">1238</span> <span class="normal">1239</span> <span class="normal">1240</span> <span class="normal">1241</span> <span class="normal">1242</span> <span class="normal">1243</span> <span class="normal">1244</span> <span class="normal">1245</span> <span class="normal">1246</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="nb">int</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Download all items from a catalog file.</span> <span class="sd"> :param catalog_file: Path to a tab-separated catalog file (identifier [title]).</span> <span class="sd"> :param genre: Genre subdirectory for all items; when omitted it is inferred</span> <span class="sd"> from the catalog's filename stem, since ``scripts/catalogs/<genre>.txt``</span> <span class="sd"> is the layout every catalog already follows. Without inference an</span> <span class="sd"> omitted ``--genre`` silently writes books to the corpus root, where no</span> <span class="sd"> catalog covers them.</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"> :return: 0 on full success, 1 if any item failed.</span> <span class="sd"> """</span> <span class="n">catalog_path</span> <span class="o">=</span> <span class="n">Path</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">catalog_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"ERROR: catalog not found: </span><span class="si">{</span><span class="n">catalog_path</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="k">return</span> <span class="mi">1</span> <span class="k">if</span> <span class="n">genre</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">catalog_path</span><span class="o">.</span><span class="n">stem</span> <span class="ow">in</span> <span class="n">ALL_GENRES</span><span class="p">:</span> <span class="n">genre</span> <span class="o">=</span> <span class="n">catalog_path</span><span class="o">.</span><span class="n">stem</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" Genre inferred from filename: </span><span class="si">{</span><span class="n">genre</span><span class="si">}</span><span class="s2">"</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_path</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 entries in catalog."</span><span class="p">)</span> <span class="k">return</span> <span class="mi">0</span> <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Processing </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"> item(s) from </span><span class="si">{</span><span class="n">catalog_path</span><span class="o">.</span><span class="n">name</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">failures</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">for</span> <span class="n">identifier</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="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"[</span><span class="si">{</span><span class="n">identifier</span><span class="si">}</span><span class="s2">]"</span><span class="p">)</span> <span class="n">result</span> <span class="o">=</span> <span class="n">download_book</span><span class="p">(</span> <span class="n">identifier</span><span class="o">=</span><span class="n">identifier</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="p">)</span> <span class="k">if</span> <span class="n">result</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> <span class="n">failures</span> <span class="o">+=</span> <span class="mi">1</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">dry_run</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="mf">1.5</span><span class="p">)</span> <span class="c1"># polite delay between IA requests</span> <span class="nb">print</span><span class="p">()</span> <span class="n">ok</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">entries</span><span class="p">)</span> <span class="o">-</span> <span class="n">failures</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">ok</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"> succeeded, </span><span class="si">{</span><span class="n">failures</span><span class="si">}</span><span class="s2"> failed."</span><span class="p">)</span> <span class="k">return</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">failures</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">else</span> <span class="mi">1</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.ia.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.ia.run_catalog_sync" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Record every downloaded IA item in its genre catalog.</p> <p>The IA half of <code>gutenkg catalog-sync</code>; see :func:<code>gutenberg_kg.gutenberg.run_catalog_sync</code>. Keyed on the identifier in each <code>reference.md</code>, so nothing is downloaded.</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; non-IA genres are ignored.</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/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">1167</span> <span class="normal">1168</span> <span class="normal">1169</span> <span class="normal">1170</span> <span class="normal">1171</span> <span class="normal">1172</span> <span class="normal">1173</span> <span class="normal">1174</span> <span class="normal">1175</span> <span class="normal">1176</span> <span class="normal">1177</span> <span class="normal">1178</span> <span class="normal">1179</span> <span class="normal">1180</span> <span class="normal">1181</span> <span class="normal">1182</span> <span class="normal">1183</span> <span class="normal">1184</span> <span class="normal">1185</span> <span class="normal">1186</span> <span class="normal">1187</span> <span class="normal">1188</span> <span class="normal">1189</span> <span class="normal">1190</span> <span class="normal">1191</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 IA item in its genre catalog.</span> <span class="sd"> The IA half of ``gutenkg catalog-sync``; see</span> <span class="sd"> :func:`gutenberg_kg.gutenberg.run_catalog_sync`. Keyed on the identifier in</span> <span class="sd"> each ``reference.md``, so nothing is downloaded.</span> <span class="sd"> :param genres: Genres to process; non-IA genres are ignored.</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="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">not</span> <span class="ow">in</span> <span class="n">ALL_GENRES</span><span class="p">:</span> <span class="c1"># ALL_GENRES is IA_GENRES in this module</span> <span class="k">continue</span> <span class="n">genre_dir</span> <span class="o">=</span> <span class="n">CORPUS_ROOT</span> <span class="o">/</span> <span class="n">genre</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">genre_dir</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span> <span class="k">continue</span> <span class="k">for</span> <span class="n">ref</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">genre_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">"*/reference.md"</span><span class="p">)):</span> <span class="n">ia_id</span> <span class="o">=</span> <span class="n">parse_reference</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">"ia_id"</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">ia_id</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="n">ia_id</span><span class="p">,</span> <span class="n">ref</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">name</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.ia.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.ia.run_survey" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Scan the IA corpus and print download/ingest status.</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; default scans all IA genres.</p> </div> </td> <td> <code>None</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>Always 0.</p> </div> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">1249</span> <span class="normal">1250</span> <span class="normal">1251</span> <span class="normal">1252</span> <span class="normal">1253</span> <span class="normal">1254</span> <span class="normal">1255</span> <span class="normal">1256</span> <span class="normal">1257</span> <span class="normal">1258</span> <span class="normal">1259</span> <span class="normal">1260</span> <span class="normal">1261</span> <span class="normal">1262</span> <span class="normal">1263</span> <span class="normal">1264</span> <span class="normal">1265</span> <span class="normal">1266</span> <span class="normal">1267</span> <span class="normal">1268</span> <span class="normal">1269</span> <span class="normal">1270</span> <span class="normal">1271</span> <span class="normal">1272</span> <span class="normal">1273</span> <span class="normal">1274</span> <span class="normal">1275</span> <span class="normal">1276</span> <span class="normal">1277</span> <span class="normal">1278</span> <span class="normal">1279</span> <span class="normal">1280</span> <span class="normal">1281</span> <span class="normal">1282</span> <span class="normal">1283</span> <span class="normal">1284</span> <span class="normal">1285</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="nb">int</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Scan the IA corpus and print download/ingest status.</span> <span class="sd"> :param genre: Optional genre filter; default scans all IA genres.</span> <span class="sd"> :return: Always 0.</span> <span class="sd"> """</span> <span class="n">genres</span> <span class="o">=</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">ALL_GENRES</span> <span class="n">total_md</span> <span class="o">=</span> <span class="n">total_ref</span> <span class="o">=</span> <span class="n">total_kg</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">for</span> <span class="n">g</span> <span class="ow">in</span> <span class="n">genres</span><span class="p">:</span> <span class="n">genre_dir</span> <span class="o">=</span> <span class="n">CORPUS_ROOT</span> <span class="o">/</span> <span class="n">g</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">genre_dir</span><span class="o">.</span><span class="n">is_dir</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">g</span><span class="si">}</span><span class="s2">: (no directory)"</span><span class="p">)</span> <span class="k">continue</span> <span class="n">book_dirs</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span> <span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">genre_dir</span><span class="o">.</span><span class="n">iterdir</span><span class="p">()</span> <span class="k">if</span> <span class="n">p</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">p</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">"</span><span class="se">\n</span><span class="s2">=== </span><span class="si">{</span><span class="n">g</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">book_dirs</span><span class="p">)</span><span class="si">}</span><span class="s2"> 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="si">{</span><span class="s1">'Book'</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">'MD'</span><span class="si">:</span><span class="s2">>3</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">'REF'</span><span class="si">:</span><span class="s2">>4</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">'KG'</span><span class="si">:</span><span class="s2">>4</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="si">{</span><span class="s1">'-'</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">62</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">for</span> <span class="n">bd</span> <span class="ow">in</span> <span class="n">book_dirs</span><span class="p">:</span> <span class="n">has_md</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">f</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">bd</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">"*.md"</span><span class="p">)</span> <span class="k">if</span> <span class="n">f</span><span class="o">.</span><span class="n">name</span> <span class="o">!=</span> <span class="s2">"reference.md"</span><span class="p">)</span> <span class="n">has_ref</span> <span class="o">=</span> <span class="p">(</span><span class="n">bd</span> <span class="o">/</span> <span class="s2">"reference.md"</span><span class="p">)</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="n">has_kg</span> <span class="o">=</span> <span class="p">(</span><span class="n">bd</span> <span class="o">/</span> <span class="s2">".dockg"</span> <span class="o">/</span> <span class="s2">"graph.sqlite"</span><span class="p">)</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="n">total_md</span> <span class="o">+=</span> <span class="nb">int</span><span class="p">(</span><span class="n">has_md</span><span class="p">)</span> <span class="n">total_ref</span> <span class="o">+=</span> <span class="nb">int</span><span class="p">(</span><span class="n">has_ref</span><span class="p">)</span> <span class="n">total_kg</span> <span class="o">+=</span> <span class="nb">int</span><span class="p">(</span><span class="n">has_kg</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">bd</span><span class="o">.</span><span class="n">name</span><span class="si">:</span><span class="s2"><50</span><span class="si">}</span><span class="s2">"</span> <span class="sa">f</span><span class="s2">" </span><span class="si">{</span><span class="s1">'✓'</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">has_md</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">>3</span><span class="si">}</span><span class="s2">"</span> <span class="sa">f</span><span class="s2">" </span><span class="si">{</span><span class="s1">'✓'</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">has_ref</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">>4</span><span class="si">}</span><span class="s2">"</span> <span class="sa">f</span><span class="s2">" </span><span class="si">{</span><span class="s1">'✓'</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">has_kg</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">>4</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="s2">Totals — md: </span><span class="si">{</span><span class="n">total_md</span><span class="si">}</span><span class="s2"> ref: </span><span class="si">{</span><span class="n">total_ref</span><span class="si">}</span><span class="s2"> kg: </span><span class="si">{</span><span class="n">total_kg</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">return</span> <span class="mi">0</span> </code></pre></div></td></tr></table></div> </details> </div> </div> <div class="doc doc-object doc-function"> <h3 id="gutenberg_kg.ia.search_ia" class="doc doc-heading"> <code class="highlight language-python"><span class="n">search_ia</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.ia.search_ia" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Search Internet Archive texts. Returns list of result dicts.</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></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> <span class="normal">1028</span> <span class="normal">1029</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></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">search_ia</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 Internet Archive texts. Returns list of result 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">urlencode</span><span class="p">(</span> <span class="p">{</span> <span class="s2">"q"</span><span class="p">:</span> <span class="sa">f</span><span class="s2">"(</span><span class="si">{</span><span class="n">query</span><span class="si">}</span><span class="s2">) AND mediatype:texts"</span><span class="p">,</span> <span class="s2">"output"</span><span class="p">:</span> <span class="s2">"json"</span><span class="p">,</span> <span class="s2">"rows"</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="s2">"fl[]"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"identifier"</span><span class="p">,</span> <span class="s2">"title"</span><span class="p">,</span> <span class="s2">"creator"</span><span class="p">,</span> <span class="s2">"date"</span><span class="p">,</span> <span class="s2">"publisher"</span><span class="p">],</span> <span class="s2">"sort[]"</span><span class="p">:</span> <span class="s2">"date desc"</span><span class="p">,</span> <span class="p">},</span> <span class="n">doseq</span><span class="o">=</span><span class="kc">True</span><span class="p">,</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">IA_SEARCH_URL</span><span class="si">}</span><span class="s2">?</span><span class="si">{</span><span class="n">params</span><span class="si">}</span><span class="s2">"</span> <span class="n">raw</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">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">raw</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">doc</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"response"</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"docs"</span><span class="p">,</span> <span class="p">[]):</span> <span class="n">creator</span> <span class="o">=</span> <span class="n">doc</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"creator"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">creator</span><span class="p">,</span> <span class="nb">list</span><span class="p">):</span> <span class="n">creator</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">creator</span><span class="p">)</span> <span class="n">date_str</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">doc</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"date"</span><span class="p">,</span> <span class="s2">""</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">"identifier"</span><span class="p">:</span> <span class="n">doc</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"identifier"</span><span class="p">,</span> <span class="s2">""</span><span class="p">),</span> <span class="s2">"title"</span><span class="p">:</span> <span class="n">doc</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">""</span><span class="p">),</span> <span class="s2">"author"</span><span class="p">:</span> <span class="n">creator</span><span class="o">.</span><span class="n">strip</span><span class="p">(),</span> <span class="s2">"date"</span><span class="p">:</span> <span class="n">date_str</span><span class="p">[:</span><span class="mi">4</span><span class="p">]</span> <span class="k">if</span> <span class="n">date_str</span> <span class="k">else</span> <span class="s2">""</span><span class="p">,</span> <span class="s2">"publisher"</span><span class="p">:</span> <span class="n">_coerce_str</span><span class="p">(</span><span class="n">doc</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"publisher"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)),</span> <span class="p">}</span> <span class="p">)</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.ia.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.ia.slugify" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Convert a title to a filesystem-friendly slug (underscores).</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></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></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 (underscores)."""</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><span class="o">.</span><span class="n">strip</span><span class="p">(</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.ia.text_notes" class="doc doc-heading"> <code class="highlight language-python"><span class="n">text_notes</span><span class="p">(</span><span class="n">meta</span><span class="p">)</span></code> <a href="#gutenberg_kg.ia.text_notes" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>The <code>## Text notes</code> section of an IA <code>reference.md</code>.</p> <p>Says where the text came from and what cleaning did to it, so a reader who meets a garbled line knows it is the scan's OCR and what was already taken out. Written only when <em>meta</em> carries the counts from :func:<code>clean_ocr</code>.</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>meta</code> </td> <td> <code><span title="dict">dict</span></code> </td> <td> <div class="doc-md-description"> <p>Item metadata, with <code>cleanup</code> counts and the <code>ocr</code> engine.</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="list">list</span>[<span title="str">str</span>]</code> </td> <td> <div class="doc-md-description"> <p>The section's lines, or none.</p> </div> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></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></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">text_notes</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">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span> <span class="w"> </span><span class="sd">"""The ``## Text notes`` section of an IA ``reference.md``.</span> <span class="sd"> Says where the text came from and what cleaning did to it, so a reader</span> <span class="sd"> who meets a garbled line knows it is the scan's OCR and what was already</span> <span class="sd"> taken out. Written only when *meta* carries the counts from</span> <span class="sd"> :func:`clean_ocr`.</span> <span class="sd"> :param meta: Item metadata, with ``cleanup`` counts and the ``ocr`` engine.</span> <span class="sd"> :returns: The section's lines, or none.</span> <span class="sd"> """</span> <span class="n">cleanup</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">"cleanup"</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">cleanup</span><span class="p">:</span> <span class="k">return</span> <span class="p">[]</span> <span class="n">engine</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">"ocr"</span><span class="p">)</span> <span class="ow">or</span> <span class="s2">"an unrecorded OCR engine"</span> <span class="k">return</span> <span class="p">[</span> <span class="s2">"## Text notes"</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"- **Source**: the Internet Archive's DjVu text, OCR by </span><span class="si">{</span><span class="n">engine</span><span class="si">}</span><span class="s2">. It has not been proofread."</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"- **Hyphenated words rejoined**: </span><span class="si">{</span><span class="n">cleanup</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'hyphens_joined'</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"- **Page numbers removed**: </span><span class="si">{</span><span class="n">cleanup</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'page_numbers'</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"- **Page headers removed**: </span><span class="si">{</span><span class="n">cleanup</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'page_headers'</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"- **Lines of OCR debris removed**: </span><span class="si">{</span><span class="n">cleanup</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'debris_lines'</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2">"</span> <span class="s2">" (drawings, rotated or mirrored captions and specks read as text)"</span><span class="p">,</span> <span class="sa">f</span><span class="s2">"- **Stray margin marks removed**: </span><span class="si">{</span><span class="n">cleanup</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'margin_marks'</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="s2">"- **Not repaired**: words the scan cuts off at the page edge, misread words,"</span> <span class="s2">" and tables, which OCR does not keep in columns. Figures are omitted;"</span> <span class="s2">" their captions are kept where OCR read them as text."</span><span class="p">,</span> <span class="s2">""</span><span class="p">,</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.ia.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> <span class="n">report</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span></code> <a href="#gutenberg_kg.ia.text_to_markdown" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Convert IA OCR text to structured Markdown.</p> <p>Pipeline: 1. Clean OCR artifacts (ligatures, hyphenation, running headers, index) 2. Add title / author / publisher front matter 3. Skip table-of-contents region 4. Detect headings (CHAPTER, PART, SECTION, ALL-CAPS, Ques.) 5. For CHAPTER headings, absorb an ALL-CAPS subtitle on the next line 6. Preserve paragraph structure; suppress extra blank lines</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>text</code> </td> <td> <code><span title="str">str</span></code> </td> <td> <div class="doc-md-description"> <p>DjVu text as the Archive serves it.</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>meta</code> </td> <td> <code><span title="dict">dict</span></code> </td> <td> <div class="doc-md-description"> <p>Item metadata, for the front matter.</p> </div> </td> <td> <em>required</em> </td> </tr> <tr class="doc-section-item"> <td> <code>report</code> </td> <td> <code><span title="dict">dict</span> | None</code> </td> <td> <div class="doc-md-description"> <p>Optional dict that receives :func:<code>clean_ocr</code>'s counts.</p> </div> </td> <td> <code>None</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>The Markdown.</p> </div> </td> </tr> </tbody> </table> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></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> <span class="normal">740</span> <span class="normal">741</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></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="n">report</span><span class="p">:</span> <span class="nb">dict</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="nb">str</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Convert IA OCR text to structured Markdown.</span> <span class="sd"> Pipeline:</span> <span class="sd"> 1. Clean OCR artifacts (ligatures, hyphenation, running headers, index)</span> <span class="sd"> 2. Add title / author / publisher front matter</span> <span class="sd"> 3. Skip table-of-contents region</span> <span class="sd"> 4. Detect headings (CHAPTER, PART, SECTION, ALL-CAPS, Ques.)</span> <span class="sd"> 5. For CHAPTER headings, absorb an ALL-CAPS subtitle on the next line</span> <span class="sd"> 6. Preserve paragraph structure; suppress extra blank lines</span> <span class="sd"> :param text: DjVu text as the Archive serves it.</span> <span class="sd"> :param meta: Item metadata, for the front matter.</span> <span class="sd"> :param report: Optional dict that receives :func:`clean_ocr`'s counts.</span> <span class="sd"> :returns: The Markdown.</span> <span class="sd"> """</span> <span class="n">text</span> <span class="o">=</span> <span class="n">clean_ocr</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">report</span><span class="o">=</span><span class="n">report</span><span class="p">)</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="n">toc_range</span> <span class="o">=</span> <span class="n">_find_toc_range</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span> <span class="c1"># --- Front matter ---</span> <span class="n">md</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="o">=</span> <span class="p">[]</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">""</span><span class="p">)</span> <span class="n">publisher</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">"publisher"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="n">pub_date</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">"date"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="n">series</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">"series"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="n">volume</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">"volume"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="n">md</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</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">author</span><span class="p">:</span> <span class="n">md</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</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">pub_line_parts</span> <span class="o">=</span> <span class="p">[</span><span class="n">publisher</span><span class="p">,</span> <span class="n">pub_date</span><span class="p">]</span> <span class="n">pub_line</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="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">pub_line_parts</span> <span class="k">if</span> <span class="n">p</span><span class="p">)</span> <span class="k">if</span> <span class="n">pub_line</span><span class="p">:</span> <span class="n">md</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">pub_line</span><span class="si">}</span><span class="s2">*"</span><span class="p">)</span> <span class="n">md</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">series</span><span class="p">:</span> <span class="n">series_line</span> <span class="o">=</span> <span class="n">series</span> <span class="k">if</span> <span class="n">volume</span><span class="p">:</span> <span class="n">series_line</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">", Vol. </span><span class="si">{</span><span class="n">volume</span><span class="si">}</span><span class="s2">"</span> <span class="n">md</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">series_line</span><span class="si">}</span><span class="s2">*"</span><span class="p">)</span> <span class="n">md</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</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</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span> <span class="c1"># --- Body conversion ---</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">0</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="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">stripped</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="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</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 residual figure markers</span> <span class="n">stripped</span> <span class="o">=</span> <span class="n">FIGURE_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</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"># Heading detection (only fire after a blank line)</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">and</span> <span class="n">prev_blank</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"># For CHAPTER headings: check if next non-blank line is an ALL-CAPS</span> <span class="c1"># subtitle (e.g. "CHAPTER I." followed by "DIRECT CURRENTS")</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="k">if</span> <span class="n">level</span> <span class="o">==</span> <span class="mi">2</span> <span class="ow">and</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">"^CHAPTER\b"</span><span class="p">,</span> <span class="n">stripped</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="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_s</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_s</span> <span class="o">=</span> <span class="n">FIGURE_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_s</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="p">(</span> <span class="n">next_s</span> <span class="ow">and</span> <span class="n">next_s</span> <span class="o">==</span> <span class="n">next_s</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">next_s</span><span class="p">)</span> <span class="o"><</span> <span class="mi">80</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_s</span><span class="p">)</span> <span class="ow">and</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"</span><span class="p">,</span> <span class="n">next_s</span><span class="p">)</span> <span class="p">):</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="n">next_s</span> <span class="n">j</span> <span class="o">+=</span> <span class="mi">1</span> <span class="c1"># consume subtitle line</span> <span class="k">else</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"># no subtitle, reset</span> <span class="k">else</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="n">md</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="n">md</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="n">j</span> <span class="k">continue</span> <span class="c1"># Normal text line</span> <span class="n">md</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">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</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.ia.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.ia.write_reference" class="headerlink" title="Permanent link">¶</a></h3> <div class="doc doc-contents "> <p>Write a reference.md sidecar with Internet Archive metadata.</p> <details class="mkdocstrings-source"> <summary>Source code in <code>src/gutenberg_kg/ia.py</code></summary> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></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> <span class="normal">839</span> <span class="normal">840</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> <span class="normal">858</span> <span class="normal">859</span> <span class="normal">860</span> <span class="normal">861</span> <span class="normal">862</span> <span class="normal">863</span> <span class="normal">864</span> <span class="normal">865</span> <span class="normal">866</span> <span class="normal">867</span> <span class="normal">868</span> <span class="normal">869</span> <span class="normal">870</span> <span class="normal">871</span> <span class="normal">872</span> <span class="normal">873</span> <span class="normal">874</span> <span class="normal">875</span> <span class="normal">876</span> <span class="normal">877</span> <span class="normal">878</span> <span class="normal">879</span> <span class="normal">880</span> <span class="normal">881</span> <span class="normal">882</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></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="n">Path</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="n">Path</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Write a reference.md sidecar with Internet Archive metadata."""</span> <span class="n">ref_path</span> <span class="o">=</span> <span class="n">book_dir</span> <span class="o">/</span> <span class="s2">"reference.md"</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">"- **Internet Archive 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">'identifier'</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">IA_DETAILS_URL</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">identifier</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="s1">'identifier'</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">"- **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'</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">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">""</span><span class="p">)</span> <span class="n">publisher</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">"publisher"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="n">pub_date</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">"date"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="n">edition</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">"edition"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="k">if</span> <span class="n">author</span> <span class="ow">or</span> <span class="n">publisher</span> <span class="ow">or</span> <span class="n">pub_date</span><span class="p">:</span> <span class="n">lines</span> <span class="o">+=</span> <span class="p">[</span><span class="s2">"## Publication"</span><span class="p">,</span> <span class="s2">""</span><span class="p">]</span> <span class="k">if</span> <span class="n">author</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">"- **Author**: </span><span class="si">{</span><span class="n">author</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">if</span> <span class="n">publisher</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">"- **Publisher**: </span><span class="si">{</span><span class="n">publisher</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">if</span> <span class="n">pub_date</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">"- **Date**: </span><span class="si">{</span><span class="n">pub_date</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">if</span> <span class="n">edition</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">"- **Edition**: </span><span class="si">{</span><span class="n">edition</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="n">series</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">"series"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="n">volume</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">"volume"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="k">if</span> <span class="n">series</span> <span class="ow">or</span> <span class="n">volume</span><span class="p">:</span> <span class="n">lines</span> <span class="o">+=</span> <span class="p">[</span><span class="s2">"## Series"</span><span class="p">,</span> <span class="s2">""</span><span class="p">]</span> <span class="k">if</span> <span class="n">series</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">"- **Series**: </span><span class="si">{</span><span class="n">series</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="k">if</span> <span class="n">volume</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">"- **Volume**: </span><span class="si">{</span><span class="n">volume</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="n">lang</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">"language"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="k">if</span> <span class="n">lang</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">lang</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="s2">""</span><span class="p">]</span> <span class="n">subjects</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">"subjects"</span><span class="p">,</span> <span class="p">[])</span> <span class="k">if</span> <span class="n">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">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="n">desc</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">"description"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span> <span class="k">if</span> <span class="n">desc</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">desc</span><span class="p">,</span> <span class="s2">""</span><span class="p">]</span> <span class="n">lines</span> <span class="o">+=</span> <span class="n">text_notes</span><span class="p">(</span><span class="n">meta</span><span class="p">)</span> <span class="n">ref_path</span><span class="o">.</span><span class="n">write_text</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="n">encoding</span><span class="o">=</span><span class="s2">"utf-8"</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>