go-post

command module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 23, 2023 License: MIT Imports: 1 Imported by: 0

README

GO-POST

Uma simples aplicacao para extrair comentarios de arquivos .go. Foi utilizado cobra para construir o CLI.

ASTs

Abstract Syntax Tree é o tipico tema que reramente aparece no nosso dia a dia com desenvolvimento mas que no dia que precisamos dela, vira um problema sem tamanho. Eu vou mostrar que nao tem motivo para ter medo e que talvez devesse ate dar um pouco mais de carinho para essas ferramentas. Podem te ajudar com tarefas que nem imaginava que poderia utilizar e resolver a sua vida na hora de programar diariamente.

Primeiramente, oque sao essas arvore? De onde vem? o que fazem? onde dormem? A ultima é facil: elas nunca dormem. Quando as outras, vamos ser breves pq eu imagino que o exemplo vai deixar tudo mais claro.

  • Oque?

    • AST nada mais é que uma forma de representar codigo de forma semantica, ou seja, ao inves de ver o codigo como uma string de caracteres. A arvore contem todo os elementos presentes no script: variaveis, funcoes, importacoes, valores, loops, etc. Ela tbm organiza esses elememtos de forma hierarquica: valor pertence a variavel, que pertence a funcao, que pertence a classe...
  • Onde?

    • AST sao uma ferramenta importante em qualquer linguagem, é utilizando essas arvores que os compiladres e interpretadores "leem" o seu codigo antes de traduzi-lo para a maquina.Toda linguagem possui pelo menos uma definicao(podem haver mais) de sua AST pois no fim é ela quem torna possivel a linguagem ser executada.
    • Nao podemos esquecer que ha mais etapas alem da AST para compilar um programa, o que vamos ver é que com essa ferramentas conseguimos "ler" o codigo da mesma forma que o compilar faz
  • Como?

    • A forma de lidar com essas estruturas é "caminhando" por elas, voce cria um visitante que ira passar por cada nó da arvore e fazer alguma operacao. Cada no contem informacoes sobre sua posicao no arquivo, conteudo, tipo, identificadores(nomes basicamente) e seus filhos, caso existam.
    • Um uso comum dessas estruturas são nos linters, quando criamos um plugin para alguma ferramenta dessas precisamos sempre comecar por um visitante que atravessa a arvore acusando infracoes as regras definidas por ele.

Demo

Esse é um daaqueles projetos que eu não sei se quem veio primeiro foi o problema ou a solução, mas se certa forma ele é os dois ao mesmo tempo. A ideiai é a seguinte: eu gosto de comentar o meu codigo, mas raramente uso eles para escrever documentacao ou artigos como esse. Se eu tivesse alguma ferramenta que pudesse extrair esses comentarios e codigos mais importantes para um arquivo .md, eu poderia facilmente adiciona-los ao restante do material. Isso que essa demo faz.

Esse artigo em si esta sendo escrito no codigo fonte desse programa, e se estiver lendo é por que o programa funciona. Mas não so isso, olhe esse struct:

type MDParser struct {
	cells        [][2]int            // Representa um bloco de texto com estrutura: (cellType, extIndex)
	txtSegments  []*ast.CommentGroup // Array de commentarios marcados com `POST`
	declSegments []ast.Decl          // Array de declaracoes marcados com `PIN`
	pins         []ast.Node          // Array com a localizacao dos marcadores `PIN`
	File         []byte              // Conteudo do arquivo alvo
}

O programa consegue extrair do codigo fonte utilizando apenas um comentario ˋ// PINˋ antes do bloco de codigo.Não é incrivel? Sugiro que abra o arquivo parser.go e veja que esta tudo la. Pode inclusive tentar voce mesmo:

ˋˋˋbash

go run main.go parse ./internal/parser.go

ˋˋˋ

O funcionamento por enquanto é bastante simples, a classe MDParser implementa apenas 3 métodos:

func (p *MDParser) parseComments(c []*ast.CommentGroup) error {
	for _, tk := range c {
		if strings.HasPrefix(tk.Text(), "PIN") {
			p.pins = append(p.pins, tk)
			p.cells = append(p.cells, [2]int{PIN, len(p.pins) - 1})
		} else if strings.HasPrefix(tk.Text(), "POST") {
			p.txtSegments = append(p.txtSegments, tk)
			p.cells = append(p.cells, [2]int{TEXT, len(p.txtSegments) - 1})
		}
	}

	return nil
}

Em parseComments os commentarios extraidos do arquivo sao classificados entre PIN e POST, em seguida adicionados aos arrays correspondentes

func (p *MDParser) parseDeclarations(decl []ast.Decl) error {
	for _, tk := range decl {
		for _, v := range p.pins {
			if v.End()+1 == tk.Pos() {
				p.declSegments = append(p.declSegments, tk)
			}
		}
	}

	return nil
}

Em parseDeclarations extraimos as declaracoes que foram marcadas com PIN que identificamos no metodo anterior.

func (p MDParser) Flush(title string) string {
	s := fmt.Sprintf("# %s\n\n", title)

	for _, cell := range p.cells {
		switch cell[0] {
		case PIN:
			s += fmt.Sprintf("```go\n%s```\n\n", string(p.File[p.pins[cell[1]].End():p.declSegments[cell[1]].End()]))
		case TEXT:
			s += strings.TrimPrefix(p.txtSegments[cell[1]].Text(), "POST\n") + "\n"
		}
	}
	return s
}

Por fim, o metodo Flush gera e retorna um arquivo Markdown com todo o conteudo extraido. Essa string pode ser salva em um novo arquivo, renderizado na tela, o que for necessario

E para amarrar tudo, existe um construtor que realiza todo o processo dado o endereco de um arquivo alvo:

func NewMDParserFromFile(targetFile string) (*MDParser, error) {
	// Criamos a AST do arquivo
	fs := token.NewFileSet()
	fTree, err := parser.ParseFile(fs, targetFile, nil, parser.ParseComments)
	if err != nil {
		return nil, err
	}

	// Extraimos o conteudo do arquivo
	buf, err := ioutil.ReadFile(targetFile)
	if err != nil {
		return nil, err
	}

	// Devolvemos um ponteiro para o objeto ja parseado
	p := &MDParser{File: buf}
	p.parseComments(fTree.Comments)
	p.parseDeclarations(fTree.Decls)
	return p, nil
}

Documentation

Overview

Copyright © 2023 JOEL SCHUTZ <[email protected]>

Directories

Path Synopsis
Init
Init
t
Init
Init

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL