vrmay23 commented on PR #20394:
URL: https://github.com/apache/nuttx/pull/20394#issuecomment-5894340472

   The checking script referred to in **Testing**. It is a one-off tool for this
   reorganisation — the list of pages with new text is embedded in it on 
purpose —
   so it is here rather than in the tree.
   
   Save it and run it from the repository root:
   
   ```
   python3 validate_reorg.py 53ac762e79
   ```
   
   It exits non-zero and names the page if any page outside the declared list
   differs in text once the move is factored out.
   
   ```python
   #!/usr/bin/env python3
   """Laudo de validacao do branch enhance/documentation (Apache NuttX).
   
       python3 validate_reorg.py [commit-base]        # default: ad176ac47f
   
   AFIRMACAO QUE ESTE SCRIPT VERIFICA
   ----------------------------------
   NAO e "so 49 arquivos mudaram".  O diff toca mais de mil arquivos.
   
   A afirmacao e mais estreita e mais forte:
   
       De todas as paginas de documentacao, apenas as declaradas abaixo tiveram
       PROSA escrita ou
       reescrita.  Em todas as outras, a mudanca esta confinada a reorganizacao:
       alvo de link, caminho, linha de tag, bloco de toctree e borda de tabela.
   
   COMO ELE VERIFICA
   -----------------
   Nao por amostragem, nem por regex sobre as linhas do diff.  Para cada pagina
   tocada que NAO esta na lista declarada, ele apaga do texto INTEIRO -- da 
versao
   antiga e da nova -- tudo que uma reorganizacao mexe, e exige que os dois
   resultados sejam byte a byte identicos.  Se sobrar uma unica palavra
   diferente, a pagina e reportada e o script sai com codigo 1.
   
   A lista esta embutida abaixo de proposito: o marca-texto amarelo que
   as destaca no site e andaime de revisao e sai antes do patch, entao o laudo
   nao pode depender dele para ser reproduzivel.
   """
   import subprocess, sys, re, collections, posixpath, pathlib, os
   
   # A base e o upstream contra o qual este trabalho se mede.  Depois de cada
   # merge com o upstream ela TEM de avancar para o novo pai upstream do merge,
   # senao o laudo passa a contar as mudancas do proprio upstream como se fossem
   # nossas -- e reprova.  Hoje: 53ac762e79, o pai upstream do merge 0cbcd255e7.
   BASE = sys.argv[1] if len(sys.argv) > 1 else "53ac762e79"
   
   # --- as paginas com prosa nova ou reescrita --------------------------------
   PROSE_PAGES = {
       "ReleaseNotes/index",
       "about/index",
       "components/index",
       "contributing/doc_templates/board",
       "developing/index",
       "guides/index",
       "implementation/index",
       "index",
       "os/arch/index",
       "os/concurrency/index",
       "os/drivers/character/bch",
       "os/drivers/character/efuse",
       "os/drivers/character/ipcc",
       "os/drivers/character/leds/index",
       "os/drivers/character/loop",
       "os/drivers/character/nullzero",
       "os/drivers/index",
       "os/drivers/special/clk",
       "os/drivers/special/devicetree",
       "os/drivers/special/pipes",
       "os/drivers/special/power/index",
       "os/drivers/special/rwbuffer",
       "os/drivers/special/syslog",
       "os/drivers/special/syslog_design",
       "os/drivers/special/usrsock",
       "os/filesystem/aio",
       "os/filesystem/index",
       "os/filesystem/nfs",
       "os/filesystem/romfs",
       "os/filesystem/userfs",
       "os/index",
       "os/interrupts/index",
       "os/ipc/index",
       "os/libs/index",
       "os/libs/libm",
       "os/memory/index",
       "os/memory/paging",
       "os/memory/shm",
       "os/networking/index",
       "os/openamp",
       "os/scheduling/index",
       "os/scheduling/nuttx_tasking",
       "os/scheduling/smp",
       "os/time/index",
       "os/video",
       "platforms/arm/lpc17xx_40xx/index",
       "os/libs/libbuiltin",
       "platforms/arm/lpc17xx_40xx/lpc40xx",
       "platforms/x86_64/intel64/index",
       "reference/index",}
   
   # --- paginas divididas em duas ---------------------------------------------
   # Um teste por pagina le uma divisao como prosa removida, porque o texto foi
   # parar noutro arquivo.  Entao a divisao sai do teste geral e e verificada a
   # parte: cada linha que saiu de uma metade tem de reaparecer, verbatim e na
   # mesma ordem, na outra.
   SPLITS = [
       ("Documentation/implementation/memory_configurations.rst",  # original 
na base
        "Documentation/os/memory/memory_configurations.rst",       # metade que 
ficou
        "Documentation/os/build_modes.rst"),                       # metade que 
saiu
   ]
   
   # --- helpers ---------------------------------------------------------------
   def sh(*a):
       return subprocess.run(a, capture_output=True, text=True).stdout
   
   def blob(rev, path):
       r = subprocess.run(["git", "show", f"{rev}:{path}"], 
capture_output=True, text=True)
       return r.stdout if r.returncode == 0 else None
   
   def rule(t=""):
       print(("-- " + t + " ").ljust(74, "-") if t else "-" * 74)
   
   # --- normalizacao: apaga tudo que uma reorganizacao mexe, e so isso --------
   TAGLINE   = re.compile(r"^\s*(\.\.\s+tags::|:tags:).*$")
   TOCTREE   = re.compile(r"^(\s*)\.\.\s+toctree::")
   TOCOPT    = 
re.compile(r"^\s*:(glob|maxdepth|titlesonly|caption|hidden|numbered|reversed):.*$")
   PATHDIR   = 
re.compile(r"^\s*\.\.\s+(image|figure|include|literalinclude)::.*$")
   BORDER    = re.compile(r"^\s*[+|][-=+| ]+[+|]\s*$")
   UNDERLINE = re.compile(r"^\s*([=\-~^\"#*+`'])\1{2,}\s*$")
   
   def normalise_rst(text):
       out, lines, i = [], text.splitlines(), 0
       while i < len(lines):
           l = lines[i]
           m = TOCTREE.match(l)
           if m:                                     # navegacao, nao texto
               ind = len(m.group(1)); i += 1
               while i < len(lines):
                   nxt = lines[i]
                   if nxt.strip() and (len(nxt) - len(nxt.lstrip())) <= ind: 
