28
28
import collections
29
29
import datetime
30
30
import importlib
31
+ import io
31
32
import itertools
32
33
import json
33
34
import operator
34
35
import os
35
36
import pathlib
36
37
import re
37
38
import shutil
39
+ from subprocess import (
40
+ PIPE ,
41
+ Popen ,
42
+ )
38
43
import sys
44
+ import tarfile
39
45
import time
40
46
import typing
41
47
@@ -81,11 +87,15 @@ def navbar_add_info(context):
81
87
``has_subitems`` that tells which one of them every element is. It
82
88
also adds a ``slug`` field to be used as a CSS id.
83
89
"""
90
+ ignore = context ["translations" ]["ignore" ]
84
91
for i , item in enumerate (context ["navbar" ]):
92
+ if item ["target" ] in ignore :
93
+ item ["target" ] = "/" + item ["target" ]
94
+
85
95
context ["navbar" ][i ] = dict (
86
96
item ,
87
97
has_subitems = isinstance (item ["target" ], list ),
88
- slug = ( item ["name" ].replace (" " , "-" ).lower () ),
98
+ slug = item ["name" ].replace (" " , "-" ).lower (),
89
99
)
90
100
return context
91
101
@@ -386,16 +396,22 @@ def get_callable(obj_as_str: str) -> object:
386
396
return obj
387
397
388
398
399
+ def get_config (config_fname : str ) -> dict :
400
+ with open (config_fname , encoding = "utf-8" ) as f :
401
+ context = yaml .safe_load (f )
402
+ return context
403
+
404
+
389
405
def get_context (config_fname : str , ** kwargs ):
390
406
"""
391
407
Load the config yaml as the base context, and enrich it with the
392
408
information added by the context preprocessors defined in the file.
393
409
"""
394
- with open (config_fname , encoding = "utf-8" ) as f :
395
- context = yaml .safe_load (f )
396
-
410
+ context = get_config (config_fname )
397
411
context ["source_path" ] = os .path .dirname (config_fname )
398
412
context .update (kwargs )
413
+ context ["languages" ] = context .get ("languages" , ["en" ])
414
+ context ["selected_language" ] = context .get ("language" , "en" )
399
415
400
416
preprocessors = (
401
417
get_callable (context_prep )
@@ -409,14 +425,27 @@ def get_context(config_fname: str, **kwargs):
409
425
return context
410
426
411
427
412
- def get_source_files (source_path : str ) -> typing .Generator [str , None , None ]:
428
+ def get_source_files (
429
+ source_path : str , language , languages
430
+ ) -> typing .Generator [str , None , None ]:
413
431
"""
414
432
Generate the list of files present in the source directory.
415
433
"""
434
+ paths = []
435
+ all_languages = languages [:]
436
+ all_languages .remove (language )
416
437
for root , dirs , fnames in os .walk (source_path ):
417
438
root_rel_path = os .path .relpath (root , source_path )
418
439
for fname in fnames :
419
- yield os .path .join (root_rel_path , fname )
440
+ path = os .path .join (root_rel_path , fname )
441
+ for language in all_languages :
442
+ if path .startswith (language + "/" ):
443
+ break
444
+ else :
445
+ paths .append (path )
446
+
447
+ for path in paths :
448
+ yield path
420
449
421
450
422
451
def extend_base_template (content : str , base_template : str ) -> str :
@@ -431,6 +460,51 @@ def extend_base_template(content: str, base_template: str) -> str:
431
460
return result
432
461
433
462
463
+ def download_and_extract_translations (url : str , dir_name : str ):
464
+ """
465
+ Download the translations from the GitHub repository.
466
+ """
467
+ response = requests .get (url )
468
+ if response .status_code == 200 :
469
+ doc = io .BytesIO (response .content )
470
+ with tarfile .open (None , "r:gz" , doc ) as tar :
471
+ tar .extractall (dir_name )
472
+ else :
473
+ raise Exception (f"Failed to download translations: { response .status_code } " )
474
+
475
+
476
+ def get_languages (source_path : str ):
477
+ """
478
+ Get the list of languages available in the translations directory.
479
+ """
480
+ languages_path = f"{ source_path } /pandas-translations-main/web/pandas/"
481
+ en_path = f"{ languages_path } /en/"
482
+ if os .path .exists (en_path ):
483
+ shutil .rmtree (en_path )
484
+
485
+ paths = os .listdir (languages_path )
486
+ return [path for path in paths if os .path .isdir (f"{ languages_path } /{ path } " )]
487
+
488
+
489
+ def copy_translations (source_path : str , target_path : str , languages : list [str ]):
490
+ """
491
+ Copy the translations to the appropriate directory.
492
+ """
493
+ languages_path = f"{ source_path } /pandas-translations-main/web/pandas/"
494
+ for lang in languages :
495
+ cmds = [
496
+ "rsync" ,
497
+ "-av" ,
498
+ "--delete" ,
499
+ f"{ languages_path } /{ lang } /" ,
500
+ f"{ target_path } /{ lang } /" ,
501
+ ]
502
+ p = Popen (cmds , stdout = PIPE , stderr = PIPE )
503
+ stdout , stderr = p .communicate ()
504
+ sys .stderr .write (stdout .decode ())
505
+ sys .stderr .write (stderr .decode ())
506
+
507
+
434
508
def main (
435
509
source_path : str ,
436
510
target_path : str ,
@@ -441,58 +515,99 @@ def main(
441
515
For ``.md`` and ``.html`` files, render them with the context
442
516
before copying them. ``.md`` files are transformed to HTML.
443
517
"""
444
- config_fname = os .path .join (source_path , "config.yml" )
518
+ base_folder = os .path .dirname (__file__ )
519
+ base_source_path = source_path
520
+ base_target_path = target_path
521
+ base_config_fname = os .path .join (source_path , "config.yml" )
445
522
446
- shutil . rmtree ( target_path , ignore_errors = True )
447
- os .makedirs ( target_path , exist_ok = True )
523
+ config = get_config ( base_config_fname )
524
+ translations_path = os .path . join ( base_folder , f" { config [ 'translations' ][ 'folder' ] } " )
448
525
449
- sys .stderr .write ("Generating context...\n " )
450
- context = get_context (config_fname , target_path = target_path )
451
- sys .stderr .write ("Context generated\n " )
526
+ sys .stderr .write ("Downloading and extracting translations...\n " )
527
+ download_and_extract_translations (config ["translations" ]["url" ], translations_path )
452
528
453
- templates_path = os .path .join (source_path , context ["main" ]["templates_path" ])
454
- jinja_env = jinja2 .Environment (loader = jinja2 .FileSystemLoader (templates_path ))
529
+ translated_languages = get_languages (translations_path )
530
+ default_language = config ["translations" ]["default_language" ]
531
+ languages = [default_language ] + translated_languages
455
532
456
- for fname in get_source_files (source_path ):
457
- if os .path .normpath (fname ) in context ["main" ]["ignore" ]:
458
- continue
533
+ sys .stderr .write ("Copying translations...\n " )
534
+ copy_translations (translations_path , source_path , translated_languages )
535
+
536
+ for language in languages :
537
+ sys .stderr .write (f"\n Processing language: { language } ...\n \n " )
538
+ if language != default_language :
539
+ target_path = os .path .join (base_target_path , language )
540
+ source_path = os .path .join (base_source_path , language )
541
+
542
+ shutil .rmtree (target_path , ignore_errors = True )
543
+ os .makedirs (target_path , exist_ok = True )
544
+
545
+ config_fname = os .path .join (source_path , "config.yml" )
546
+ sys .stderr .write ("Generating context...\n " )
547
+
548
+ context = get_context (
549
+ config_fname ,
550
+ target_path = target_path ,
551
+ language = language ,
552
+ languages = languages ,
553
+ )
554
+ sys .stderr .write ("Context generated\n " )
459
555
460
- sys .stderr .write (f"Processing { fname } \n " )
461
- dirname = os .path .dirname (fname )
462
- os .makedirs (os .path .join (target_path , dirname ), exist_ok = True )
556
+ templates_path = os .path .join (source_path , context ["main" ]["templates_path" ])
557
+ jinja_env = jinja2 .Environment (loader = jinja2 .FileSystemLoader (templates_path ))
463
558
464
- extension = os .path .splitext (fname )[- 1 ]
465
- if extension in (".html" , ".md" ):
466
- with open (os .path .join (source_path , fname ), encoding = "utf-8" ) as f :
467
- content = f .read ()
468
- if extension == ".md" :
469
- body = markdown .markdown (
470
- content , extensions = context ["main" ]["markdown_extensions" ]
559
+ for fname in get_source_files (source_path , language , languages ):
560
+ if os .path .normpath (fname ) in context ["main" ]["ignore" ]:
561
+ continue
562
+
563
+ sys .stderr .write (f"Processing { fname } \n " )
564
+ dirname = os .path .dirname (fname )
565
+ os .makedirs (os .path .join (target_path , dirname ), exist_ok = True )
566
+
567
+ extension = os .path .splitext (fname )[- 1 ]
568
+ if extension in (".html" , ".md" ):
569
+ with open (os .path .join (source_path , fname ), encoding = "utf-8" ) as f :
570
+ content = f .read ()
571
+ if extension == ".md" :
572
+ body = markdown .markdown (
573
+ content , extensions = context ["main" ]["markdown_extensions" ]
574
+ )
575
+ # Apply Bootstrap's table formatting manually
576
+ # Python-Markdown doesn't let us config table attributes by hand
577
+ body = body .replace (
578
+ "<table>" , '<table class="table table-bordered">'
579
+ )
580
+ content = extend_base_template (
581
+ body , context ["main" ]["base_template" ]
582
+ )
583
+ context ["base_url" ] = "" .join (
584
+ ["../" ] * os .path .normpath (fname ).count ("/" )
471
585
)
472
- # Apply Bootstrap's table formatting manually
473
- # Python-Markdown doesn't let us config table attributes by hand
474
- body = body .replace ("<table>" , '<table class="table table-bordered">' )
475
- content = extend_base_template (body , context ["main" ]["base_template" ])
476
- context ["base_url" ] = "" .join (["../" ] * os .path .normpath (fname ).count ("/" ))
477
- content = jinja_env .from_string (content ).render (** context )
478
- fname_html = os .path .splitext (fname )[0 ] + ".html"
479
- with open (
480
- os .path .join (target_path , fname_html ), "w" , encoding = "utf-8"
481
- ) as f :
482
- f .write (content )
483
- else :
484
- shutil .copy (
485
- os .path .join (source_path , fname ), os .path .join (target_path , dirname )
486
- )
586
+ content = jinja_env .from_string (content ).render (** context )
587
+ fname_html = os .path .splitext (fname )[0 ] + ".html"
588
+ with open (
589
+ os .path .join (target_path , fname_html ), "w" , encoding = "utf-8"
590
+ ) as f :
591
+ f .write (content )
592
+ else :
593
+ shutil .copy (
594
+ os .path .join (source_path , fname ), os .path .join (target_path , dirname )
595
+ )
596
+ return 0
487
597
488
598
489
599
if __name__ == "__main__" :
490
600
parser = argparse .ArgumentParser (description = "Documentation builder." )
601
+
602
+ # For each language, the output will be written in a subdirectory named
603
+ # after the language (default: build/, build/es, etc...)
491
604
parser .add_argument (
492
- "source_path " , help = "path to the source directory (must contain config.yml) "
605
+ "--target-path " , default = "build" , help = "directory where to write the output. "
493
606
)
607
+ # e.g. python pandas_web.py --source_path pandas/
494
608
parser .add_argument (
495
- "--target-path" , default = "build" , help = "directory where to write the output"
609
+ "source_path" ,
610
+ help = "path to the source directory (must contain each language folder)" ,
496
611
)
497
612
args = parser .parse_args ()
498
613
sys .exit (main (args .source_path , args .target_path ))
0 commit comments