Marcio Trindade

Brincando com Go parte 2

Este artigo é uma continuação do artigo anterior Brincando com Go parte 1 que se você ainda não viu recomendo que comece por ele. Nesta etapa vamos separar a nossa lista de emails em 3 slices e escrever estes em arquivos distintos.

Primeiro vamos adicionar a lógica pra separar os emails em 3 slices.

example.go
...
// lendo o arquivo inteiro e atribuindo o valor na variável lines,
// se um erro ocorrer ficará na variável err.
lines, err := reader.ReadAll()

// Loga o erro se o mesmo ocorrer
// e para a execução do programa.
if err != nil {
        log.Fatal("Error reading all lines: ", err)
}

// declarando as variaveis que serão utilizadas
// para fazer as separações dos emails
var headers []string
var validEmails [][]string
var invalidEmails [][]string
var duplicatedEmails [][]string

// vamos criar agora uma variável do tipo map, que
// é bem parecido com o hash do Ruby,para
// identificarmos os emails duplicados.
existEmails := make(map[string]bool)

// loop for pra interar em todos os emails
for i, line := range lines {
        // separando a primeira linha que é o
        // cabeçalho do CSV.
        if i == 0 {
                headers = line
                continue
        }

        // tratando o email, que deve ser sempre
        // a primeira coluna do arquivo.
        email := line[0]
        // remove os espaços em branco.
        email = strings.TrimSpace(email)
        // transforma em caixa baixa.
        email = strings.ToLower(email)
        // retorna o email tratado para o csv.
        line[0] = email

        // verifica se já existe a chave email no map.
        if existEmails[email] {
                // se tiver adicionamos a linha no slice de emails duplicados.
                duplicatedEmails = append(duplicatedEmails, line)
        } else {
                // verifica se o email é válido atraves de expressão regular.
                valid, _ := regexp.MatchString(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`, email)
                if valid {
                        // emails válidos são adicionados no slice de emails válidos.
                        validEmails = append(validEmails, line)
                } else {
                        // emails inválidos são adicionados no slice de emails inválidos.
                        invalidEmails = append(invalidEmails, line)
                }
                // adiciona o email no map de emails duplicados sendo
                // a chave o email e o valor um boleano true
                existEmails[email] = true
        }
}

// logando os emails já separados
log.Println("headers", headers)
log.Println("validEmails", validEmails)
log.Println("invalidEmails", invalidEmails)
log.Println("duplicatedEmails", duplicatedEmails)
...

Alterei o arquivo csv para:

sample.csv
email
valid@test.com
invalid@test
valid@test.com

Tendo este novo arquivo csv se executarmos o programa go run example.go sample.csv podemos observar que os emails foram separados nos 3 slices:

console
$go run example.go sample.csv
2014/08/04 21:49:52 headers [email]
2014/08/04 21:49:52 validEmails [[valid@test.com]]
2014/08/04 21:49:52 invalidEmails [[invalid@test]]
2014/08/04 21:49:52 duplicatedEmails [[valid@test.com]]

Agora como a lógica para escrever o conteúdo do slice em um arquivo csv é muito parecida vamos separar esta em uma nova função evidando duplicarmos código, segue abaixo a função que utilizaremos.

example.go
func createCsvFile(headers []string, lines [][]string, fileName string) {
        // cria um novo arquivo
        file, _ := os.Create(fileName)

        // cria um novo writer pra escrever o conteúdo
        // no arquivo csv
        writer := csv.NewWriter(file)
        // escreve o header que foi extraído do csv
        writer.Write(headers)
        // escreve as linhas com os emails no csv
        writer.WriteAll(lines)
        // limpa os dados do buffer de escrita
        writer.Flush()
        // fecha o arquivo
        file.Close()
}

Agora basta chamar esta função pra cada slice que a segunda parte do nosso programa está pronta. Abaixo o arquivo final de como ficou nosso programa.

example.go
// nome do pacote o padrão é o main
package main

// aqui você declara os pacotes que vai utilizar
import (
        "encoding/csv"
        "log"
        "os"
        "path"
        "regexp"
        "strings"
)

// função inicial que é executada quando você chama o programa
func main() {
        // Se a quantidade de argumentos for menor do que 2
        // loga uma mensagem de erro e para a execução do programa.
        if len(os.Args) < 2 {
                log.Fatal("You need to set the filename\nexample: ./example sample.csv")
        }

        // seta uma variável com o nome do arquivo
        // que foi passado como argumento
        fileName := os.Args[1]

        // aqui podemos ver uma atribuição dupla, muito comum em go
        // normalmente quando um erro ocorrer este é um dos retornos
        // da função. Aqui podemos ver que se um erro ocorrer
        // ao abrir o arquivo é atribuídoo o erro na variável err.
        file, err := os.Open(fileName)

        // Loga o erro se o mesmo ocorrer
        // e para a execução do programa.
        if err != nil {
                log.Fatal("Error:", err)
        }

        // força pra quando acabar a execução do programar
        // fechar o arquivo mesmo se um erro acorrer.
        defer file.Close()

        // criando um novo leitor do tipo csv para o nosso arquivo
        reader := csv.NewReader(file)
        // setando as configurações do nosso arquivo CSV
        reader.Comma = ','
        reader.TrimLeadingSpace = true

        // lendo o arquivo inteiro e atribuindo o valor na variável lines,
        // se um erro ocorrer ficará na variável err.
        lines, err := reader.ReadAll()

        // Loga o erro se o mesmo ocorrer
        // e para a execução do programa.
        if err != nil {
                log.Fatal("Error reading all lines: ", err)
        }

        // declarando as variaveis que serão utilizadas
        // para fazer as separações dos emails
        var headers []string
        var validEmails [][]string
        var invalidEmails [][]string
        var duplicatedEmails [][]string

        // vamos criar agora uma variável do tipo map, que
        // é bem parecido com o hash do Ruby,para
        // identificarmos os emails duplicados.
        existEmails := make(map[string]bool)

        // loop for pra interar em todos os emails
        for i, line := range lines {
                // separando a primeira linha que é o
                // cabeçalho do CSV.
                if i == 0 {
                        headers = line
                        continue
                }

                // tratando o email, que deve ser sempre
                // a primeira coluna do arquivo.
                email := line[0]
                // remove os espaços em branco.
                email = strings.TrimSpace(email)
                // transforma em caixa baixa.
                email = strings.ToLower(email)
                // retorna o email tratado para o csv.
                line[0] = email

                // verifica se já existe a chave email no map.
                if existEmails[email] {
                        // se tiver adicionamos a linha no slice de emails duplicados.
                        duplicatedEmails = append(duplicatedEmails, line)
                } else {
                        // verifica se o email é válido atraves de expressão regular.
                        valid, _ := regexp.MatchString(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`, email)
                        if valid {
                                // emails válidos são adicionados no slice de emails válidos.
                                validEmails = append(validEmails, line)
                        } else {
                                // emails inválidos são adicionados no slice de emails inválidos.
                                invalidEmails = append(invalidEmails, line)
                        }
                        // adiciona o email no map de emails duplicados sendo
                        // a chave o email e o valor um boleano true
                        existEmails[email] = true
                }
        }

        // define qual será o diretório onde criaremos
        // os arquivos para os novos arquivos
        path := path.Dir(os.Args[1]) + "/output"

        // cria o diretório se o mesmo não existir
        err = os.MkdirAll(path, 0755)
        // Loga o erro se o mesmo ocorrer
        // e para a execução do programa.
        if err != nil {
                log.Fatal("Error to create", path, err)
        }

        // cria os arquivos para cada slice dos emails
        createCsvFile(headers, validEmails, path+"/valid.csv")
        createCsvFile(headers, invalidEmails, path+"/invalid.csv")
        createCsvFile(headers, duplicatedEmails, path+"/duplicated.csv")
}

func createCsvFile(headers []string, lines [][]string, fileName string) {
        // cria um novo arquivo
        file, _ := os.Create(fileName)

        // cria um novo writer pra escrever o conteúdo
        // no arquivo csv
        writer := csv.NewWriter(file)
        // escreve o header que foi extraído do csv
        writer.Write(headers)
        // escreve as linhas com os emails no csv
        writer.WriteAll(lines)
        // limpa os dados do buffer de escrita
        writer.Flush()
        // fecha o arquivo
        file.Close()
}

Se executarmos o comando go run example.go sample.csv não teremos mais saída, apenas será criado um diretório no mesmo caminho do arquivo sample.csv e neste teremos 3 novos arquivos sendo eles "valid" com os emails válidos, "invalid" com os emails inválidos e o arquivo "duplicated" com emails repetidos.