break
                   i += 1
               continue
           i += 1
           if TAGLINE.match(l) or TOCOPT.match(l):  continue
           if PATHDIR.match(l):   out.append("|PATHDIR|"); continue
           if BORDER.match(l):    out.append("|BORDER|");  continue
           if UNDERLINE.match(l): out.append("|RULE|");    continue
           l = re.sub(r":(doc|ref|any):`([^`<]*)<[^`>]*>`", r":\1:`\2<>`", l)
           l = re.sub(r":(doc|ref|any):`[^`]*`",            r":\1:`<>`",   l)
           l = re.sub(r"`([^`<]*)<[^`>]*>`_+",              r"`\1<>`_",    l)
           l = re.sub(r"https?://\S+",                      "URL",         l)
           l = re.sub(r"/?(?:[\w.@-]+/)+[\w.*@-]+",         "PATH",        l)
           l = re.sub(r"\s+", " ", l).strip()
           if l: out.append(l)
       return "\n".join(out)
   
   def normalise_md(text):
       """Release notes: apaga o que e estrutura de Markdown, deixa as palavras.
   
       A reorganizacao promoveu a primeira linha a titulo de documento 
(sublinhado
       setext) e, onde o arquivo ja usava '#' para secoes, empurrou todos os
       niveis um degrau abaixo para que a release aparecesse como UMA entrada no
       indice, e nao uma por secao.  Tambem escapou colchetes que o Markdown 
comia
       e fechou cercas de codigo.  Nada disso e palavra."""
       out = []
       for l in text.splitlines():
           if re.match(r"^\s*[=-]{3,}\s*$", l):  continue     # sublinhado 
setext
           if re.match(r"^\s*```", l):            continue     # cerca de codigo
           l = re.sub(r"^\s*#+\s*", "|H| ", l)                 # nivel de titulo
           l = l.replace("\\", "")                              # escapes de 
Markdown
           l = re.sub(r"\s+", " ", l).strip()
           if l: out.append(l)
       return "\n".join(out)
   
   # --- 1. o que o git mostra -------------------------------------------------
   head = sh("git", "rev-parse", "--short", "HEAD").strip()
   stat = sh("git", "-c", "diff.renameLimit=20000", "diff", "--find-renames",
             "--shortstat", BASE, "HEAD").strip()
   print()
   rule()
   print(f"  LAUDO  base={BASE}  head={head}")
   rule()
   print(f"\n  git diff --find-renames --stat {BASE} HEAD")
   print(f"    {stat}\n")
   
   status = sh("git", "-c", "diff.renameLimit=20000", "diff", "--find-renames",
               "--name-status", BASE, "HEAD")
   entries = []
   for line in status.splitlines():
       p = line.split("\t")
       entries.append((p[0], p[1], p[-1]))
   
   nat = collections.Counter()
   for st, a, b in entries:
       if st == "R100": nat["movido, conteudo identico"] += 1
       elif st == "A":  nat["adicionado"] += 1
       elif st == "D":  nat["removido"] += 1
       else:            nat["conteudo alterado"] += 1
   rule("1. OS ARQUIVOS DO DIFF, POR NATUREZA")
   for k, v in nat.most_common(): print(f"    {k:<32} {v:>5}")
   print(f"    {'TOTAL':<32} {sum(nat.values()):>5}")
   
   # --- 2. paginas .rst -------------------------------------------------------
   pair = {}
   for st, a, b in entries:
       if not b.endswith(".rst"): continue
       if st.startswith("R"): pair[b] = a
       elif st == "M":        pair[b] = a
   head_rst = [f for f in sh("git", "ls-files", "Documentation").split() if 
f.endswith(".rst")]
   
   SPLIT_HALVES = {h for _, a, b in SPLITS for h in (a, b)}
   
   cat, suspeitas = collections.Counter(), []
   for new in head_rst:
       d = new[len("Documentation/"):-4]
       if new in SPLIT_HALVES:
           cat["metade de uma pagina dividida (secao 3)"] += 1; continue
       if d in PROSE_PAGES:
           cat[f"prosa escrita ou reescrita (as {len(PROSE_PAGES)})"] += 1; 
