From 76edfda6271d1c0126fa9962eb24433971949a5f Mon Sep 17 00:00:00 2001 From: Matthieu Boileau Date: Fri, 28 Jun 2019 11:51:31 +0200 Subject: [PATCH 1/7] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2a72931..f8a2be0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ content/ attachments/ .idea +venv/ __pycache__/ *.log spip_calcul.yml -- GitLab From eaee500091d50b34d75f997ac648b367966ff674 Mon Sep 17 00:00:00 2001 From: Matthieu Boileau Date: Wed, 2 Oct 2019 08:23:05 +0200 Subject: [PATCH 2/7] Some refactor --- spip2pelican.py | 374 +++++++++++++++++++++++------------------------- 1 file changed, 183 insertions(+), 191 deletions(-) diff --git a/spip2pelican.py b/spip2pelican.py index 518185b..3b29e2a 100755 --- a/spip2pelican.py +++ b/spip2pelican.py @@ -49,11 +49,125 @@ logger.addHandler(fh) logger.addHandler(ch) -class SpipToMarkdown: - """A class to convert a Spip article string into a markdown Pelican article string""" +class Article: + """A generic class for a single Spip article or rubrique to be converted into a Pelican article file""" - def __init__(self, website): + def __init__(self, spip_article, spip_type, website): + + self.type = spip_type self.website = website + id_tag = 'id_' + self.type + self.short_id = spip_article[id_tag] + self.id = f"{SHORTEN[self.type]}{self.short_id}" + self.title = spip_article['titre'] + self.popularity = float(spip_article.get('popularite', 0.)) + + self.rubrique = spip_article['id_rubrique'] + + rubrique_category = self.website.rubrique_to_category[self.rubrique] + if self.type == 'rubrique': + self.category = rubrique_category + else: + # type is article or breve + types = self.type + 's' + article_category = self.website.categories[types].get(self.short_id, 'spip_divers') + if article_category != 'spip_divers': + self.category = article_category + else: + self.category = self.website.categories[types].get(self.short_id, rubrique_category) + + if self.category == 'spip_divers': + logger.warning(f"Article belongs to spip_divers {self.id}: {self.title}") + + if self.category == 'skip': + self.skip_reason = SKIP_REASON["skip_rub"] + elif not spip_article['texte'] and not self.type == 'rubrique': + self.skip_reason = SKIP_REASON["empty"] + elif spip_article['statut'] != 'publie': + self.skip_reason = SKIP_REASON["unpub"] + else: + self.skip_reason = '' + + if not self.skip_reason: + + self.prefix = f"spip_{self.type}-{self.short_id}" + self.path = os.path.join(self.category, f"{self.prefix}.{self.website.ml_type}") + try: + self.date = spip_article['date'] + except KeyError: + self.date = spip_article['date_heure'] + + self.modified = spip_article.get('date_modif', self.date) + self.summary = spip_article.get('descriptif', None) + self.text = spip_article['texte'] + + try: + self.authors = self.website.author_index[self.id] + except KeyError: + self.authors = self.website.default_author + + self.tags = [self.type] + + + def get_header(self): + pass + + def convert(self, s): + pass + + def convert_title(self, title): + """strip to remove any line break at end of string""" + return self.convert(title).strip() + + def export_to_pelican(self): + """ + Content of a markdown article should look like: + title: My super title + date: 2010-12-03 10:20 + modified: 2010-12-05 19:30 + category: Python + tags: pelican, publishing + slug: my-super-post + authors: Alexis Metaireau, Conan Doyle + summary: Short version for index and feeds + + This is the content of my super blog post. + """ + + logger.info(f"{self.type}_{self.short_id}: {self.title}") + + if self.skip_reason: + logger.warning(f" WARNING: skipping because {self.skip_reason}") + else: + self.title = self.convert_title(self.title) + content = ftfy.fix_encoding(self.convert(self.text)) + markdown = self.get_header() + content + + export_path = os.path.join(CONTENTDIR, self.path) + with open(export_path, 'w') as f: + f.write(markdown) + logger.debug(f" --> {export_path}") + + return self.skip_reason + + +class ArticleMd(Article): + """A single Spip article or rubrique to be converted into Pelican""" + + def get_header(self): + """Return header in md format""" + header = f"""\ +title: {self.title} +date: {self.date} +modified: {self.modified} +category: {self.category} +tags: {self.tags} +slug: {self.prefix} +authors: {self.authors} +summary: {self.summary} + +""" + return header def convert(self, s): """Convert string from Spip format to Pelican markdown format""" @@ -297,11 +411,24 @@ class SpipToMarkdown: return re.sub(r"\[([^\]]*)->([^\]]+)\]", link_replace, s) -class SpipToRst(SpipToMarkdown): - """A class to export spip article format to a ReStructuredText Pelican article""" +class ArticleRst(Article): + """A single Spip article or rubrique to be converted into Pelican""" - def __init__(self, website): - self.website = website + def get_header(self): + """Return header in rst format""" + print(self.title) + header = f"""\ +:title: {self.title} +:date: {self.date} +:modified: {self.modified} +:category: {self.category} +:tags: {self.tags} +:slug: {self.prefix} +:authors: {self.authors} +:summary: {self.summary} + +""" + return header def convert(self, s): """Convert string from Spip format to Pelican markdown format""" @@ -330,6 +457,7 @@ class SpipToRst(SpipToMarkdown): SPIP: or md: [text](url) or ![](img) """ + def doc_rst(match): doc_type = match[1] doc_id = int(match[2]) @@ -406,7 +534,7 @@ class SpipToRst(SpipToMarkdown): if isinstance(li.contents[0], str): text = li.contents[0].replace('\n', '') li.replace_with(text) - + return soup.prettify(formatter=None) def remove_space(self, s): @@ -419,7 +547,7 @@ class SpipToRst(SpipToMarkdown): new = [] for l in s.split("\n"): if l.lstrip().startswith('-'): - new.append(l+'\n') + new.append(l + '\n') else: new.append(l.lstrip()) return '\n'.join(new) @@ -430,7 +558,7 @@ class SpipToRst(SpipToMarkdown): for html in soup.find_all('ul'): s = pypandoc.convert_text(html, 'rst', format='html', extra_args=['--wrap=preserve']) html.replace_with(s) - + for html in soup.find_all('a'): s = pypandoc.convert_text(html, 'rst', format='html', extra_args=['--wrap=preserve']) html.replace_with(s) @@ -463,6 +591,7 @@ class SpipToRst(SpipToMarkdown): SPIP: {{ ... }} md: **...** """ + def bold_rst(match): text = match[2].strip() return f'**{text}** ' @@ -480,6 +609,7 @@ class SpipToRst(SpipToMarkdown): SPIP: {...} md: *...* """ + def italic_rst(match): text = match[2].strip() return f'*{text}* ' @@ -497,8 +627,9 @@ class SpipToRst(SpipToMarkdown): SPIP: - or -# in 1rst level, -## for second level, etc. md: 1. with 4-space indents """ + def ordered_rst(match): - indent = ' '*4*(match[1].count('*')-1) + indent = ' ' * 4 * (match[1].count('*') - 1) return f'\n{indent}- {match[2]}\n' regex = re.compile(r'^\s*-\s*(\#*)(.*)') @@ -522,8 +653,9 @@ class SpipToRst(SpipToMarkdown): SPIP: - or -* in 1rst level, -** for second level, etc. md: - with 4-space indents """ + def unordered_rst(match): - indent = ' '*4*(match[1].count('*')-1) + indent = ' ' * 4 * (match[1].count('*') - 1) text = match[2].strip() return f'\n{indent}- {text}\n' @@ -540,9 +672,10 @@ class SpipToRst(SpipToMarkdown): SPIP: {{{...}}} md: ## ... """ + def header_rst(match): text = match[2].strip() - return text + '\n' + '='*len(text) + '\n' + return text + '\n' + '=' * len(text) + '\n' regex = re.compile(r'({{3})([^}]*)(}{3})') @@ -554,9 +687,10 @@ class SpipToRst(SpipToMarkdown): SPIP: {{{{{...}}}}} md: ### ... """ + def header_rst(match): text = match[2].strip() - return text + '\n' + '-'*len(text) + '\n' + return text + '\n' + '-' * len(text) + '\n' regex = re.compile(r'({{5})([^}]*)(}{5})') @@ -590,13 +724,13 @@ class SpipToRst(SpipToMarkdown): def link_rst(match): text = match[1] link = match[2].strip() - + if text == '' or text == link: return f'{link}' - + email = re.match('mailto:(.*)', link) if email: - return f'{email.group(1).strip()}' + return f'{email.group(1).strip()}' http_url = re.match(r'http', link) if http_url: @@ -617,180 +751,20 @@ class SpipToRst(SpipToMarkdown): link = nullify_url(art_id, text, link) return f'`{text} <{link}>`__' - link = os.path.join(self.website.attachments_prefix, link) return f'`{text} <{link}>`__' regex = re.compile(r'\[([^]]*)\s*-\s*>\s*([^]]*)\]') return regex.sub(link_rst, s) -class Article: - """A generic class for a single Spip article or rubrique to be converted into a Pelican article file""" - - def __init__(self, spip_article, spip_type, website): - - self.type = spip_type - self.website = website - id_tag = 'id_' + self.type - print(spip_article) - self.short_id = spip_article[id_tag] - self.id = f"{SHORTEN[self.type]}{self.short_id}" - self.title = spip_article['titre'] - - self.rubrique = spip_article['id_rubrique'] - - rubrique_category = self.website.rubrique_to_category[self.rubrique] - if self.type == 'rubrique': - self.category = rubrique_category - else: - # type is article or breve - types = self.type + 's' - article_category = self.website.categories[types].get(self.short_id, 'spip_divers') - if article_category != 'spip_divers': - self.category = article_category - else: - self.category = self.website.categories[types].get(self.short_id, rubrique_category) - - if self.category == 'spip_divers': - logger.warning(f"Article belongs to spip_divers {self.id}: {self.title}") - - if self.category == 'skip': - self.skip_reason = SKIP_REASON["skip_rub"] - elif not spip_article['texte'] and not self.type == 'rubrique': - self.skip_reason = SKIP_REASON["empty"] - elif spip_article['statut'] != 'publie': - self.skip_reason = SKIP_REASON["unpub"] - else: - self.skip_reason = '' - - if not self.skip_reason: - - self.prefix = f"spip_{self.type}-{self.short_id}" - self.path = os.path.join(self.category, f"{self.prefix}.{self.website.ml_type}") - try: - self.date = spip_article['date'] - except KeyError: - self.date = spip_article['date_heure'] - - self.modified = spip_article.get('date_modif', self.date) - self.summary = spip_article.get('descriptif', None) - self.text = spip_article['texte'] - - try: - self.authors = self.website.author_index[self.id] - except KeyError: - self.authors = self.website.default_author - - self.tags = [self.type] - - self.convert = self.get_converter() - - def get_header(self): - pass - - def get_converter(self): - pass - - def convert_title(self, title): - """Article title needs special conversion""" - pass - - def export_to_pelican(self): - """ - Content of a markdown article should look like: - title: My super title - date: 2010-12-03 10:20 - modified: 2010-12-05 19:30 - category: Python - tags: pelican, publishing - slug: my-super-post - authors: Alexis Metaireau, Conan Doyle - summary: Short version for index and feeds - - This is the content of my super blog post. - """ - - logger.info(f"{self.type}_{self.short_id}: {self.title}") - - if self.skip_reason: - logger.warning(f" WARNING: skipping because {self.skip_reason}") - else: - self.title = self.convert_title(self.title) - content = ftfy.fix_encoding(self.convert(self.text)) - markdown = self.get_header() + content - - export_path = os.path.join(CONTENTDIR, self.path) - with open(export_path, 'w') as f: - f.write(markdown) - logger.debug(f" --> {export_path}") - - return self.skip_reason - - -class ArticleMd(Article): - """A single Spip article or rubrique to be converted into Pelican""" - - def get_converter(self): - """Return a Markdown converter""" - s2ml = SpipToMarkdown(self.website) # Instanciate a spip -> markdown translator - return s2ml.convert - - def convert_title(self, title): - """strip to remove any linke break at end of string""" - return self.convert(title).strip() - - def get_header(self): - """Return header in md format""" - header = f"""\ -title: {self.title} -date: {self.date} -modified: {self.modified} -category: {self.category} -tags: {self.tags} -slug: {self.prefix} -authors: {self.authors} -summary: {self.summary} - -""" - return header - - -class ArticleRst(Article): - """A single Spip article or rubrique to be converted into Pelican""" - - def get_converter(self): - """Return a Markdown converter""" - s2ml = SpipToRst(self.website) # Instanciate a spip -> rst translator - return s2ml.convert - - def convert_title(self, title): - """Prevent line breaks when converting title""" - return self.convert(title).strip() - - def get_header(self): - """Return header in rst format""" - print(self.title) - #title = f"{self.title}\n{'#'*len(self.title)}\n\n" - header = f"""\ -:title: {self.title} -:date: {self.date} -:modified: {self.modified} -:category: {self.category} -:tags: {self.tags} -:slug: {self.prefix} -:authors: {self.authors} -:summary: {self.summary} - -""" - return header - class Website: """Define a website content from Spip data""" DEFAULT_AUTHOR = "Webmaster" ATTACHMENTS_PREFIX = "attachments/spip/" - SPIPDIR = "spip_yml" + WORK_DIR = "." + SPIP_DIR = "spip_yml" @staticmethod def _load_and_clean_yaml(filename): @@ -798,7 +772,7 @@ class Website: with open(filename, mode='r') as yml_file: return yaml.load(remove_null_date(strip_invalid(yml_file))) - def __init__(self, reset_output_dir=True, include_breves=False, ml_type='md'): + def __init__(self, reset_output_dir=True, include_breves=False, ml_type='md', config_file='config.yml'): self.reset_output_dir = reset_output_dir self.include_breves = include_breves @@ -815,21 +789,22 @@ class Website: self.rubrique_nodes = {} self.rubrique_tree = None - config_filename = "config.yml" - with open(config_filename, 'r') as ymlfile: + with open(config_file, 'r') as ymlfile: cfg = yaml.load(ymlfile) # Set attributes from config.yml file or use default values self.site_url = cfg['site_url'] self.default_author = cfg.get('default_author', self.DEFAULT_AUTHOR) - spip_file = cfg.get('spip_file', {}) - self.attachments_prefix = spip_file.get('attachments_prefix', self.ATTACHMENTS_PREFIX) - self.rubriques_filename = os.path.join(self.SPIPDIR, "spip_rubriques_clean.yml") - self.articles_filename = os.path.join(self.SPIPDIR, "spip_articles_clean.yml") - self.breves_filename = os.path.join(self.SPIPDIR, "spip_breves_clean.yml") - self.documents_filename = os.path.join(self.SPIPDIR, "spip_documents.yml") - self.authors_filename = os.path.join(self.SPIPDIR, "spip_auteurs_clean.yml") - self.authors_links_filename = os.path.join(self.SPIPDIR, "spip_auteurs_liens.yml") + self.work_dir = cfg.get('work_dir', self.WORK_DIR) + self.attachments_prefix = cfg.get('attachments_prefix', self.ATTACHMENTS_PREFIX) + self.spip_dir = os.path.join(self.work_dir, cfg.get('spip_dir', self.SPIP_DIR)) + + self.rubriques_filename = os.path.join(self.spip_dir, "spip_rubriques_clean.yml") + self.articles_filename = os.path.join(self.spip_dir, "spip_articles_clean.yml") + self.breves_filename = os.path.join(self.spip_dir, "spip_breves_clean.yml") + self.documents_filename = os.path.join(self.spip_dir, "spip_documents_clean.yml") + self.authors_filename = os.path.join(self.spip_dir, "spip_auteurs_clean.yml") + self.authors_links_filename = os.path.join(self.spip_dir, "spip_auteurs_liens_clean.yml") self.categories = {} @@ -946,6 +921,16 @@ class Website: if article.rubrique == id_rubrique and article.type == spip_type: print(f" - {article.short_id} # {article.title}") + def sort_articles(self): + """Print a list of given spip_type articles""" + logger.info(f"Spip articles sorted by popularity:") + selected_articles = [article for article in self.articles if not article.skip_reason] + popularities = [article.popularity for article in selected_articles] + sorted_articles = [article for _, article in reversed(sorted(zip(popularities, selected_articles), + key=lambda x: x[0]))] + for article in sorted_articles: + print(f"{article.path} {article.popularity:.5f}") + def _build_doc_index(self): """Build the index dictionary: {id_doc: file_path}""" @@ -1032,15 +1017,19 @@ def parse_cl_args(): parser.add_argument('-b', '--breves', metavar="id_rubrique", nargs=1, help="List Spip breves corresponding to given rubrique id") parser.add_argument('-c', '--convert', action='store_true', default=True, help="Convert to Pelican") + parser.add_argument('-cf', '--config', metavar="config", default='config.yml', type=str, + help="Configuration file") parser.add_argument('-ib', '--include_breves', action='store_true', help="Include Spip breves in conversion") parser.add_argument('-ml', '--markup', metavar="language", default='md', type=str, help="Set markup language (md or rst)") + parser.add_argument('-p', '--popularite', action='store_true', default=False, + help="Sort articles and rubriques according to popularity") return parser.parse_args() if __name__ == '__main__': args = parse_cl_args() - website = Website(include_breves=args.include_breves, ml_type=args.markup) + website = Website(include_breves=args.include_breves, ml_type=args.markup, config_file=args.config) website.read_spip() if args.rubriques: # Show only Spip rubrique structure @@ -1053,5 +1042,8 @@ if __name__ == '__main__': # Show a list of breves that belongs to id_rubrique id_rubrique = int(args.breves[0]) website.print_articles(id_rubrique, 'breve') + elif args.popularite: + # Show a list of articles sorted according to popularity + website.sort_articles() else: website.export_to_pelican() -- GitLab From 65668b49bc62acc29afbb766000324e429d04442 Mon Sep 17 00:00:00 2001 From: Matthieu Boileau Date: Wed, 2 Oct 2019 08:23:30 +0200 Subject: [PATCH 3/7] Start some tests --- .gitignore | 2 + requirements.txt | 1 + test/config.yml | 93 ++++++++++++++++ test/spip_yml_clean/spip_articles_clean.yml | 89 +++++++++++++++ test/spip_yml_clean/spip_auteurs_clean.yml | 42 ++++++++ .../spip_auteurs_liens_clean.yml | 10 ++ test/spip_yml_clean/spip_breves_clean.yml | 22 ++++ test/spip_yml_clean/spip_documents_clean.yml | 18 ++++ test/spip_yml_clean/spip_rubriques_clean.yml | 101 ++++++++++++++++++ test/test_article.py | 19 ++++ 10 files changed, 397 insertions(+) create mode 100644 test/config.yml create mode 100644 test/spip_yml_clean/spip_articles_clean.yml create mode 100644 test/spip_yml_clean/spip_auteurs_clean.yml create mode 100644 test/spip_yml_clean/spip_auteurs_liens_clean.yml create mode 100644 test/spip_yml_clean/spip_breves_clean.yml create mode 100644 test/spip_yml_clean/spip_documents_clean.yml create mode 100644 test/spip_yml_clean/spip_rubriques_clean.yml create mode 100644 test/test_article.py diff --git a/.gitignore b/.gitignore index f8a2be0..87ca2ef 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ attachments/ .idea venv/ __pycache__/ +.vscode/ +.pytest_cache/ *.log spip_calcul.yml spip_yml diff --git a/requirements.txt b/requirements.txt index da006b6..eb4cdf8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ colorlog ftfy pypandoc ruamel.yaml +pytest diff --git a/test/config.yml b/test/config.yml new file mode 100644 index 0000000..ea26a46 --- /dev/null +++ b/test/config.yml @@ -0,0 +1,93 @@ +site_url: http://calcul.math.cnrs.fr +work_dir: test +spip_dir: spip_yml_clean +default_author: Webmaster +attachments_prefix: attachments/spip/ +categories: + articles: + # These spip articles will fall into categories below + evt_sci: + - 9 # Mini-symposium "Calcul Scientifique pour la médecine" au congrès SMAI 2007 + - 7 # Session spéciale au CANUM 2004 + - 8 # Session spéciale au congrès SMAI 2005 + - 82 # 1ères journées du GDR Calcul : 9-10 novembre 2009 + - 219 # 10 ans du Groupe Calcul : "Histoire du Calcul" + - 252 # Journée problème de Poisson + - 177 # Journées du GDR Calcul : 5 et 6 juillet 2011 + - 287 # Journée Python et Data Science à Rennes + - 157 # Journées du GDR et du réseau Calcul, 9 et 10 novembre 2010 + - 135 # Journée GNR MOMAS / GDR Calcul, 5 mai 2010, Paris + - 258 # Méthode de Galerkin discontinue et ses applications + - 267 # Multirésolution adaptative pour la simulation de problème multi-échelles et parallélisme + - 294 # JCAD, Journées Calcul Données + - 233 # Ecole "optimisation" + - 240 # École Optimisation 2014 + - 274 # ANF "Boîtes à outil éléments finis open source" + - 291 # École Thématique GEOMDATA + - 295 # JCAD, Journées Calcul Données, du 24 au 26 octobre 2018, Lyon + - 76 # Mini-symposium "Méthodes Multirésolutions", SMAI 2009 + - 148 # Mini-symposium CANUM 2010 "Modélisation et calcul scientifique : les enjeux en génération d'images" + - 165 # Journée en l'honneur des soixante ans de Thierry Dumont + - 228 # SMAI 2013: session commune GDR-MASCOT NUM et GDR Calcul + - 268 # Mini symposium "Recherche reproductible" - CANUM 2016 + evt_tech: + - 2 # Licences et gestion de projets, 14 octobre 2004 + - 3 # Compilateurs, architectures et performances des codes, 24 mars 2005 + - 4 # C++ avancé, STL et librairies scientifiques, 2 décembre 2005 + - 5 # Python en calcul scientifique + - 6 # Journée Méso-centres, 13 février 2008 + - 74 # 2ème journée mésocentres, 24 septembre 2009 + - 263 # 8èmes journées méso-centre + - 130 # "Méso-centres : quels outils pour aujourd'hui et pour demain ?", 10 juin 2010 + - 149 # 3ème journée mésocentre, 21 septembre 2010 + - 181 # 4èmes journées mésocentres - 20/21 sept 2011 + - 214 # 5ème journées mésocentres + - 229 # 6èmes journées mésocentres + - 242 # 7èmes journées mésocentres + - 270 # 9èmes journées méso-centres + - 275 # Journée Runtime + - 285 # 10èmes journées méso-centres + - 292 # Journées GPGPU + - 293 # Campagnes de calcul reproductibles + - 10 # Réunion d'information et de concertation à propos des grilles de calcul + - 150 # Workshop "Masse de données : I/O, format de fichier, visualisation et archivage" + - 211 # Mini-symposium CANUM 2012 "Le GPU est-il l'avenir du calcul scientifique ?" + - 75 # Mini-symposium "Architectures émergentes pour le calcul", SMAI 2009 + - 239 # CANUM 2014 : "A nouvelles machines, nouveaux algorithmes" + skip: + - 78 # Config + - 182 # Notification d'inscription aux journées mésocentres 2011 + - 232 # Liste des inscrits - journée mésocentres 2013 + - 226 # Liste des inscrits - Journée du 9/04/2013 + - 78 # Notification d'inscription aux journées du GDR Calcul 2011 + - 85 # Participants - 2ème journée mésocentre - 24/09/2009 + - 158 # Liste des inscrits - Journée Mésocentres du 21 septembre 2010 + - 161 # Liste des inscrits : Journées du Groupe Calcul, 9 et 10 nov 2010 + - 249 # inscrits à la 7ème journée mésocentres + - 151 # Validation d'inscription + - 160 # Liste des inscrits : Workshop "Masse de données : I/O, format de fichier, visualisation et archivage", 13 janvier 2011 + - 138 # Confirmation d'inscription + rubriques: + # spip rubriques and spip articles that are not listed above will fall into categories below + evt_sci: + - 77 # Ecole Thématique du GDR Calcul, 2010 : "Méthodes multirésolution et méthodes de raffinement adaptatif de maillage" + - 93 # Ecole Thématique du GDR Calcul, 2011 : "Méthodes de décomposition de domaine : de la théorie à la pratique" + - 106 # Ecole thématique sur les méthodes multigrilles + - 107 # Ecole Thématique PRECIS 2017 [6] + - 88 # ANGD "Calcul parallèle et application aux plasmas froids" + - 98 # Ecole "précision et reproductibilité en calcul numérique" + - 90 # CEMRACS 2012 + evt_tech: + - 39 # 6. Formations / Ecoles + paysage: + - 6 # 3. Mésocentres + - 10 # 5. Formations + - 14 # 4. Autres moyens de calcul en France + groupe: 2 # 1. Présentation du Groupe Calcul + skip: + - 10 # Formations organisées par les méso-centres + - 38 # 2. Offres d'emploi + breves: + paysage: + - 1 + - 2 diff --git a/test/spip_yml_clean/spip_articles_clean.yml b/test/spip_yml_clean/spip_articles_clean.yml new file mode 100644 index 0000000..dd9fd65 --- /dev/null +++ b/test/spip_yml_clean/spip_articles_clean.yml @@ -0,0 +1,89 @@ +- + id_article: 3 + titre: 'Compilateurs, architectures et performances des codes, 24 mars 2005' + id_rubrique: 4 + descriptif: + texte: |- + Le {Groupe Calcul} et l'Unité de Service {CODICIEL} s'associent pour proposer une journée de formation destinée aux acteurs du calcul scientifique et portant sur la compréhension des outils utilisés pour le développement des codes de calcul, en particulier des compilateurs. + Cette journée est organisée avec le soutien de la [Fédération Lyonnaise de Calcul Haute Performance->http://flchp.univ-lyon1.fr/] + + {{{Programme}}} + + ---- + + -* {{10h00}} : {Accueil. Café.} + + ---- + + -* {{10h30}} : {{Alain Lichnewsky}}, Professeur, Laboratoire de Mathématique, Université Paris-Sud : {Le compilateur : un outil mystérieux du calcul scientifique.} + + -** {{Résumé}} Les calculateurs utilisés dans les grands problèmes du calcul scientifique ont atteint des degrés élevés de parallélisme. D'autre part les applications deviennent très sophistiquées, que ce soit en raison de leur algorithmique + interne ou en raison de la nécessité d'exposer un parallélisme de plus en plus massif. + Les langages de programmation offrent une aide importante à la mise en oeuvre de programmes. Nous essaierons dans cet exposé de donner une idée des concepts et des technologies sous-jacents, en particulier concernant la phase de + ``compilation'' destinée à obtenir du code machine. + Notre propos est surtout d'aider à la construction de grandes applications en facilitant la compréhension d'ensemble du processus de construction applicative. + + -** {{Plan}} + -*** Notions sur les langages de programmation + -*** Notions de sémantique + -*** Le processus de traduction + -*** Les représentations intermédiaires + -*** Traduction vers une représentation de haut niveau + -*** Transformations à haut niveau + -*** Traduction vers une représentation de bas niveau + -*** Génération de code + -*** Optimisation des ressources + -*** Ordonnancement + + -** [Support->Documents/Journees/mars2005/compilateur.pdf] + + ---- + + -* {{12h30}} : {Repas.} + + ---- + + -* {{14h00}} : {{François BODIN}}, Professeur IRISA, Université Rennes 1 : {"Code tuning" : Structures des programmes, architectures et performances.} + + -** {{Résumé}} L'accroissement de performance des microprocesseurs réside dans l'augmentation de la fréquence d'horloge des processeurs et dans l'utilisation du parallélisme entre instructions (les architectures superscalaires telles que le Pentium d'Intel, ...). Cependant, à mesure que la performance crête augmente la structure des codes influe de plus en plus sur leurs temps d'exécution. + Dans cette présentation nous abordons l'ensemble des notions nécessaires à la compréhension des performances des codes. D'un côté, nous rappelons les principaux mécanismes architecturaux influençant les performances des programmes (pipeline, parallélisme entre instructions, hiérarchie mémoire, etc.). De l'autre côté, nous montrons au travers de transformations de code l'impact de la structure des programmes sur les performances. + + -** [Support->Documents/Journees/mars2005/CodeTuning.pdf] + + ---- + + -* {{16h00}} : {Discussion, Débat, Questions aux intervenants} - {Autour du développement de logiciels pour le calcul scientifique.} + + ---- + + {{{Date et Lieu}}} + + + - {{Date}} : Le Jeudi 24 mars 2005 + - {{Lieu}} : Université Lyon 1, amphi du Bâtiment Dirac. + + + ps: + date: 2008-02-07 09:24:27 + statut: publie + id_secteur: 2 + popularite: 0.907296313756637 + lang: fr +- + id_article: 209 + titre: 'État des lieux de mésos-centres en 2012' + id_rubrique: 13 + descriptif: + texte: |- + Ce rapport a été écrit sur la base de la mise à jour des [données des mésocentres->rub7] par leurs responsables et d'un sondage effectué en 2012 auprès de ces mêmes responsables. + + {Rédacteurs} : {{Mark Asch, Emmanuel Chaljub, Romaric David }}. + + [Télécharger le rapport sur l'état des lieux des structures de type mésocentres + en France (Juin 2012)->doc73] + ps: + date: 2012-06-07 09:05:45 + statut: publie + id_secteur: 6 + popularite: 0.908745215988163 + lang: fr diff --git a/test/spip_yml_clean/spip_auteurs_clean.yml b/test/spip_yml_clean/spip_auteurs_clean.yml new file mode 100644 index 0000000..fd39ae4 --- /dev/null +++ b/test/spip_yml_clean/spip_auteurs_clean.yml @@ -0,0 +1,42 @@ +- + id_auteur: 1 + nom: Webmaster + bio: + email: Violaine.Louvet@univ-grenoble-alpes.fr + login: admin + pass: a05eb303e4ac23a8c63d3fc171e4e9ff495c3421ef838cb171cab1847bd4e16b + low_sec: TLxMFR88 + statut: 0minirezo + pgp: + htpass: $1$zbhiaL8G$dqrNtsMwdmy3oMTuarX9T0 + en_ligne: 2018-11-21 10:24:40 + imessage: + messagerie: + alea_actuel: 11240972905beab63fe6fc94.21530289 + alea_futur: 12170183715bf2b4cb0bb1d0.84015863 + prefs: a:6:{s:3:"cnx";s:5:"perma";s:7:"options";s:8:"avancees";s:7:"couleur";i:1;s:7:"display";i:2;s:18:"display_navigation";s:22:"navigation_avec_icones";s:14:"display_outils";s:3:"oui";} + cookie_oubli: + source: spip + lang: fr + webmestre: oui +- + id_auteur: 3 + nom: Romaric David + bio: + email: david@unistra.fr + login: rdavid + pass: 3d826af54cf9dc38e7d1d3f6ecedb16b7bb6ae552bd787eda50fad6bcec79fc1 + low_sec: pLqLiqWz + statut: 0minirezo + pgp: + htpass: $1$oL5GC6LZ$TwyjJW3IlIYce4FAmagoo/ + en_ligne: 2017-09-07 11:27:00 + imessage: oui + messagerie: + alea_actuel: 2186229525946e5f52d54a3.66798894 + alea_futur: 192218665059b110e4b4df61.98282562 + prefs: a:5:{s:3:"cnx";s:5:"perma";s:7:"couleur";i:1;s:7:"display";i:2;s:18:"display_navigation";s:22:"navigation_avec_icones";s:14:"display_outils";s:3:"oui";} + cookie_oubli: + source: spip + lang: + webmestre: non diff --git a/test/spip_yml_clean/spip_auteurs_liens_clean.yml b/test/spip_yml_clean/spip_auteurs_liens_clean.yml new file mode 100644 index 0000000..1fa5823 --- /dev/null +++ b/test/spip_yml_clean/spip_auteurs_liens_clean.yml @@ -0,0 +1,10 @@ +- + id_auteur: 1 + id_objet: 3 + objet: article + vu: non +- + id_auteur: 3 + id_objet: 209 + objet: article + vu: non diff --git a/test/spip_yml_clean/spip_breves_clean.yml b/test/spip_yml_clean/spip_breves_clean.yml new file mode 100644 index 0000000..6b01b5a --- /dev/null +++ b/test/spip_yml_clean/spip_breves_clean.yml @@ -0,0 +1,22 @@ +- + id_breve: 1 + date_heure: 2012-12-14 00:00:00 + titre: '1/2 journée inaugurale du réseau LyonCalcul' + texte: 'L’initiative LyonCalcul propose de profiter de la proximité géographique pour organiser un réseau de contacts et une structure légère d’animation scientifique et technologique.' + lien_titre: Site du réseau LyonCalcul + lien_url: http://lyoncalcul.univ-lyon1.fr + statut: publie + id_rubrique: 4 + lang: fr +- + id_breve: 2 + date_heure: 2012-12-03 00:00:00 + titre: 'École "Datacentres : exploiter sans gaspiller"' + texte: |- + Du 3 au 7 Décembre à Villard de Lans aura lieu l'école "Datacentres : exploiter sans gaspiller". Cette école est organisée par le Groupe de Services [Ecoinfo->http://www.ecoinfo.cnrs.fr/], en collaboration avec le Groupe Calcul. + + lien_titre: Site web d'ecoinfo + lien_url: http://www.ecoinfo.cnrs.fr/ + statut: publie + id_rubrique: 13 + lang: fr diff --git a/test/spip_yml_clean/spip_documents_clean.yml b/test/spip_yml_clean/spip_documents_clean.yml new file mode 100644 index 0000000..1b8cfba --- /dev/null +++ b/test/spip_yml_clean/spip_documents_clean.yml @@ -0,0 +1,18 @@ +- + id_document: 73 + id_vignette: 0 + titre: '' + date: 2012-06-07 09:04:19 + descriptif: + fichier: pdf/2012_rapport_meso-2.pdf + taille: 3394058 + largeur: 0 + hauteur: 0 + mode: document + distant: non + extension: pdf + statut: publie + date_publication: 1970-01-01 01:00:00 + brise: 0 + credits: + media: file diff --git a/test/spip_yml_clean/spip_rubriques_clean.yml b/test/spip_yml_clean/spip_rubriques_clean.yml new file mode 100644 index 0000000..7161f77 --- /dev/null +++ b/test/spip_yml_clean/spip_rubriques_clean.yml @@ -0,0 +1,101 @@ +- + id_rubrique: 4 + id_parent: 2 + titre: '5. Journées du Groupe Calcul' + descriptif: + texte: |- + Le Groupe Calcul organise des journées de formation et d'informations autour du développement d’applications scientifiques, et de l'environnement scientifique lié au calcul. + + - Le {27 novembre 2018} : [{Interopérabilité et pérennisation des données de la recherche : comment FAIR En pratique ?} -> art297] à Paris + + - Les {24, 25 et 26 octobre 2018} : [{JCAD, Journées Calcul Données } ->art294] à Lyon + + - Le {23 octobre 2018} : [{Comment réaliser des campagnes de calcul reproductibles ?} ->art293] à Lyon + + - Le {19 décembre 2017} : [Python et Data Science à Rennes->art287] + + - Les {11, 12 et 13 décembre 2017} : [Visualisation In Situ à Toulouse->art288] + + - Les {26 et 27 septembre 2017} : [10èmes journées mésocentres->art285] + + - Le {20 janvier 2017} : [journée Runtime->art275] + + - Les {23 et 24 novembre 2016}: [journées SUCCES->https://succes2016.sciencesconf.org/] + + - Les {11 et 12 octobre 2016} : [9èmes journées mésocentres->art270] + + - Le {30 mai 2016} : [Multirésolution adaptative pour la simulation de problème multi-échelles et parallélisme->art267] + + - Le 6 Octobre 2015 : [8ème journée mésocentres->art263] + + - Le {19 juin 2015}, [méthode de Galerkin discontinue et ses applications->art258] + + - Le {26 janvier 2015}, [journée problème de Poisson->art252] + + - Le {7 octobre 2014}, [7ème journée mésocentres->art242] + + - Les {13 et 14 Novembre 2013}, [journées SUCCES->http://succes2013.sciencesconf.org] + + - Le {19 Septembre 2013}, [6èmes journées mésocentres->229] + + - Le {9 avril 2013}, [10 ans du Groupe Calcul "Histoire du Calcul"->art219] + + - les {1,2,3 octobre 2012}, [5èmes journées mésocentres->art214], co-localisées avec les journées scientifiques France Grilles + + - les {20 et 21 septembre 2011}, [{{4ème journées mésocentres}}->art181] (également en [Webcast le 21/9/11->http://webcast.in2p3.fr/live/journee_mesocentres]) + + - les {5 et 6 juillet 2011}, [{{troisièmes journées annuelles du GDR Calcul}}->art177] + + - les {9 et 10 novembre 2010}, [{{deuxièmes journées annuelles du Groupe Calcul}}->art157] + + - le {21 septembre 2010}, [{{3ème journée mésocentres}}->art149] + + - le {10 juin 2010}, [{{Mésocentres : quels outils pour aujourd’hui et pour demain ?}}->art130] + + - le {5 mai 2010}, [{{Journée GNR MOMAS / GDR Calcul}}->art135] + + - les {9 et 10 novembre 2009}, [{{premières journées du GDR Calcul}}->art82] + + - le {24 septembre 2009}, [{{2ème journée mésocentres}}->art74] + + - le {17 mars 2009}, [{{journée sur l"Impact de l’informatique sur l’environnement"}}->http://www.eco-info.org/spip.php?article96] co-organisée avec le groupe de travail [EcoInfo->http://www.ecoinfo.cnrs.fr/] + + - le {13 février 2008}, [{{journée dédiée aux méso-centres de calcul}}->art6], co-organisée avec le réseau [ResInfo->http://www.resinfo.cnrs.fr/] + + - le {13 septembre 2007}, [JoSy : {{gestion des serveurs de calcul}}->http://www.resinfo.cnrs.fr/spip.php?article1], co-organisée avec le réseau [ResInfo->http://www.resinfo.cnrs.fr/] + + - le {14 décembre 2006}, [{{Python en calcul scientifique}}->art5] + + - le {2 décembre 2005}, [{{C++ avancé : design patterns, STL et librairies scientifiques}}->art4] + + - le {24 mars 2005}, [{{Compilateurs, architectures et performances des codes}}->art3] + + - le {14 octobre 2004}, [{{Licence et gestion de projet}}->art2] + + id_secteur: 2 + statut: publie + date: 2018-09-28 17:41:31 + lang: fr + statut_tmp: publie + date_tmp: 2011-06-16 12:38:45 + profondeur: 1 +- + id_rubrique: 13 + id_parent: 11 + titre: 'Rapports et documents' + descriptif: + texte: |- + Cette rubrique regroupe les rapports et documents en lien avec les méso-centres : + + - [L'état des lieux des mésocentres en France en avril 2014->art241] + - [L'état des lieux des mésocentres en France en juin 2012->art209] + - [L'état des lieux des mésocentres en France en février 2011->art178] + - [L'état des lieux des mésocentres en France en septembre 2009->art86]. + - [L'état des lieux des mésocentres en France en février 2008->art14]. + id_secteur: 6 + statut: publie + date: 2014-04-17 18:24:49 + lang: fr + statut_tmp: publie + date_tmp: 2012-01-23 22:37:00 + profondeur: 2 diff --git a/test/test_article.py b/test/test_article.py new file mode 100644 index 0000000..52dcb8f --- /dev/null +++ b/test/test_article.py @@ -0,0 +1,19 @@ +from spip2pelican import Website + + +def test_website(): + + Website(config_file="test/config.yml") + + +def test_read_spip(): + + website = Website(ml_type='md', include_breves=True, + config_file="test/config.yml") + website.read_spip() + + +def test_export(): + website = Website(ml_type='md', config_file="test/config.yml") + website.read_spip() + website.export_to_pelican() -- GitLab From dab2cba00f486a81a648f14cb0239e80d2ec7df1 Mon Sep 17 00:00:00 2001 From: Matthieu Boileau Date: Wed, 2 Oct 2019 08:48:00 +0200 Subject: [PATCH 4/7] pep8 + other cleaning --- spip2pelican.py | 203 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 136 insertions(+), 67 deletions(-) diff --git a/spip2pelican.py b/spip2pelican.py index 3b29e2a..91a5b5b 100755 --- a/spip2pelican.py +++ b/spip2pelican.py @@ -26,7 +26,10 @@ yaml = YAML(typ='safe') SKIP_REASON = {"skip_rub": "belonging to a skipped rubrique", "empty": "empty content", "unpub": "not published"} -SHORTEN = {'article': 'art', 'rubrique': 'rub', 'breve': 'brev', 'message': 'mess'} +SHORTEN = {'article': 'art', + 'rubrique': 'rub', + 'breve': 'brev', + 'message': 'mess'} CONTENTDIR = 'content' LOGFILE = 'spip2pelican.log' @@ -50,7 +53,10 @@ logger.addHandler(ch) class Article: - """A generic class for a single Spip article or rubrique to be converted into a Pelican article file""" + """ + A generic class for a single Spip article or rubrique to be converted + into a Pelican article file + """ def __init__(self, spip_article, spip_type, website): @@ -70,14 +76,17 @@ class Article: else: # type is article or breve types = self.type + 's' - article_category = self.website.categories[types].get(self.short_id, 'spip_divers') + article_category = self.website.categories[types].get( + self.short_id, 'spip_divers') if article_category != 'spip_divers': self.category = article_category else: - self.category = self.website.categories[types].get(self.short_id, rubrique_category) + self.category = self.website.categories[types].get( + self.short_id, rubrique_category) if self.category == 'spip_divers': - logger.warning(f"Article belongs to spip_divers {self.id}: {self.title}") + logger.warning(f"Article belongs to spip_divers {self.id}: " + f"{self.title}") if self.category == 'skip': self.skip_reason = SKIP_REASON["skip_rub"] @@ -91,7 +100,8 @@ class Article: if not self.skip_reason: self.prefix = f"spip_{self.type}-{self.short_id}" - self.path = os.path.join(self.category, f"{self.prefix}.{self.website.ml_type}") + self.path = os.path.join(self.category, + f"{self.prefix}.{self.website.ml_type}") try: self.date = spip_article['date'] except KeyError: @@ -108,7 +118,6 @@ class Article: self.tags = [self.type] - def get_header(self): pass @@ -197,7 +206,8 @@ summary: {self.summary} """A call back function to replace a Spip doc by a Pelican link""" doc_type = matchobj.group(1) doc_id = int(matchobj.group(2)) - url = os.path.join(self.website.attachments_prefix, "IMG", self.website.doc_index[doc_id]) + url = os.path.join(self.website.attachments_prefix, "IMG", + self.website.doc_index[doc_id]) docname = os.path.basename(url) if doc_type == 'doc': return f"[{docname}]({url})" @@ -210,7 +220,10 @@ summary: {self.summary} """Replace html href by the right Pelican (relative) URL""" def link_replace(matchobj): - """A call back function to replace a Spip absolute link by a relative link to Pelican file""" + """ + A call back function to replace a Spip absolute link + by a relative link to Pelican file + """ spip_type = matchobj.group(1) id_art = int(matchobj.group(2)) @@ -232,10 +245,12 @@ summary: {self.summary} new_url = re.sub(r"\A{}/spip.php\?(article|rubrique)([0-9]+)(.*)".format(self.website.site_url), link_replace, link_url) - new_url = re.sub(r"\A({}/|)(Documents/.*)".format(self.website.site_url), link_replace_doc, new_url) + new_url = re.sub(r"\A({}/|)(Documents/.*)".format( + self.website.site_url), link_replace_doc, new_url) link['href'] = new_url - return soup.prettify(formatter=None) # formatter=None to avoid ">" -> ">" conversion + # formatter=None to avoid ">" -> ">" conversion + return soup.prettify(formatter=None) def html_img(self, s): """Replace html img src by the right Pelican (relative) URL""" @@ -251,7 +266,8 @@ summary: {self.summary} new_src = re.sub(r"\ADocuments/.*", src_replace, img_src) img['src'] = new_src - return soup.prettify(formatter=None) # formatter=None to avoid ">" -> ">" conversion + # formatter=None to avoid ">" -> ">" conversion + return soup.prettify(formatter=None) @staticmethod def bold_spaces(s): @@ -382,25 +398,29 @@ summary: {self.summary} else: # [text->url] if re.match(r"\Ahttp", url) or re.match(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", url): - # [text->http://] or [text->https://] or [text->email@subdomain.domain] + # [text->http://] or [text->https://] or + # [text->email@subdomain.domain] new_url = url else: doc_url = re.match(r"\Adoc([0-9]+)", url) art_url = re.match(r"\A(art|rub|brev)([0-9]+)", url) if doc_url: # [text->doc#] - new_url = os.path.join(self.website.attachments_prefix, "IMG", + new_url = os.path.join(self.website.attachments_prefix, + "IMG", self.website.doc_index[int(doc_url.group(1))]) elif art_url: # [text->art#,rub#,brev#] art_id = art_url.group(0) try: - new_url = "{filename}/" + self.website.article_index[art_id] + new_url = "{filename}/" + \ + self.website.article_index[art_id] except KeyError: new_url = nullify_url(art_id, text, url) else: # [text->path_to_file] - new_url = os.path.join(self.website.attachments_prefix, url) + new_url = os.path.join(self.website.attachments_prefix, + url) if new_url: new_link = f"[{text}]({new_url})" @@ -416,7 +436,6 @@ class ArticleRst(Article): def get_header(self): """Return header in rst format""" - print(self.title) header = f"""\ :title: {self.title} :date: {self.date} @@ -461,8 +480,9 @@ class ArticleRst(Article): def doc_rst(match): doc_type = match[1] doc_id = int(match[2]) - url = os.path.join(self.website.attachments_prefix, "IMG", self.website.doc_index[doc_id]) - print(self.website.doc_index[doc_id]) + url = os.path.join(self.website.attachments_prefix, + "IMG", + self.website.doc_index[doc_id]) docname = os.path.basename(url) if doc_type == 'doc': return f'`{docname} <{url}>`__' @@ -476,7 +496,10 @@ class ArticleRst(Article): """Replace html href by the right Pelican (relative) URL""" def link_replace(matchobj): - """A call back function to replace a Spip absolute link by a relative link to Pelican file""" + """ + A call back function to replace a Spip absolute link + by a relative link to Pelican file + """ spip_type = matchobj.group(1) id_art = int(matchobj.group(2)) @@ -501,7 +524,8 @@ class ArticleRst(Article): new_url = re.sub(r"\A({}/|)(Documents/.*)".format(self.website.site_url), link_replace_doc, new_url) link['href'] = new_url - return soup.prettify(formatter=None) # formatter=None to avoid ">" -> ">" conversion + # formatter=None to avoid ">" -> ">" conversion + return soup.prettify(formatter=None) def html_img(self, s): """Replace html img src by the right Pelican (relative) URL""" @@ -517,13 +541,14 @@ class ArticleRst(Article): new_src = re.sub(r"\ADocuments/.*", src_replace, img_src) img['src'] = new_src - return soup.prettify(formatter=None) # formatter=None to avoid ">" -> ">" conversion + # formatter=None to avoid ">" -> ">" conversion + return soup.prettify(formatter=None) def fix_table(self, s): def remove_bad_char(match): return '| |' - regex = re.compile('\|(\^|<)\|') + regex = re.compile(r'\|(\^|<)\|') s = regex.sub(remove_bad_char, s) return re.sub(r'\|', '', s) @@ -556,11 +581,13 @@ class ArticleRst(Article): soup = BeautifulSoup(lines, 'html.parser') for html in soup.find_all('ul'): - s = pypandoc.convert_text(html, 'rst', format='html', extra_args=['--wrap=preserve']) + s = pypandoc.convert_text(html, 'rst', format='html', + extra_args=['--wrap=preserve']) html.replace_with(s) for html in soup.find_all('a'): - s = pypandoc.convert_text(html, 'rst', format='html', extra_args=['--wrap=preserve']) + s = pypandoc.convert_text(html, 'rst', format='html', + extra_args=['--wrap=preserve']) html.replace_with(s) return soup.prettify(formatter=None) @@ -659,7 +686,7 @@ class ArticleRst(Article): text = match[2].strip() return f'\n{indent}- {text}\n' - regex = re.compile('^\s*-\s*(\**)(.*)') + regex = re.compile(r'^\s*-\s*(\**)(.*)') new = [] for l in s.split("\n"): @@ -772,7 +799,8 @@ class Website: with open(filename, mode='r') as yml_file: return yaml.load(remove_null_date(strip_invalid(yml_file))) - def __init__(self, reset_output_dir=True, include_breves=False, ml_type='md', config_file='config.yml'): + def __init__(self, reset_output_dir=True, include_breves=False, + ml_type='md', config_file='config.yml'): self.reset_output_dir = reset_output_dir self.include_breves = include_breves @@ -796,15 +824,23 @@ class Website: self.site_url = cfg['site_url'] self.default_author = cfg.get('default_author', self.DEFAULT_AUTHOR) self.work_dir = cfg.get('work_dir', self.WORK_DIR) - self.attachments_prefix = cfg.get('attachments_prefix', self.ATTACHMENTS_PREFIX) - self.spip_dir = os.path.join(self.work_dir, cfg.get('spip_dir', self.SPIP_DIR)) - - self.rubriques_filename = os.path.join(self.spip_dir, "spip_rubriques_clean.yml") - self.articles_filename = os.path.join(self.spip_dir, "spip_articles_clean.yml") - self.breves_filename = os.path.join(self.spip_dir, "spip_breves_clean.yml") - self.documents_filename = os.path.join(self.spip_dir, "spip_documents_clean.yml") - self.authors_filename = os.path.join(self.spip_dir, "spip_auteurs_clean.yml") - self.authors_links_filename = os.path.join(self.spip_dir, "spip_auteurs_liens_clean.yml") + self.attachments_prefix = cfg.get('attachments_prefix', + self.ATTACHMENTS_PREFIX) + self.spip_dir = os.path.join(self.work_dir, + cfg.get('spip_dir', self.SPIP_DIR)) + + self.rubriques_filename = os.path.join(self.spip_dir, + "spip_rubriques_clean.yml") + self.articles_filename = os.path.join(self.spip_dir, + "spip_articles_clean.yml") + self.breves_filename = os.path.join(self.spip_dir, + "spip_breves_clean.yml") + self.documents_filename = os.path.join(self.spip_dir, + "spip_documents_clean.yml") + self.authors_filename = os.path.join(self.spip_dir, + "spip_auteurs_clean.yml") + self.authors_links_filename = os.path.join(self.spip_dir, + "spip_auteurs_liens_clean.yml") self.categories = {} @@ -815,16 +851,20 @@ class Website: cfg_categories = cfg['categories'][spip_type] for pelican_category, spip_id in cfg_categories.items(): if type(spip_id) == int: - # this pelican category corresponds to a single rubrique + # this pelican category corresponds + # to a single rubrique categories[spip_id] = pelican_category elif type(spip_id) == list: - # this pelican category corresponds to a list of rubriques + # this pelican category corresponds + # to a list of rubriques for rubrique in spip_id: categories[rubrique] = pelican_category else: - logger.critical(f"Error in {config_filename}: {pelican_category}: {spip_id}") + logger.critical(f"Error in {config_file}: " + f"{pelican_category}: {spip_id}") except KeyError: - logger.warning(f"No category description for {spip_type} in {config_filename}") + logger.warning(f"No category description for {spip_type} " + f"in {config_file}") finally: categories[-1] = "spip_divers" return categories @@ -866,9 +906,12 @@ class Website: with open(self.rubriques_filename, mode='r') as yml_file: rubriques = yaml.load(yml_file.read()) - self.parents = {rubrique['id_rubrique']: rubrique['id_parent'] for rubrique in rubriques} - self.labels = {rubrique['id_rubrique']: rubrique['titre'].strip() for rubrique in rubriques} - self.rubrique_to_category = {rubrique['id_rubrique']: get_category(rubrique['id_rubrique']) + self.parents = {rubrique['id_rubrique']: rubrique['id_parent'] + for rubrique in rubriques} + self.labels = {rubrique['id_rubrique']: rubrique['titre'].strip() + for rubrique in rubriques} + self.rubrique_to_category = {rubrique['id_rubrique']: + get_category(rubrique['id_rubrique']) for rubrique in rubriques} def print_rubrique_tree(self): @@ -879,13 +922,17 @@ class Website: return f"{node_id}: {self.labels[node_id]}" def insert_node(node_id): - """A recursive function to insert a node in the anytree.AnyNode tree""" + """ + A recursive function to insert a node in the anytree.AnyNode tree + """ node_name = get_label(node_id) if node_name not in self.rubrique_nodes: # Create node only if it does not exist if node_id == 0: # This the root node - self.rubrique_nodes[node_name] = anytree.AnyNode(name=node_name, id=node_id, count=0) + self.rubrique_nodes[node_name] = anytree.AnyNode(name=node_name, + id=node_id, + count=0) else: parent_id = self.parents[node_id] parent_name = get_label(parent_id) @@ -894,17 +941,23 @@ class Website: # insert parent if it does not exist insert_node(parent_id) parent = self.rubrique_nodes[parent_name] - self.rubrique_nodes[node_name] = anytree.AnyNode(name=node_name, id=node_id, count=0, parent=parent) + self.rubrique_nodes[node_name] = anytree.AnyNode(name=node_name, + id=node_id, + count=0, + parent=parent) self.labels[0] = "root" # Add root node label for node_id in self.parents: insert_node(node_id) - self.rubrique_tree = self.rubrique_nodes[get_label(0)] # rubrique_tree is the root node + # rubrique_tree is the root node + self.rubrique_tree = self.rubrique_nodes[get_label(0)] # Count the number of pages for each rubrique for article in self.articles: - rubrique_node = anytree.search.find_by_attr(self.rubrique_tree, article.rubrique, name="id") + rubrique_node = anytree.search.find_by_attr(self.rubrique_tree, + article.rubrique, + name="id") rubrique_node.count += 1 logger.debug("-------") @@ -912,24 +965,28 @@ class Website: logger.debug("id: title [number of pages]") # Render the AnyNode object like the bash "tree" command would do for pre, fill, rubrique_node in anytree.RenderTree(self.rubrique_nodes['0: root']): - print(f"{pre}{rubrique_node.name} [{rubrique_node.count}]") + logger.info(f"{pre}{rubrique_node.name} [{rubrique_node.count}]") def print_articles(self, id_rubrique, spip_type): """Print a list of given spip_type articles""" - logger.info(f"Spip {spip_type}s that belong to rubrique {id_rubrique}: {self.labels[id_rubrique]}") + logger.info(f"Spip {spip_type}s that belong to rubrique {id_rubrique}: " + f"{self.labels[id_rubrique]}") for article in self.articles: if article.rubrique == id_rubrique and article.type == spip_type: - print(f" - {article.short_id} # {article.title}") + logger.info(f" - {article.short_id} # {article.title}") def sort_articles(self): """Print a list of given spip_type articles""" logger.info(f"Spip articles sorted by popularity:") - selected_articles = [article for article in self.articles if not article.skip_reason] + selected_articles = [article for article in self.articles + if not article.skip_reason] popularities = [article.popularity for article in selected_articles] - sorted_articles = [article for _, article in reversed(sorted(zip(popularities, selected_articles), - key=lambda x: x[0]))] + sorted_articles = [article for _, article + in reversed(sorted(zip(popularities, + selected_articles), + key=lambda x: x[0]))] for article in sorted_articles: - print(f"{article.path} {article.popularity:.5f}") + logger.info(f"{article.path} {article.popularity:.5f}") def _build_doc_index(self): """Build the index dictionary: {id_doc: file_path}""" @@ -943,7 +1000,8 @@ class Website: # Load author file as a list authors = self._load_and_clean_yaml(self.authors_filename) - author_name_index = {author['id_auteur']: author['nom'] for author in authors} + author_name_index = {author['id_auteur']: author['nom'] + for author in authors} # Load article/author file as a list authors_links = self._load_and_clean_yaml(self.authors_links_filename) @@ -959,7 +1017,10 @@ class Website: """Instanciate a list of Article objects""" def add_articles(filename, spip_type): - """Add article or rubrique to website.articles and website.article_index""" + """ + Add article or rubrique to website.articles and + website.article_index + """ # Load original article file as a list with open(filename, mode='r') as yml_file: spip_articles = yaml.load(yml_file) @@ -1002,7 +1063,8 @@ class Website: logger.info(f" {len(processed)} processed articles:") logger.info(f" {processed.count('')} converted articles") for k in SKIP_REASON: - logger.warning(f" {processed.count(SKIP_REASON[k])} skipped articles because {SKIP_REASON[k]}") + logger.warning(f" {processed.count(SKIP_REASON[k])} skipped " + f"articles because {SKIP_REASON[k]}") logger.warning(f" {self.nullified_urls} nullified URLs") logger.info("-------") logger.debug(f"See {LOGFILE}") @@ -1010,26 +1072,33 @@ class Website: def parse_cl_args(): """Parse command line arguments""" - parser = argparse.ArgumentParser(description="Convert Spip website as YAML data to Pelican format") - parser.add_argument('-r', '--rubriques', action='store_true', help="Show Spip rubriques structure as a tree") + description = "Convert Spip website as YAML data to Pelican format" + parser = argparse.ArgumentParser(description=description) + parser.add_argument('-r', '--rubriques', action='store_true', + help="Show Spip rubriques structure as a tree") parser.add_argument('-a', '--articles', metavar="id_rubrique", nargs=1, help="List Spip articles corresponding to given rubrique id") parser.add_argument('-b', '--breves', metavar="id_rubrique", nargs=1, help="List Spip breves corresponding to given rubrique id") - parser.add_argument('-c', '--convert', action='store_true', default=True, help="Convert to Pelican") - parser.add_argument('-cf', '--config', metavar="config", default='config.yml', type=str, + parser.add_argument('-c', '--convert', action='store_true', default=True, + help="Convert to Pelican") + parser.add_argument('-cf', '--config', metavar="config", + default='config.yml', type=str, help="Configuration file") - parser.add_argument('-ib', '--include_breves', action='store_true', help="Include Spip breves in conversion") - parser.add_argument('-ml', '--markup', metavar="language", default='md', type=str, - help="Set markup language (md or rst)") - parser.add_argument('-p', '--popularite', action='store_true', default=False, + parser.add_argument('-ib', '--include_breves', action='store_true', + help="Include Spip breves in conversion") + parser.add_argument('-ml', '--markup', metavar="language", default='md', + type=str, help="Set markup language (md or rst)") + parser.add_argument('-p', '--popularite', action='store_true', + default=False, help="Sort articles and rubriques according to popularity") return parser.parse_args() if __name__ == '__main__': args = parse_cl_args() - website = Website(include_breves=args.include_breves, ml_type=args.markup, config_file=args.config) + website = Website(include_breves=args.include_breves, ml_type=args.markup, + config_file=args.config) website.read_spip() if args.rubriques: # Show only Spip rubrique structure -- GitLab From de64381bf72009fbed18b17ef3d9186aafac99b7 Mon Sep 17 00:00:00 2001 From: Matthieu Boileau Date: Wed, 2 Oct 2019 08:52:43 +0200 Subject: [PATCH 5/7] Setup CI --- .gitlab-ci.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..38f2eaf --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,31 @@ +# This file is a template, and might need editing before it works on your project. +# Official language image. Look for the different tagged releases at: +# https://hub.docker.com/r/library/python/tags/ +image: python:latest + +# Change pip's cache directory to be inside the project directory since we can +# only cache local items. +variables: + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" + +# Pip's cache doesn't store the python packages +# https://pip.pypa.io/en/stable/reference/pip_install/#caching +# +# If you want to also cache the installed packages, you have to install +# them in a virtualenv and cache it as well. +cache: + paths: + - .cache/pip + - venv/ + +before_script: + - python -V # Print out python version for debugging + - pip install virtualenv + - virtualenv venv + - source venv/bin/activate + - pip install -r requirements.txt + +test: + script: + - pytest -sv + -- GitLab From 6aab7cd9a695a7dcceb9046995fd413849f7b283 Mon Sep 17 00:00:00 2001 From: Matthieu Boileau Date: Wed, 2 Oct 2019 08:54:03 +0200 Subject: [PATCH 6/7] Up CI --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 38f2eaf..9b1b2d4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,6 +26,8 @@ before_script: - pip install -r requirements.txt test: + tags: + - docker script: - pytest -sv -- GitLab From 20488aacd483174a112510b3b99567055f03909e Mon Sep 17 00:00:00 2001 From: Matthieu Boileau Date: Wed, 2 Oct 2019 09:02:35 +0200 Subject: [PATCH 7/7] Fix CI --- .gitlab-ci.yml | 2 +- requirements.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9b1b2d4..0bdc207 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,5 +29,5 @@ test: tags: - docker script: - - pytest -sv + - python -m pytest -sv diff --git a/requirements.txt b/requirements.txt index eb4cdf8..e75f9a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ ftfy pypandoc ruamel.yaml pytest +fastcache \ No newline at end of file -- GitLab