continue
       old = pair.get(new)
       if old is None:
           cat["nunca tocada"] += 1; continue
       a, b = blob(BASE, old), blob("HEAD", new)
       if a == b:
           cat["nunca tocada" if old == new else "movida, byte a byte 
identica"] += 1; continue
       if normalise_rst(a) == normalise_rst(b):
           cat["alterada, so reorganizacao"] += 1
       else:
           cat["*** SOBROU PROSA ***"] += 1; suspeitas.append((d, old))
   # Paginas .rst NOVAS que nao estao declaradas.  Uma pagina adicionada nao tem
   # versao anterior, entao o teste de residuo acima nao a alcanca: se este 
script
   # apenas presumisse que ela nao tem prosa, bastaria esquecer de declarar uma
   # pagina para o laudo aprovar um texto que ninguem verificou.  Por isso cada
   # uma e TESTADA: sobrou linha de prosa depois de tirar titulo, diretiva, 
opcao
   # de diretiva e entrada de toctree, a pagina e prosa nao declarada, e o laudo
   # reprova.
   def tem_prosa(caminho):
       linhas = pathlib.Path(caminho).read_text(errors="replace").splitlines()
       resto, i = [], 0
       while i < len(linhas):
           ln = linhas[i]
           nxt = linhas[i + 1] if i + 1 < len(linhas) else ""
           if re.fullmatch(r"[=~^\"'*+#<>_-]{3,}", ln.strip()):
               i += 1; continue                       # sublinhado de titulo
           if ln.strip() and re.fullmatch(r"[=~^\"'*+#<>_-]{3,}", nxt.strip()):
               i += 2; continue                       # o titulo e seu 
sublinhado
           if ln.lstrip().startswith(".."):
               i += 1; continue                       # diretiva ou comentario
           if re.fullmatch(r"\s+:[\w-]+:.*", ln):
               i += 1; continue                       # opcao de diretiva
           if re.fullmatch(r"\s+\S+", ln) and " " not in ln.strip():
               i += 1; continue                       # entrada de toctree
           if ln.strip():
               resto.append(ln.strip())
           i += 1
       return resto
   
   novas = [f for st, a, f in entries
            if st == "A" and f.endswith(".rst")
            and f[len("Documentation/"):-4] not in PROSE_PAGES
            and f not in SPLIT_HALVES]
   sem_prosa = []
   for f in novas:
       prosa = tem_prosa(f) if os.path.exists(f) else []
       if prosa:
           cat["*** PROSA NAO DECLARADA ***"] += 1
           suspeitas.append((f[len("Documentation/"):-4],
                             f"pagina nova com {len(prosa)} linhas de prosa, "
                             f"fora da lista declarada"))
       else:
           sem_prosa.append(f)
   cat["nova, sem prosa (so toctree)"] += len(sem_prosa)
   for f in novas:
       if f in head_rst: cat["nunca tocada"] -= 1
   
   rule(f"2. PAGINAS .rst NO HEAD ({len(head_rst)})")
   for k, v in cat.most_common():
       if v: print(f"    {k:<38} {v:>5}")
   print(f"    {'TOTAL':<38} {sum(v for v in cat.values()):>5}")
   print(f"\n    removidas de vez (nao existem no HEAD): "
         f"{sum(1 for st,a,b in entries if st=='D' and b.endswith('.rst'))}")
   
   # --- 3. paginas divididas 
---------------------------------------------------
   import difflib
   rule(f"3. PAGINAS DIVIDIDAS EM DUAS ({len(SPLITS)})")
   split_fail = []
   for src_old, kept, moved in SPLITS:
       o = (blob(BASE, src_old) or "").splitlines()
       k = (blob("HEAD", kept) or "").splitlines()
       m = (blob("HEAD", moved) or "").splitlines()
       saiu = []
       for tag, i1, i2, j1, j2 in difflib.SequenceMatcher(None, o, k, 
autojunk=False).get_opcodes():
           if tag in ("delete", "replace"): saiu.extend(o[i1:i2])
       sm = difflib.SequenceMatcher(None, saiu, m, autojunk=False)
       verbatim = sum(i2 - i1 for tag, i1, i2, j1, j2 in sm.get_opcodes() if 
tag == "equal")
       perdidas = [l for tag, i1, i2, j1, j2 in sm.get_opcodes()
                   if tag in ("delete", "replace") for l in saiu[i1:i2]]
       novas = [l for tag, i1, i2, j1, j2 in sm.get_opcodes()
                if tag in ("insert", "replace") for l in m[j1:j2]]
       print(f"    {src_old[len('Documentation/'):]}  ({len(o)} linhas)")
       print(f"      -> {kept[len('Documentation/'):]}  ({len(k)} linhas)")
       print(f"      -> {moved[len('Documentation/'):]}  ({len(m)} linhas)")
       print(f"         linhas que sairam da primeira:            
{len(saiu):>4}")
       print(f"         reaparecem verbatim na segunda:           
{verbatim:>4}")
       print(f"         perdidas:                                 "
             f"{len([l for l in perdidas if l.strip()]):>4}"
             f"   (+ {len([l for l in perdidas if not l.strip()])} em branco)")
       print(f"         acrescentadas (titulo e rotulo da pagina): 
{len(novas):>3}")
       for l in novas: print(f"             + {l[:66]}")
       if [l for l in perdidas if l.strip()]:
           split_fail.append(moved)
   
   # --- 4. release notes (.md) ------------------------------------------------
   md = [(a, b) for st, a, b in entries if b.endswith(".md") and st != "R100"]
   md_ok, md_extra = 0, []
   for a, b in md:
       x, y = blob(BASE, a), blob("HEAD", b)
       if normalise_md(x) == normalise_md(y): md_ok += 1
       else: md_extra.append(b)
   rule(f"4. RELEASE NOTES .md ({len(md)})")
   print(f"    so a promocao do titulo (sublinhado setext)   {md_ok:>5}")
   print(f"    com alguma correcao alem disso                
{len(md_extra):>5}")
   for f in md_extra: print(f"        {f[len('Documentation/'):]}")
   
   # --- 5. arquivos que nao sao pagina ---------------------------------------
   PORQUE = {
    ".github/workflows/doc.yml":
      "roda check_doc_coverage.py no CI e observa boards/",
    "Documentation/Pipfile":
      "declara sphinx-reredirects",
    "Documentation/Pipfile.lock":
      "trava sphinx-reredirects; sem isto o 'pipenv sync' do CI falharia",
    "Documentation/_extensions/review_highlight.py":
      "ANDAIME DE REVISAO: pinta as declaradas de amarelo. Sai antes do patch",
    "Documentation/_extensions/tags_overview.py":
      "reagrupa o indice de tags em arch > chip > part",
    "Documentation/_static/custom.css":
      "estilo do indice de tags, prosa justificada, e as regras do andaime",
    "Documentation/_templates/layout.html":
      "conserta o logo sumido: o tema renomeou 'logo' para 'logo_url'",
    "Documentation/conf.py":
      "extensoes, redirects, ano do copyright derivado do build",
    "Documentation/platforms/chip-vendors.txt":
      "85 regras familia -> fabricante, usadas pelo CI",
    "Documentation/platforms/doc-coverage-ignore.txt":
      "38 boards sem pagina, debito rastreado",
    "Documentation/redirects.py":
      "514 redirects, caminhos calculados; preserva as URLs antigas",
    "tools/ci/check_doc_coverage.py":
      "verificacao nova: boards/ e Documentation/platforms/ tem de bater",
   }
   outros = [(st, b) for st, a, b in entries
             if not b.endswith((".rst", ".md")) and st != "R100"]
   rule(f"5. ARQUIVOS QUE NAO SAO PAGINA ({len(outros)})")
   for st, f in sorted(outros, key=lambda x: x[1]):
       porque = PORQUE.get(f, "diagrama SVG autoral" if f.endswith(".svg") else 
"")
       print(f"    {st:<3} {f}")
       if porque: print(f"        {porque}")
   
   # --- 6. veredito -----------------------------------------------------------
   print()
   rule()
   if split_fail:
       print("  REPROVADO: uma divisao perdeu texto:")
       for f in split_fail: print(f"                {f}")
       rule()
       sys.exit(1)
   if suspeitas:
       print(f"  REPROVADO: {len(suspeitas)} pagina(s) fora da lista declarada 
com texto alem")
       print("             de reorganizacao:")
       for d, o in suspeitas: print(f"                {d}")
       rule()
       sys.exit(1)
   print("  APROVADO  --  a afirmacao verificada e sobre CONTEUDO")
   print()
   print(f"  Fora das {len(PROSE_PAGES)} paginas declaradas, nenhuma palavra de 
prosa")
   print("  foi escrita, reescrita ou removida, com UMA excecao, nomeada aqui: 
o")
   print("  titulo da release note NuttX-0.2.9, que dizia 'NuttX-0.2.8'. Typo")
   print("  herdado do upstream, corrigido na Fase 1.")
   print()
   print("  O que mudou fora delas, e um leitor percebe:")
   print("    . para onde os links apontam (mesma pagina de destino, caminho 
novo)")
   print("    . quais tags uma pagina de board carrega")
   print("    . o nivel dos titulos nas release notes, para a release virar 
UMA")
   print("      entrada no indice em vez de uma por secao")
   print("    . a navegacao: onde cada pagina fica na arvore")
   print()
   print("  Isto NAO afirma que so 49 arquivos mudaram. O diff toca 1026, 
porque")
   print("  a reestruturacao foi grande. Afirma que o TEXTO mudou em 49 
paginas,")
   print("  e que elas estao nomeadas, uma a uma, na lista embutida neste 
script.")
   rule()
   ```
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to