├── .github └── ISSUE_TEMPLATE │ ├── error-en-los-ejemplos.md │ └── problemas-con-los-slides.md ├── .gitignore ├── CONTRIBUTORS.md ├── LICENSE ├── README.md ├── README.md.org ├── TODO.org ├── day-01 ├── 01-installation │ └── README.md.org ├── 02-hello-world │ ├── README.md.org │ └── main.go ├── 03-variables │ ├── README.md.org │ └── main.go ├── 04-types │ ├── README.md.org │ └── main.go ├── 05-functions │ ├── README.md.org │ └── main.go ├── 06-control-statments │ ├── README.md.org │ ├── for.go │ ├── if.go │ └── switch.go ├── 07-arrays │ ├── README.md.org │ └── main.go ├── 08-maps │ ├── README.md.org │ └── main.go ├── 09-packages-modules │ ├── README.md.org │ ├── geometry │ │ └── square.go │ ├── go.mod │ └── main.go └── 10-cmdline │ ├── README.md.org │ ├── go.mod │ └── main.go ├── day-02 ├── 11-errors-defer-panic-recover │ ├── README.md.org │ ├── defer-recover │ │ └── main.go │ └── errors │ │ └── main.go ├── 12-testing │ ├── README.md.org │ ├── functions.go │ ├── functions_test.go │ ├── go.mod │ └── testdata │ │ └── hello_world ├── 13-structs-pointers │ ├── README.md.org │ └── main.go ├── 14-interfaces │ ├── README.md.org │ ├── empty-interface.go │ ├── interface-values.go │ ├── interfaces-struct-composition.go │ ├── interfaces.go │ └── type-assertions.go └── 15-concurrency │ ├── README.md.org │ └── concurrency │ ├── go.mod │ └── main.go ├── day-03 ├── 16-json │ ├── README.md.org │ └── json-ser-de │ │ ├── go.mod │ │ └── main.go ├── 17-rest-api │ ├── README.md.org │ ├── hello-world-api │ │ └── main.go │ ├── helloworld-api-gorilla │ │ └── main.go │ ├── rest-api-gorilla │ │ └── main.go │ └── rest-api │ │ ├── go.mod │ │ └── main.go └── 18-http-client │ ├── README.md.org │ ├── go.mod │ └── main.go ├── day-04 ├── challenge1 │ ├── 1.1 │ │ ├── README.md.org │ │ └── main.go │ ├── 1.2 │ │ ├── README.md.org │ │ └── main.go │ ├── 1.3 │ │ ├── README.md.org │ │ ├── reverseapp │ │ │ └── main.go │ │ └── stringutil │ │ │ └── reverse.go │ ├── 1.4 │ │ ├── README.md.org │ │ ├── reverseapp │ │ │ └── main.go │ │ └── stringutil │ │ │ └── reverse.go │ └── 1.5 │ │ ├── README.md.org │ │ ├── reverseapp │ │ └── main.go │ │ └── stringutil │ │ ├── reverse.go │ │ └── reverse_test.go └── challenge2 │ ├── 2.1 │ ├── README.md.org │ └── main.go │ ├── 2.2 │ ├── README.md.org │ ├── main.go │ └── main_test.go │ └── 2.3 │ ├── README.md.org │ ├── main.go │ └── main_test.go ├── day-05 ├── challenge3 │ ├── README.md.org │ ├── math.go │ └── math_test.go └── challenge4 │ ├── 4.1 │ ├── README.md.org │ └── main.go │ ├── 4.2 │ ├── README.md.org │ ├── inmemoryrepository.go │ ├── main.go │ ├── mongorepository.go │ └── todorepository.go │ └── 4.3 │ ├── README.md.org │ ├── go.mod │ ├── go.sum │ ├── main.go │ ├── main_test.go │ ├── memoryrepository.go │ ├── mongorepository.go │ ├── todorepository.go │ └── views │ └── index.html ├── day-06 ├── 19-dbaccess │ ├── README.md.org │ ├── mongo │ │ ├── dbaccess.go │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ └── sqlite │ │ ├── data.db │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go └── 20-echo │ ├── README.md.org │ └── echo │ ├── cmd │ └── litter │ │ └── litter.go │ ├── go.mod │ ├── go.sum │ └── internal │ ├── handler │ ├── handler.go │ ├── post.go │ └── user.go │ └── model │ ├── post.go │ └── user.go ├── day-07 ├── 21-sarama │ ├── README.md.org │ ├── consumergroup │ │ ├── README.md │ │ ├── go.mod │ │ └── main.go │ ├── http_server │ │ ├── .gitignore │ │ ├── README.md │ │ ├── go.mod │ │ ├── http_server.go │ │ └── http_server_test.go │ ├── interceptors │ │ ├── README.md │ │ ├── go.mod │ │ ├── main.go │ │ └── trace_interceptor.go │ └── producer-consumer │ │ ├── cmd │ │ ├── consumer-groups │ │ │ └── consumer-groups.go │ │ ├── consumer │ │ │ └── consumer.go │ │ └── producer │ │ │ └── producer.go │ │ ├── go.mod │ │ └── go.sum └── 22-concurrency-patterns │ └── README.md.org ├── index.html └── reveal ├── css ├── layout.scss ├── print │ ├── paper.scss │ └── pdf.scss ├── reveal.scss └── theme │ ├── README.md │ ├── source │ ├── beige.scss │ ├── black.scss │ ├── blood.scss │ ├── league.scss │ ├── moon.scss │ ├── night.scss │ ├── serif.scss │ ├── simple.scss │ ├── sky.scss │ ├── solarized.scss │ └── white.scss │ └── template │ ├── exposer.scss │ ├── mixins.scss │ ├── settings.scss │ └── theme.scss ├── dist ├── reset.css ├── reveal.css ├── reveal.esm.js ├── reveal.js └── theme │ ├── beige.css │ ├── black.css │ ├── blood.css │ ├── fonts │ ├── league-gothic │ │ ├── LICENSE │ │ ├── league-gothic.css │ │ ├── league-gothic.eot │ │ ├── league-gothic.ttf │ │ └── league-gothic.woff │ └── source-sans-pro │ │ ├── LICENSE │ │ ├── source-sans-pro-italic.eot │ │ ├── source-sans-pro-italic.ttf │ │ ├── source-sans-pro-italic.woff │ │ ├── source-sans-pro-regular.eot │ │ ├── source-sans-pro-regular.ttf │ │ ├── source-sans-pro-regular.woff │ │ ├── source-sans-pro-semibold.eot │ │ ├── source-sans-pro-semibold.ttf │ │ ├── source-sans-pro-semibold.woff │ │ ├── source-sans-pro-semibolditalic.eot │ │ ├── source-sans-pro-semibolditalic.ttf │ │ ├── source-sans-pro-semibolditalic.woff │ │ └── source-sans-pro.css │ ├── league.css │ ├── moon.css │ ├── night.css │ ├── serif.css │ ├── simple.css │ ├── sky.css │ ├── solarized.css │ └── white.css └── plugin ├── highlight ├── highlight.esm.js ├── highlight.js ├── monokai.css ├── plugin.js └── zenburn.css ├── markdown ├── markdown.esm.js ├── markdown.js └── plugin.js ├── math ├── math.esm.js ├── math.js └── plugin.js ├── notes ├── notes.esm.js ├── notes.js ├── plugin.js └── speaker-view.html ├── search ├── plugin.js ├── search.esm.js └── search.js └── zoom ├── plugin.js ├── zoom.esm.js └── zoom.js /.github/ISSUE_TEMPLATE/error-en-los-ejemplos.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Error en los ejemplos 3 | about: Reporar errores o mejoras con los ejemplos 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: yorodm 7 | 8 | --- 9 | 10 | **Describa el error** 11 | Descripción pequeña del error. 12 | Ejemplo (camino hasta el proyecto): 13 | Error: 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/problemas-con-los-slides.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Problemas con los slides 3 | about: Reportar problemas con los slides 4 | title: "[SLIDE]" 5 | labels: documentation 6 | assignees: yorodm 7 | 8 | --- 9 | 10 | Número de sección: 11 | Problema: 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Colaboradores 2 | 3 | Agradecimientos a los siguientes usuarios que han colaborado con el 4 | _workshop_ en general: 5 | 6 | 1. [leiserfg](https://github.com/leiserfg) 7 | 2. [oneohthree](https://github.com/oneohthree) 8 | 3. [cccaballero](https://github.com/cccaballero) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013 Shopify 4 | Copyright (c) 2017 LabStack 5 | Copyright (c) 2019 Rodolfo Finochietti 6 | Copyright (c) 2021 Yoandy Rodriguez (https://github.com/yorodm) 7 | 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang desde 0 hasta API 2 | 3 | Slides y ejemplos para un workshop de Golang para principiantes. 4 | 5 | ## Tabla de contenidos 6 | 7 | 1. Primeros pasos 8 | 2. Hello World 9 | 3. Variables 10 | 4. Tipos 11 | 5. Funciones 12 | 6. Instrucciones de control 13 | 7. Arreglos 14 | 8. Maps 15 | 9. Paquetes y módulos 16 | 10. Linea de comandos 17 | 11. Manejo de errores: error, defer, recover, panic 18 | 12. Pruebas unitarias 19 | 13. Structs y punteros 20 | 14. Interfaces 21 | 15. Concurrencia en Go. 22 | 16. Formatos de datos: JSON y XML 23 | 17. Creando APIs REST 24 | 18. HTTP Client 25 | 19. Challenge 1.1 26 | 20. Challenge 1.2 27 | 21. Challenge 1.3 28 | 22. Challenge 1.4 29 | 23. Challenge 1.5 30 | 24. Challenge 2.1 31 | 25. Challenge 2.2 32 | 26. Challenge 2.3 33 | 27. Challenge 3 34 | 28. Challenge 4.1 35 | 29. Challenge 4.2 36 | 30. Challenge 4.3 37 | 31. Acceso a bases de datos 38 | 32. Labstack Echo 39 | 33. Golang y Kafka [en progreso] 40 | -------------------------------------------------------------------------------- /README.md.org: -------------------------------------------------------------------------------- 1 | #+EXPORT_FILE_NAME: index.html 2 | #+REVEAL_ROOT: ./reveal/ 3 | #+Title: Golang de 0 a API. 4 | #+Author: Yoandy Rodríguez Martínez 5 | #+Email: yoandy.rmartinez@gmail.com 6 | 7 | #+OPTIONS: date:nil toc:nil timestamp:nil reveal_width:1200, reveal_height:800 8 | #+REVEAL_MARGIN: 0.1 9 | #+REVEAL_MIN_SCALE: 0.2 10 | #+REVEAL_MAX_SCALE: 2.5 11 | #+REVEAL_THEME: moon 12 | #+REVEAL_TRANS: none 13 | #+REVEAL_HLEVEL: 2 14 | #+REVEAL_HEAD_PREAMBLE: 15 | 16 | #+INCLUDE: day-01/01-installation/README.md.org 17 | #+INCLUDE: day-01/02-hello-world/README.md.org 18 | #+INCLUDE: day-01/03-variables/README.md.org 19 | #+INCLUDE: day-01/04-types/README.md.org 20 | #+INCLUDE: day-01/05-functions/README.md.org 21 | #+INCLUDE: day-01/06-control-statments/README.md.org 22 | #+INCLUDE: day-01/07-arrays/README.md.org 23 | #+INCLUDE: day-01/08-maps/README.md.org 24 | #+INCLUDE: day-01/09-packages-modules/README.md.org 25 | #+INCLUDE: day-01/10-cmdline/README.md.org 26 | #+INCLUDE: day-02/11-errors-defer-panic-recover/README.md.org 27 | #+INCLUDE: day-02/12-testing/README.md.org 28 | #+INCLUDE: day-02/13-structs-pointers/README.md.org 29 | #+INCLUDE: day-02/14-interfaces/README.md.org 30 | #+INCLUDE: day-02/15-concurrency/README.md.org 31 | #+INCLUDE: day-03/16-json/README.md.org 32 | #+INCLUDE: day-03/17-rest-api/README.md.org 33 | #+INCLUDE: day-03/18-http-client/README.md.org 34 | #+INCLUDE: day-04/challenge1/1.1/README.md.org 35 | #+INCLUDE: day-04/challenge1/1.2/README.md.org 36 | #+INCLUDE: day-04/challenge1/1.3/README.md.org 37 | #+INCLUDE: day-04/challenge1/1.4/README.md.org 38 | #+INCLUDE: day-04/challenge1/1.5/README.md.org 39 | #+INCLUDE: day-04/challenge2/2.1/README.md.org 40 | #+INCLUDE: day-04/challenge2/2.2/README.md.org 41 | #+INCLUDE: day-04/challenge2/2.3/README.md.org 42 | #+INCLUDE: day-05/challenge3/README.md.org 43 | #+INCLUDE: day-05/challenge4/4.1/README.md.org 44 | #+INCLUDE: day-05/challenge4/4.2/README.md.org 45 | #+INCLUDE: day-05/challenge4/4.3/README.md.org 46 | #+INCLUDE: day-06/19-dbaccess/README.md.org 47 | #+INCLUDE: day-06/20-echo/README.md.org 48 | #+INCLUDE: day-07/21-sarama/README.md.org 49 | #+INCLUDE: day-07/22-concurrency-patterns/README.md.org 50 | -------------------------------------------------------------------------------- /TODO.org: -------------------------------------------------------------------------------- 1 | * Cosas que quedan por terminar 2 | 3 | ** Semana 1 [1/3] 4 | 5 | - [X] Dia 1. [10/10] 6 | - [X] 01-installation 7 | - [X] 02-hello-world 8 | - [X] 03-variables 9 | - [X] 04-types 10 | - [X] 05-functions 11 | - [X] 06-control-statments 12 | - [X] 07-arrays 13 | - [X] 08-maps 14 | - [X] 09-packages-modules 15 | - [X] 10-cmdline 16 | - [-] Dia 2. [4/5] 17 | - [X] 11-errors-defer-panic-recover 18 | - [X] 12-testing 19 | - [X] 13-structs-pointers 20 | - [X] 14-interfaces 21 | - [ ] 15-concurrency 22 | - [ ] Dia 3. [0/3] 23 | - [ ] 16-json-xml 24 | - [ ] 17-rest-api 25 | - [ ] 18-http-client 26 | 27 | 28 | ** Semana 2 [0/3] 29 | 30 | - [ ] Dia 6. 31 | - [ ] 19-dbaccess 32 | - [ ] 20-echo 33 | - [ ] Dia 7 34 | - [ ] 21-concurrency-patterns 35 | - [ ] 22-context 36 | - [ ] 23 cancellation 37 | -------------------------------------------------------------------------------- /day-01/01-installation/README.md.org: -------------------------------------------------------------------------------- 1 | 2 | * Primeros pasos 3 | :PROPERTIES: 4 | :CUSTOM_ID: first-steps 5 | :END: 6 | 7 | ** Instalación 8 | :PROPERTIES: 9 | :CUSTOM_ID: install 10 | :END: 11 | 12 | 1. Descargar la versión para tu OS desde [[https://golang.org/dl]] 13 | (algunas distribuciones de Linux incluyen el paquete en sus 14 | repositorios). 15 | 16 | 2. Abrir el instalador y seguir las 17 | [[https://golang.org/doc/install][instrucciones]]. 18 | 19 | #+begin_example 20 | (Unix| Linux): /usr/local/go (varía según el sistema) 21 | Windows: C:\Go 22 | #+end_example 23 | 24 | 3. Para chequear que la instalación está correcta escribir en la 25 | consola 26 | 27 | #+begin_example 28 | $ go version 29 | #+end_example 30 | 31 | ** Configuración inicial 32 | :PROPERTIES: 33 | :CUSTOM_ID: config-initial 34 | :END: 35 | 36 | 1. Activar variables de entorno para el trabajo con módulos 37 | 38 | #+begin_example 39 | $ go env -w GO111MODULE=on GOPROXY=https://goproxy.io,direct 40 | #+end_example 41 | 42 | 2. Para chequear que todas las variables de entorno están correctas 43 | escribir en la consola 44 | 45 | #+begin_example 46 | $ go env 47 | GOROOT = [Directorio de instalación] 48 | GOPROXY=https://goproxy.io,direct 49 | GO111MODULE=on 50 | #+end_example 51 | 52 | ** Completamiento en editores 53 | :PROPERTIES: 54 | :CUSTOM_ID: gpls 55 | :END: 56 | 57 | 1. Instalar la herramienta =gopls=. 58 | 59 | #+begin_example 60 | $ go get golang.org/x/tools/gopls@latest 61 | #+end_example 62 | 63 | ** Otras Herramientas 64 | :PROPERTIES: 65 | :CUSTOM_ID: otras-herramientas 66 | :END: 67 | 68 | - [[https://code.visualstudio.com/download][Visual Studio Code]] 69 | - [[https://code.visualstudio.com/docs/languages/go][Visual Studio Code Go extension]] 70 | - [[https://www.getpostman.com][Postman]] 71 | - [[https://www.mongodb.com][MongoDB]] 72 | -------------------------------------------------------------------------------- /day-01/02-hello-world/README.md.org: -------------------------------------------------------------------------------- 1 | * Hello World 2 | :PROPERTIES: 3 | :CUSTOM_ID: hello-world 4 | :END: 5 | Vamos a programar el tradicional =Hello World=. 6 | 7 | ** Pasos 8 | :PROPERTIES: 9 | :CUSTOM_ID: steps 10 | :END: 11 | 12 | 1. Crear un directorio llamado =hello-world=. 13 | 2. Crear un archivo de texto llamado =main.go= dentro del directorio 14 | creado en el paso 1. 15 | 3. Agregar el siguiente código al archivo =main.go= 16 | 17 | #+begin_src go 18 | package main 19 | 20 | import "fmt" 21 | 22 | func main() { 23 | fmt.Println("Hello World") 24 | } 25 | #+end_src 26 | 27 | - Ejecutar el programa con el comando =go run main.go= 28 | 29 | ** ¿Qué sucede exactamente? 30 | :PROPERTIES: 31 | :CUSTOM_ID: deep-dive 32 | :END: 33 | 34 | En esta sección vamos a revisar cada línea del programa =Hello World=. 35 | 36 | *** Packages e imports 37 | 38 | Linea 1 => =package main= 39 | 40 | Los *packages* son la forma de agrupar funcionalidad común en Golang. El 41 | package default es =main= y usualmente significa que el archivo que lo 42 | contiene es el punto de entrada. 43 | 44 | Linea 3 => =import "fmt"= 45 | 46 | En esta línea especificamos que el package =fmt= (que se incluye como 47 | parte de Golang), es requerido para ejecutar este programa. 48 | 49 | *** Funciones 50 | 51 | Linea 5 => =func main() {= 52 | 53 | Las funciones en Golang se definen con el keyword =func=. En esta línea 54 | estamos definiendo una función llamada =main= sin argumentos. El código 55 | de la función se escribe entre ={ }=. 56 | 57 | Para crear un ejecutable en Golang tiene que existir una funcion llamada 58 | =main= dentro de un package también llamado =main=. Solo puede haber una 59 | funcion =main=, incluso si tenemos muchos archivos =.go= (solo puede 60 | existir un entrypoint por ejecutable) 61 | 62 | *** Bibliotecas 63 | 64 | Línea 6 => =fmt.Println("Hello World")= 65 | 66 | Esta línea imprime =Hello World= en la consola. =Println= es una función 67 | dentro del package =fmt= que recibe como argumento el string 68 | ="Hello World"=. 69 | 70 | La función =Println= hace dos cosas:: 71 | 72 | - Imprime la entrada que el usuario específica en el argumento. 73 | - Agrega una nueva línea después de imprimir la entrada especificada. 74 | -------------------------------------------------------------------------------- /day-01/02-hello-world/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("Hello World") 7 | } 8 | -------------------------------------------------------------------------------- /day-01/03-variables/README.md.org: -------------------------------------------------------------------------------- 1 | * Variables 2 | :PROPERTIES: 3 | :CUSTOM_ID: variables 4 | :END: 5 | 6 | ** Definiendo variables 7 | 8 | Una variable es un nombre que se le asigna a una porción de memoria para 9 | almacenar un valor de un determinado tipo. 10 | 11 | El keyword =var= se usa para declarar variables. La sintaxis es 12 | =var = seguido (opcionalmente) por un valor inicial. 13 | 14 | *** Ejemplos de variables 15 | 16 | #+begin_src go 17 | var a int // Inicializado por defecto 18 | 19 | var b = 10 // Se infiere el tipo 20 | 21 | var c, d = 20, 30 // Asignando valores múltiples 22 | 23 | e, f := 40, 50 // Crear y asignar 24 | #+end_src 25 | 26 | *** Valor inicial 27 | 28 | Si no se especifica un valor inicial Golang asigna uno por defecto 29 | dependiento del tipo de dato de la variable. 30 | 31 | Para imprimir el valor de una variable se puede usar =Println=. 32 | 33 | =fmt.Println("a = ", a)= 34 | -------------------------------------------------------------------------------- /day-01/03-variables/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func init() { 6 | fmt.Println("Se ejecuta init") 7 | } 8 | 9 | func main() { 10 | var a int // Default initialization 11 | fmt.Println("a: ", a) 12 | 13 | var b = 10 // Automatic type inference 14 | fmt.Println("b: ", b) 15 | 16 | var c, d = 20, 30 // Multiple value assignment 17 | fmt.Println("c: ", c, " d: ", d) 18 | 19 | e, f := 40, 50 // Shorthand assignment 20 | fmt.Println("e: ", e, " f: ", f) 21 | } 22 | -------------------------------------------------------------------------------- /day-01/04-types/README.md.org: -------------------------------------------------------------------------------- 1 | * Tipos 2 | :PROPERTIES: 3 | :CUSTOM_ID: tipos 4 | :END: 5 | 6 | ** Tipos simples 7 | 8 | Los siguientes tipos de datos están disponibles en Golang: 9 | 10 | - bool 11 | - Tipos numéricos: 12 | 13 | - int8, int16, int32, int64, int 14 | - uint8, uint16, uint32, uint64, uint 15 | - float32, float64 16 | - complex64, complex128 17 | - byte 18 | - rune 19 | - string 20 | - error 21 | - chan 22 | 23 | *** Booleanos 24 | 25 | =bool= representa valores boleanos =true= o =false=. 26 | 27 | *** Tipos numéricos 28 | 29 | Para tipos numéricos, el número que sigue al tipo indica la cantidad de 30 | bits que se usa para representar el valor en memoria. El número de bits 31 | determina qué tan grande el número puede ser. 32 | 33 | =int8=: 8 bit signed integers 34 | 35 | =int16=: 16 bit entero con signo 36 | 37 | =int32=: 32 bit entero con signo 38 | 39 | =int=: 32 or 64 bit entero con signo (dependiende de la plataforma). 40 | 41 | =uint= : 32 or 64 bit entero sin signo (dependiende de la plataforma). 42 | 43 | =float32=: 32 bit número flotante. 44 | 45 | =float64=: 64 bit número flotante. 46 | 47 | =byte= alias de =uint8= 48 | 49 | =rune= alias de =int32= 50 | 51 | *** Números complejos 52 | 53 | =complex64=: números complejos con parte real e imaginaria =float32= 54 | 55 | =complex128=: números complejos con parte real e imaginaria =float64= 56 | 57 | La función =complex= se utiliza para construir un número complejo con 58 | partes reales e imaginarias: 59 | 60 | #+begin_src go 61 | func complex(r, i FloatType) ComplexType 62 | #+end_src 63 | 64 | *** El tipo string 65 | 66 | =string= es una colección de caracteres encerrados entre comillas. 67 | 68 | #+begin_src go 69 | first := "Allen" 70 | last := "Varghese" 71 | name := first +" "+ last 72 | fmt.Println("My name is",name) 73 | #+end_src 74 | 75 | *** Errores y canales. 76 | 77 | =error= es un tipo especial para indicar condiciones de error. 78 | 79 | =chan= es un tipo especial que representa un canal de comunicación. 80 | 81 | ** Conversion de tipos 82 | :PROPERTIES: 83 | :CUSTOM_ID: conversion-de-tipos 84 | :END: 85 | 86 | No hay conversión automática de tipos en Golang. Se utilizan funciones 87 | de tipo para hacer una conversión. 88 | 89 | #+begin_src go 90 | int() 91 | float64() 92 | #+end_src 93 | 94 | ** Constantes 95 | :PROPERTIES: 96 | :CUSTOM_ID: constantes 97 | :END: 98 | 99 | Las constantes no cambian sus valores una vez asignadas. Se usa el keyword 100 | =const= en vez de =var= para declarar una constante. 101 | 102 | #+begin_src go 103 | const a bool = true 104 | const b int32 = 32890 105 | const c string = "Something" 106 | #+end_src 107 | -------------------------------------------------------------------------------- /day-01/04-types/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | func main() { 9 | var i int16 = 55 // int16 10 | var j float32 = 67.8 // float32 11 | sum := i + int16(j) // j is converted to int 12 | fmt.Println(sum) 13 | 14 | const aa int = 48 15 | const bb bool = true 16 | fmt.Println(aa, bb) 17 | 18 | // Type conversion 19 | var x, y int = 3, 4 20 | var f float64 = math.Sqrt(float64(x*x + y*y)) 21 | var z uint = uint(f) 22 | fmt.Println(x, y, z) 23 | } 24 | -------------------------------------------------------------------------------- /day-01/05-functions/README.md.org: -------------------------------------------------------------------------------- 1 | * Funciones 2 | :PROPERTIES: 3 | :CUSTOM_ID: funciones 4 | :END: 5 | 6 | ** Funciones en Golang 7 | :PROPERTIES: 8 | :CUSTOM_ID: golang-funcs 9 | :END: 10 | 11 | Una función es un grupo de instrucciones que se ejecutan todas juntas 12 | como un bloque. Una función puede o no tener argumentos de entrada, y 13 | retornar valores. 14 | 15 | En Golang una función se define con el keyword =func=. El siguiente es 16 | un ejemplo de una función que suma dos enteros: 17 | 18 | #+begin_src go 19 | func AddIntegers(a int, b int) int { 20 | return a + b 21 | } 22 | #+end_src 23 | 24 | ** Retornando valores 25 | :PROPERTIES: 26 | :CUSTOM_ID: golang-returns 27 | :END: 28 | 29 | El keyword =return= es usado para indicar qué valor va a retornar la 30 | función. 31 | 32 | Golang soporta que una función devuelva múltiples valores: 33 | 34 | #+begin_src go 35 | func SumDifference(a int, b int) (int, int) { 36 | return a + b, a - b 37 | } 38 | #+end_src 39 | 40 | ** Identificador en blanco 41 | :PROPERTIES: 42 | :CUSTOM_ID: blank-identifier 43 | :END: 44 | 45 | Se utiliza el *identificador en blanco* en el lugar de un valor que se 46 | quiere descartar al llamar a una función: 47 | 48 | #+begin_src go 49 | var _, diff = SumDifference(10, 20) 50 | 51 | fmt.Println("Difference is ", diff) 52 | #+end_src 53 | 54 | ** Valores de retorno con nombres 55 | :PROPERTIES: 56 | :CUSTOM_ID: named-return-values 57 | :END: 58 | 59 | Cuando se define una función se le puede asignar un nombre al tipo de 60 | dato de retorno para luego referenciarlo en el código de la función. 61 | 62 | #+begin_src go 63 | func Product(a int, b int) (prod int) { 64 | prod = a * b 65 | return 66 | } 67 | #+end_src 68 | 69 | Al asignar un valor de retorno con nombre no hace falta incluirlo en 70 | la sentencia =return=. 71 | 72 | ** Funciones anónimas y clausuras 73 | :PROPERTIES: 74 | :CUSTOM_ID: funciones-anónimas-y-clausuras 75 | :END: 76 | 77 | Golang soporta funciones anónimas y de segundo orden. Tomemos por 78 | ejemplo la función =sort.Slice= 79 | 80 | #+begin_src go 81 | func Slice(slice interface{}, less func(i, j int) bool) 82 | #+end_src 83 | 84 | El parámetro =less= describe una función que toma dos enteros y 85 | retorna un valor =bool= 86 | 87 | *** Funciones anónimas. 88 | 89 | Podemos asignar una función a una variable o definirla en el momento 90 | de su uso. Las funciones anónimas forman *clausuras* 91 | 92 | #+begin_src go 93 | c := []int{20, 11, 12, 1, 5} 94 | less := func(i int, j int) bool { 95 | return c[i] < c[j] 96 | } 97 | sort.Slice(c, less) 98 | fmt.Println("Despues del sort", c) 99 | //Output: [1 5 11 12 20] 100 | c := []int{20, 11, 12, 1, 5} 101 | sort.Slice(c, func(i int, j int) bool { 102 | return c[i] < c[j] 103 | }) 104 | fmt.Println("Despues del sort", c) 105 | //Output: [1 5 11 12 20] 106 | #+end_src 107 | -------------------------------------------------------------------------------- /day-01/05-functions/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | ) 7 | 8 | func main() { 9 | var sum = AddIntegers(10, 20) 10 | fmt.Println("Sum of 10 and 20 is", sum) 11 | 12 | var sum2, diff = SumDifference(10, 20) 13 | fmt.Println("Sum & Difference of 10 and 20 are (", sum2, ",", diff, ")") 14 | 15 | var _, diff1 = SumDifference(10, 20) 16 | fmt.Println("Difference of 10 and 20 is", diff1) 17 | c := []int{20, 11, 12, 1, 5} 18 | fmt.Println("Antes del sort", c) 19 | less := func(i int, j int) bool { 20 | return c[i] < c[j] 21 | } 22 | sort.Slice(c, less) 23 | fmt.Println("Despues del sort", c) 24 | } 25 | 26 | // AddIntegers adds two integers 27 | func AddIntegers(a int, b int) int { 28 | return a + b 29 | } 30 | 31 | // SumDifference gives the sum and difference of two numbers 32 | func SumDifference(a int, b int) (int, int) { 33 | return a + b, a - b 34 | } 35 | -------------------------------------------------------------------------------- /day-01/06-control-statments/README.md.org: -------------------------------------------------------------------------------- 1 | * Instrucciones de control 2 | :PROPERTIES: 3 | :CUSTOM_ID: instrucciones-de-control 4 | :END: 5 | 6 | Golang soporta múltiples instrucciones de control e iteradores. 7 | 8 | ** =if=/=else= 9 | :PROPERTIES: 10 | :CUSTOM_ID: if---else-if---else 11 | :END: 12 | 13 | Se pueden tener muchas instrucciones =else if=. El bloque =else= es 14 | opcional: 15 | 16 | #+begin_src go 17 | if condition { 18 | // codigo a ejecutar 19 | } else if condition { 20 | // codigo a ejecutar 21 | } else { 22 | // codigo a ejecutar 23 | } 24 | #+end_src 25 | 26 | #+begin_quote 27 | El bloque =else= debe estar en la misma línea que la llave de cierre 28 | (=}=). 29 | #+end_quote 30 | 31 | *** Inicializar la condición. 32 | 33 | La instruccion =if= soporta indicar una instrucción opcional que se 34 | ejecuta antes de la condición: 35 | 36 | #+begin_src go 37 | if statement; condition { 38 | // código a ejecutar 39 | } 40 | #+end_src 41 | 42 | ** =for= 43 | :PROPERTIES: 44 | :CUSTOM_ID: for 45 | :END: 46 | 47 | Hay una sola instrucción de iteración en Golang, el iterador =for=. 48 | 49 | #+begin_src go 50 | for initialization; condition; post { 51 | // codigo a ejecutar 52 | } 53 | #+end_src 54 | 55 | Los tres componentes =initialization=, =condition= y =post= son 56 | opcionales en Golang. 57 | 58 | *** =for= en detalle 59 | 60 | 1. La inicialización se ejecuta una sola vez. 61 | 2. La condición se prueba antes de ejecutar el ciclo. 62 | 3. El post se ejecuta después de cada iteración del ciclo. 63 | 64 | *** Ejemplo de =for= 65 | 66 | El siguiente ejemplo imprime números del 1 al 10 usando un iterador 67 | =for=: 68 | 69 | #+begin_src go 70 | for i := 1; i <= 10; i = i + 1 { 71 | fmt.Println(i) 72 | } 73 | #+end_src 74 | 75 | ** =switch= 76 | :PROPERTIES: 77 | :CUSTOM_ID: switch 78 | :END: 79 | 80 | La instrucción de control =switch= evalúa una expresión y la compara 81 | contra una lista de posibles valores o expresiones que puedan coincidir. 82 | Es una forma abreviada de escribir muchas cláusulas =if else=. 83 | 84 | #+begin_src go 85 | switch expression { 86 | case expression or value | (, expression or value)*: 87 | // código a ejecutar 88 | (break | fallthrough) 89 | case expression or value | (, expression or value)*: 90 | // código a ejecutar 91 | default: 92 | // código a ejecutar 93 | } 94 | #+end_src 95 | 96 | *** =break=, =fallthrough= y =default= 97 | 98 | 1. =break= detiene el flujo y sale del =switch= 99 | 2. =fallthrough= continúa al próximo =case=. 100 | 2. Si no hay coincidencias, se ejecuta el código en el bloque 101 | =default=. 102 | -------------------------------------------------------------------------------- /day-01/06-control-statments/for.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("1 .. 10 =>") 7 | simple() 8 | 9 | fmt.Println("\n\n1 .. 7 =>") 10 | simpleBreak() 11 | 12 | fmt.Println("\n\nOdd numbers =>") 13 | simpleContinue() 14 | 15 | fmt.Println("\n\nNested loops =>") 16 | printTower() 17 | } 18 | 19 | func simple() { 20 | for i := 1; i <= 10; i = i + 1 { 21 | fmt.Print(i, " ") 22 | } 23 | } 24 | 25 | // simpleBreak will print numbers from 1 to 7 26 | func simpleBreak() { 27 | for i := 1; i <= 10; i = i + 1 { 28 | if i == 8 { 29 | break 30 | } 31 | fmt.Print(i, " ") 32 | } 33 | } 34 | 35 | // simpleContinue will print only odd numbers 36 | func simpleContinue() { 37 | for i := 1; i <= 10; i = i + 1 { 38 | if i%2 == 0 { 39 | continue 40 | } 41 | fmt.Print(i, " ") 42 | } 43 | } 44 | 45 | func printTower() { 46 | const ROWS = 5 47 | for i := 0; i < ROWS; i++ { 48 | for j := 0; j <= i; j++ { 49 | fmt.Print(" * ") 50 | } 51 | fmt.Println("") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /day-01/06-control-statments/if.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var aa = 10 7 | if aa == 10 { 8 | fmt.Println("The number is 10!") 9 | } else if aa%2 == 0 { 10 | fmt.Println("Even number!") 11 | } else { 12 | fmt.Println("Odd number") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /day-01/06-control-statments/switch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | func main() { 9 | const finger = 3 10 | // const finger = 6 // Should go to the default section 11 | 12 | switch finger { 13 | case 1: 14 | fmt.Println("Thumb") 15 | case 2: 16 | fmt.Println("Index") 17 | case 3: 18 | fmt.Println("Middle") 19 | case 4: 20 | fmt.Println("Ring") 21 | case 5: 22 | fmt.Println("Pinky") 23 | default: 24 | fmt.Println("incorrect finger number") 25 | } 26 | 27 | fmt.Print("Go runs on ") 28 | 29 | switch os := runtime.GOOS; os { 30 | case "darwin": 31 | fmt.Println("OS X.") 32 | case "linux": 33 | fmt.Println("Linux.") 34 | default: 35 | fmt.Printf("%s.", os) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /day-01/07-arrays/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | copyArray() 7 | fmt.Println("") 8 | sumOfArray() 9 | fmt.Println("") 10 | printArray() 11 | fmt.Println("") 12 | printSlice() 13 | fmt.Println("") 14 | updateSlice() 15 | fmt.Println("") 16 | appendSlice() 17 | } 18 | 19 | func copyArray() { 20 | a := [...]string{"IRL", "IND", "US", "CAN"} 21 | b := a 22 | 23 | b[1] = "CHN" 24 | 25 | fmt.Println("Original :", a) 26 | fmt.Println("Copy :", b) 27 | } 28 | 29 | func sumOfArray() { 30 | a := [...]int{1, 2, 3, 4, 5} 31 | sum := 0 32 | for i, v := range a { 33 | fmt.Println("Index:", i, " Value is:", v) 34 | sum += v 35 | } 36 | fmt.Println("Sum:", sum) 37 | } 38 | 39 | func printArray() { 40 | a := [3][2]string{ 41 | {"lion", "tiger"}, 42 | {"cat", "dog"}, 43 | {"pigeon", "peacock"}, 44 | } 45 | for _, v1 := range a { 46 | for _, v2 := range v1 { 47 | fmt.Printf("%s ", v2) 48 | } 49 | fmt.Println("") 50 | } 51 | } 52 | 53 | func printSlice() { 54 | a := [...]int{1, 2, 3, 4, 5} 55 | sa := a[1:4] 56 | fmt.Println(sa) 57 | } 58 | 59 | func updateSlice() { 60 | a := [...]int{1, 2, 3, 4, 5} 61 | sa := a[1:4] 62 | 63 | fmt.Println("Before:", a) 64 | sa[0] = 22 65 | 66 | fmt.Println("After:", a) 67 | } 68 | 69 | func appendSlice() { 70 | sa := []int{1, 2, 3} 71 | newSa := append([]int{}, sa...) 72 | 73 | fmt.Println(newSa) 74 | } 75 | -------------------------------------------------------------------------------- /day-01/08-maps/README.md.org: -------------------------------------------------------------------------------- 1 | * Maps 2 | :PROPERTIES: 3 | :CUSTOM_ID: maps 4 | :END: 5 | 6 | ** Tipos asociativos, =map= 7 | :PROPERTIES: 8 | :CUSTOM_ID: maps-inicial 9 | :END: 10 | 11 | Un =map= es un tipo de dato incluido en Golang que asocia una clave con un 12 | valor. El valor puede ser recuperado a partir de la clave. 13 | 14 | Un =map= vacío se crea con la función =make=: 15 | 16 | #+begin_src go 17 | make(map[type of key]type of value) 18 | 19 | eg: make(map[string]int) 20 | #+end_src 21 | 22 | ** Agregando valores 23 | :PROPERTIES: 24 | :CUSTOM_ID: agregando-valores 25 | :END: 26 | 27 | Los valores en un =map= se referencian igual que en un arreglo. Un valor 28 | se agrega a un =map= asignándole una clave, si la clave ya existe el valor 29 | para esa clave se sobrescribe. 30 | 31 | #+begin_src go 32 | m := make(map[string]int) 33 | m["a"] = 1 34 | m["b"] = 2 35 | 36 | fmt.Println(m) 37 | 38 | // Output: map[a:1 b:2] 39 | #+end_src 40 | 41 | *** Inicializando un =map= 42 | 43 | Un =map= se puede inicializar con valores en su declaración igual que un 44 | arreglo: 45 | 46 | #+begin_src go 47 | m := map[string]int { 48 | "a": 1, 49 | "b": 2, 50 | } 51 | #+end_src 52 | 53 | ** Obteniendo valores 54 | :PROPERTIES: 55 | :CUSTOM_ID: retrieving-values 56 | :END: 57 | 58 | Una manera de chequear si una clave existe es la siguiente: 59 | 60 | #+begin_src go 61 | data, ok := m["some key"] 62 | #+end_src 63 | 64 | Si =ok= es =true=, la clave existe y la variable =data= contendrá la 65 | información recuperada, si =ok= es =false= la clave no existe. 66 | 67 | Acceder a una clave inexistente en un =map= causa un =panic=. 68 | 69 | ** Iteraciones 70 | :PROPERTIES: 71 | :CUSTOM_ID: iteraciones 72 | :END: 73 | 74 | El iterador =for= se utiliza para recorrer las claves y valores de un 75 | =map= 76 | 77 | #+begin_src go 78 | m := map[string]int { 79 | "a": 1, 80 | "b": 2, 81 | "c": 3, 82 | } 83 | for key, value := range m { 84 | fmt.Println("Key:", key, " Value:", value) 85 | } 86 | // Output: Key: a Value 1 87 | // Key: b Value 2 88 | // Key: c Value 3 89 | #+end_src 90 | 91 | #+REVEAL: split 92 | 93 | Se puede utilizar el identificador en blanco (=_=) para descartar 94 | cualquiera de los elementos del par. 95 | 96 | #+begin_quote 97 | El orden en que se recorre el =map= no esta garantizado, y cada ejecución puede tener un order diferente. 98 | #+end_quote 99 | 100 | ** Eliminacion 101 | :PROPERTIES: 102 | :CUSTOM_ID: eliminacion 103 | :END: 104 | 105 | Los valores se eliminan de un =map= con la función =delete=. La función no 106 | retorna nada. La sintaxis es la siguiente: 107 | 108 | #+begin_src go 109 | delete(m, key) 110 | #+end_src 111 | 112 | ** Tamaño 113 | :PROPERTIES: 114 | :CUSTOM_ID: tamaño 115 | :END: 116 | 117 | El tamaño de un =map= es retornado por la funcion =len=. Esta funcion 118 | retorna la cantidad de claves que hay en un map. La sintaxis es la 119 | siguiente: 120 | 121 | #+begin_src go 122 | len(m) 123 | #+end_src 124 | 125 | ** Tipo 126 | :PROPERTIES: 127 | :CUSTOM_ID: tipo 128 | :END: 129 | 130 | Un =map= es un /tipo referencia/. Por lo tanto si un =map= se asigna a 131 | otra variable, ambas variable apuntan a los mismos pares (clave, 132 | valor). Lo mismo sucede si se pasa como argumento en una funcion. 133 | -------------------------------------------------------------------------------- /day-01/08-maps/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | m := map[string]int{ 7 | "a": 1, 8 | "b": 2, 9 | "c": 3, 10 | } 11 | for key, value := range m { 12 | fmt.Println("Key:", key, " Value:", value) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /day-01/09-packages-modules/geometry/square.go: -------------------------------------------------------------------------------- 1 | package geometry 2 | 3 | import "fmt" 4 | 5 | func init() { 6 | fmt.Println("This is geometry.init") 7 | } 8 | 9 | type square struct { 10 | a int 11 | b int 12 | } 13 | 14 | func NewSquare(a, b int) *square { 15 | return &square{a, b} 16 | } 17 | 18 | // Area of a square 19 | func (s square) Area() int { 20 | return s.a * s.b 21 | } 22 | 23 | // Perimeter of a square 24 | func (s square) Perimeter() int { 25 | return 2 * (s.a + s.b) 26 | } 27 | -------------------------------------------------------------------------------- /day-01/09-packages-modules/go.mod: -------------------------------------------------------------------------------- 1 | module sample10 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /day-01/09-packages-modules/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sample10/geometry" 6 | ) 7 | 8 | func main() { 9 | var length, breadth = 10, 20 10 | s := geometry.NewSquare(length, breadth) 11 | fmt.Println("Area is", s.Area()) 12 | fmt.Println("Perimeter is", s.Perimeter()) 13 | } 14 | -------------------------------------------------------------------------------- /day-01/10-cmdline/README.md.org: -------------------------------------------------------------------------------- 1 | * Línea de comandos 2 | :PROPERTIES: 3 | :CUSTOM_ID: linea-de-comandos 4 | :END: 5 | 6 | ** Argumentos 7 | :PROPERTIES: 8 | :CUSTOM_ID: argumentos 9 | :END: 10 | 11 | Interactuar con la línea de comandos es muy útil y muy común al ejecutar 12 | scripts y programas que no tienen GUI. El paquete =os= se usa para tener 13 | acceso a los argumentos de la línea de comandos al ejecutar un programa 14 | de Golang. 15 | 16 | #+REVEAL: split 17 | 18 | Los argumentos de la línea de comandos están disponibles en el arreglo 19 | =os.Args=: 20 | 21 | #+begin_src go 22 | import "os" 23 | 24 | os.Args // Lista completa incluyendo el nombre del programa 25 | os.Args[1:] // Solo los argumentos 26 | #+end_src 27 | 28 | ** Flags 29 | :PROPERTIES: 30 | :CUSTOM_ID: flags 31 | :END: 32 | 33 | Especificar los argumentos como valores separados por espacios en la 34 | terminal es muy básico y difícil de extender. El uso de =flags= provee 35 | mas flexibilidad en cómo los argumentos se especifican, sobre todo los 36 | opcionales. Golang cuenta con un paquete llamado =flag= para 37 | soportarlos. 38 | 39 | #+begin_src go 40 | import "flag" 41 | 42 | flag.(nombre del flag, valor default, descripcion) 43 | #+end_src 44 | 45 | #+REVEAL: split 46 | 47 | Un flag se define con la sintaxis anterior. El == puede ser 48 | =string=, =int= o =bool=. 49 | 50 | Un flag opcional se puede indicar controlando si el valor es el mismo 51 | que el valor especificado por defecto. 52 | 53 | Un flag se puede leer independientemente de la posición en que el 54 | usuario los hubiera especificado. 55 | 56 | ** Environment Variables 57 | :PROPERTIES: 58 | :CUSTOM_ID: environment-variables 59 | :END: 60 | 61 | Las variables de entorno se usan para configurar aspectos de sistema en 62 | la mayoría de los *NIX y también en Windows. Son pares de clave-valor 63 | usualmente disponibles para todos los programas que corren en la 64 | terminal/shell. Se pueden definir variables de entorno custom solo 65 | disponibles durante la ejecución de un script de shell. 66 | 67 | #+REVEAL: split 68 | 69 | El paquete =os= proporciona dos funciones, =Setenv= y =Getenv=, para 70 | escribir y leer variables de entorno. Un string vacío se retorna si la 71 | clave no existe. 72 | 73 | #+begin_src go 74 | import "os" 75 | 76 | os.Setenv("FOO", "1") 77 | fmt.Println("FOO:", os.Getenv("FOO")) 78 | fmt.Println("BAR:", os.Getenv("BAR")) 79 | 80 | //Output: FOO: 1 81 | // BAR: 82 | #+end_src 83 | 84 | #+REVEAL: split 85 | 86 | La función =Environ= retorna la lista completa de todas las variables de 87 | entorno existentes. 88 | 89 | #+begin_src go 90 | import "strings" 91 | 92 | for _, e := range os.Environ() { 93 | pair := strings.Split(e, "=") 94 | fmt.Println(pair[0]) 95 | } 96 | #+end_src 97 | -------------------------------------------------------------------------------- /day-01/10-cmdline/go.mod: -------------------------------------------------------------------------------- 1 | module sample11 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /day-01/10-cmdline/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func main() { 11 | // Run only one of the below functions 12 | cmdArgs() 13 | cmdFlags() 14 | cmdEnv() 15 | } 16 | 17 | func cmdArgs() { 18 | argsWithProg := os.Args 19 | argsWithoutProg := os.Args[1:] 20 | 21 | arg := os.Args[3] 22 | fmt.Println("With program name: ", argsWithProg) 23 | fmt.Println("Just args:", argsWithoutProg) 24 | fmt.Println("3rd Argument:", arg) 25 | } 26 | 27 | func cmdFlags() { 28 | wordPtr := flag.String("word", "foo", "a string") 29 | numbPtr := flag.Int("numb", 42, "an int") 30 | boolPtr := flag.Bool("fork", false, "a bool") 31 | flag.Parse() 32 | 33 | fmt.Println("word:", *wordPtr) 34 | fmt.Println("numb:", *numbPtr) 35 | fmt.Println("fork:", *boolPtr) 36 | fmt.Println("tail:", flag.Args()) 37 | } 38 | 39 | func cmdEnv() { 40 | os.Setenv("FOO", "1") 41 | fmt.Println("FOO:", os.Getenv("FOO")) 42 | fmt.Println("BAR:", os.Getenv("BAR")) 43 | 44 | for _, e := range os.Environ() { 45 | pair := strings.Split(e, "=") 46 | fmt.Println(pair[0], "->", pair[1]) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /day-02/11-errors-defer-panic-recover/README.md.org: -------------------------------------------------------------------------------- 1 | * Manejo de errores: error, defer, recover, panic 2 | :PROPERTIES: 3 | :CUSTOM_ID: manejo-de-errores-error-defer-recover-panic 4 | :END: 5 | 6 | ** Manejo de errores en Golang 7 | :PROPERTIES: 8 | :CUSTOM_ID: manejo-de-errores-en-golang 9 | :END: 10 | 11 | En lugar de excepciones como Python, Java o C#, Golang sigue un enfoque 12 | más cercano a los lenguajes funcionales donde el estado de error es 13 | representado por un tipo de datos, 14 | 15 | #+begin_src go 16 | var DivisionByZero = errors.New("División por cero") 17 | 18 | func safedivide(a int, b int) (int, error) { 19 | if b == 0 { 20 | return 0, DivisionByZero 21 | } 22 | return a / b, nil 23 | } 24 | #+end_src 25 | 26 | ** El tipo error 27 | :PROPERTIES: 28 | :CUSTOM_ID: error-type 29 | :END: 30 | 31 | El tipo =error= es una interfaz /built-in/ del lenguaje 32 | 33 | #+begin_src go 34 | type error interface { 35 | Error() string 36 | } 37 | #+end_src 38 | 39 | *** Errores personalizados 40 | 41 | Esto nos permite crear nuestros propios tipos de errores 42 | 43 | #+begin_src go 44 | type MyError struct { 45 | message: string 46 | status: int 47 | } 48 | 49 | func (m *MyError) Error() string { 50 | return fmt.Sprintf("Error - %s. Status %d", m.message, m.status) 51 | } 52 | 53 | func returnMyError() error { 54 | return &MyError{ 55 | message: "None", 56 | status : -1, 57 | } 58 | } 59 | #+end_src 60 | 61 | *** Operando con errores 62 | 63 | El paquete =errors= contiene funciones dedicadas a manejar tipos de 64 | error. Podemos usar la función =errors.Is= para verificar si un error 65 | es de un tipo específico. 66 | 67 | #+begin_src go 68 | if _, err := os.Open("non-existing"); err != nil { 69 | if errors.Is(err, os.ErrNotExist) { 70 | fmt.Println("file does not exist") 71 | } else { 72 | fmt.Println(err) 73 | } 74 | } 75 | #+end_src 76 | 77 | ** Defer 78 | :PROPERTIES: 79 | :CUSTOM_ID: defer 80 | :END: 81 | 82 | La sentencia =defer= se utiliza para indicar que la función a 83 | continuación se debe ejecutar a la salida de la función actual 84 | 85 | Es muy usual en Golang usar =defer= para destruir o liberar cualquier 86 | tipo de recurso temporal que se obtenga en la función. 87 | 88 | #+begin_src go 89 | func doSomething() { 90 | a := getExternalResource(); 91 | defer a.Release() 92 | /// Resto de la función 93 | } 94 | #+end_src 95 | 96 | =defer= se ejecuta sin importar las causas por las que la función 97 | actual haya terminado, lo que garantiza en el ejemplo anterior que 98 | =a.Release()= siempre se ejecute. 99 | 100 | ** Panic y recover 101 | :PROPERTIES: 102 | :CUSTOM_ID: panic-y-recover 103 | :END: 104 | 105 | Golang incluye una función especial =panic= para indicar un error que no 106 | puede ser manejado de forma correcta. 107 | 108 | La contraparte de =panic= es la función =recover=, que verifica si 109 | ocurrió una llamada a =panic= en el contexto de la función actual. 110 | 111 | Si una llamada a =panic= no es seguida por un =recover=, la función 112 | termina y el contexto pasa al invocador. Esto continúa hasta que se 113 | encuentre un recover o se llegue a la función =main= en cuyo caso el 114 | programa se detendrá con un mensaje de error. 115 | 116 | #+REVEAL: split 117 | 118 | #+begin_src go 119 | func f() { 120 | defer func() { 121 | if r := recover(); r != nil { 122 | fmt.Println("Recovered in f", r) 123 | } 124 | }() 125 | fmt.Println("Calling g.") 126 | g(0) 127 | fmt.Println("Returned normally from g.") 128 | } 129 | 130 | func g(i int) { 131 | if i > 3 { 132 | fmt.Println("Panicking!") 133 | panic(fmt.Sprintf("%v", i)) 134 | } 135 | defer fmt.Println("Defer in g", i) 136 | fmt.Println("Printing in g", i) 137 | g(i + 1) 138 | } 139 | #+end_src 140 | 141 | ** Panic y recover no son try y catch 142 | :PROPERTIES: 143 | :CUSTOM_ID: panic-y-recover-no-son-try-y-catch 144 | :END: 145 | 146 | Uno de los errores más comunes para los que se inician en Golang es 147 | pensar en =panic= y =recover= como una alternativa a los bloques 148 | =try-catch= que aparecen en otros lenguajes. Esto es considerado una 149 | mala práctica. 150 | 151 | La función =panic= se debe utilizar solo para indicar estados en el 152 | flujo de una aplicación para los cuales no hay solución efectiva. Por 153 | otro lado =recover= debería utilizarse para liberar o destruir recursos 154 | adquiridos por la aplicación antes de hacer una salida forzosa. 155 | -------------------------------------------------------------------------------- /day-02/11-errors-defer-panic-recover/defer-recover/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | f() 7 | fmt.Println("Returned normally from f.") 8 | } 9 | 10 | func f() { 11 | defer func() { 12 | if r := recover(); r != nil { 13 | fmt.Println("Recovered in f", r) 14 | } 15 | }() 16 | fmt.Println("Calling g.") 17 | g(0) 18 | fmt.Println("Returned normally from g.") 19 | } 20 | 21 | func g(i int) { 22 | if i > 3 { 23 | fmt.Println("Panicking!") 24 | panic(fmt.Sprintf("%v", i)) 25 | } 26 | defer fmt.Println("Defer in g", i) 27 | fmt.Println("Printing in g", i) 28 | g(i + 1) 29 | } 30 | -------------------------------------------------------------------------------- /day-02/11-errors-defer-panic-recover/errors/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | var DivisionByZero = errors.New("Division por cero") 9 | 10 | func safedivide(a int, b int) (int, error) { 11 | if b == 0 { 12 | return 0, DivisionByZero 13 | } 14 | return a / b, nil 15 | } 16 | 17 | func main() { 18 | a, b := 1, 5 19 | c, err := safedivide(a, b) 20 | if err != nil { 21 | fmt.Println(err) 22 | } 23 | fmt.Printf("%d entre %d es %d \n", a, b, c) 24 | a, b = 10, 0 25 | c, err = safedivide(a, b) 26 | if err != nil { 27 | fmt.Println(err) 28 | } 29 | fmt.Printf("%d entre %d es %d \n", a, b, c) 30 | } 31 | -------------------------------------------------------------------------------- /day-02/12-testing/README.md.org: -------------------------------------------------------------------------------- 1 | * Pruebas unitarias 2 | :PROPERTIES: 3 | :CUSTOM_ID: pruebas-unitarias 4 | :END: 5 | 6 | Golang incluye en el paquete [[https://pkg.go.dev/testing][testing]] las herramientas necesarias para 7 | hacer pruebas unitarias. Por convención las pruebas unitarias se 8 | incluyen en el mismo paquete de la funcionalidad a probar, adicionando 9 | a los archivos el sufijo =_test=. 10 | 11 | Además de pruebas unitarias, Golang nos permite escribir pruebas de 12 | rendimiento o /benchmarks/ y ejemplos de prueba. 13 | 14 | ** Nuestra primera prueba unitaria. 15 | :PROPERTIES: 16 | :CUSTOM_ID: nuestra-primera-prueba-unitaria. 17 | :END: 18 | 19 | Si tenemos una función para calcular los números de fibonacci. 20 | 21 | #+begin_src go 22 | func Fibonacci(n int) int { 23 | if n < 2 { 24 | return n 25 | } 26 | return Fibonacci(n-1) + Fibonacci(n-2) 27 | } 28 | #+end_src 29 | 30 | *** Pruebas unitarias 31 | 32 | #+begin_src go 33 | func TestFibonacci(t *testing.T) { 34 | var in int = 10 35 | var want int = 55 36 | 37 | got := Fibonacci(in) 38 | if got != want { 39 | t.Errorf("Fibonacci(%d) == %d, want %d", in, got, want) 40 | } 41 | } 42 | #+end_src 43 | 44 | *** Pruebas de ejemplo 45 | 46 | 47 | Los ejemplos además de ejecutar pruebas, son una forma de documentar el 48 | uso de funcionalidades. 49 | 50 | #+begin_src go 51 | func ExampleFibonacci() { 52 | fmt.Println(Fibonacci(10)) 53 | // Output: 55 54 | } 55 | #+end_src 56 | 57 | #+REVEAL: split 58 | 59 | Tanto las pruebas unitarias como los ejemplos se ejecutan utilizando el 60 | comando =go test=. 61 | 62 | ** Benchmarks 63 | :PROPERTIES: 64 | :CUSTOM_ID: benchmarks 65 | :END: 66 | 67 | Un /benchmark/ o prueba de rendimiento nos permite examinar el desempeño 68 | de nuestras funcionalidades. 69 | 70 | #+begin_src go 71 | func BenchmarkFibonacci(b *testing.B) { 72 | for n := 0; n < b.N; n++ { 73 | Fibonacci(10) 74 | } 75 | } 76 | #+end_src 77 | 78 | ** Datos de pruebas o fixtures 79 | :PROPERTIES: 80 | :CUSTOM_ID: datos-de-pruebas-o-fixtures 81 | :END: 82 | 83 | Por convención, Golang ignora cualquier directorio que empiece con =.=, 84 | =_= o se llame =testdata=. Dado que las pruebas siempre se ejecutan en 85 | el directorio donde se encuentra el archivo =_test= podemos usar la 86 | función =Join= del paquete =path/filepath= para acceder a los datos de 87 | prueba. 88 | 89 | #+REVEAL: split 90 | 91 | #+begin_src go 92 | func TestBytesInFile(t *testing.T) { 93 | l, err := BytesInFile(filepath.Join("testdata", "hello_world")) 94 | if err != nil { 95 | t.Errorf("Error %s", err) 96 | t.FailNow() 97 | } 98 | if l != 12 { // New line at end of file 99 | t.Errorf("Got wrong number of bytes %d", l) 100 | } 101 | } 102 | #+end_src 103 | -------------------------------------------------------------------------------- /day-02/12-testing/functions.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | ) 7 | 8 | // Calculate a Fibonacci number 9 | func Fibonacci(n int) int { 10 | if n < 2 { 11 | return n 12 | } 13 | return Fibonacci(n-1) + Fibonacci(n-2) 14 | } 15 | 16 | func BytesInFile(filename string) (int, error) { 17 | f, err := os.Open(filename) 18 | defer f.Close() 19 | if err != nil { 20 | return 0, err 21 | } 22 | s, err := ioutil.ReadAll(f) 23 | if err != nil { 24 | return 0, err 25 | } 26 | return len(s), nil 27 | } 28 | -------------------------------------------------------------------------------- /day-02/12-testing/functions_test.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "testing" 7 | ) 8 | 9 | func TestFibonacci(t *testing.T) { 10 | var in int = 10 11 | var want int = 55 12 | 13 | got := Fibonacci(in) 14 | if got != want { 15 | t.Errorf("Fibonacci(%d) == %d, want %d", in, got, want) 16 | } 17 | } 18 | 19 | func BenchmarkFibonacci(b *testing.B) { 20 | for n := 0; n < b.N; n++ { 21 | Fibonacci(10) 22 | } 23 | } 24 | 25 | func ExampleFibonacci() { 26 | fmt.Println(Fibonacci(10)) 27 | // Output: 55 28 | } 29 | 30 | func TestBytesInFile(t *testing.T) { 31 | l, err := BytesInFile(filepath.Join("testdata", "hello_world")) 32 | if err != nil { 33 | t.Errorf("Error %s", err) 34 | t.FailNow() 35 | } 36 | if l != 12 { // New line at end of file 37 | t.Errorf("Got wrong number of bytes %d", l) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /day-02/12-testing/go.mod: -------------------------------------------------------------------------------- 1 | module sample13 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /day-02/12-testing/testdata/hello_world: -------------------------------------------------------------------------------- 1 | Hola Mundo 2 | -------------------------------------------------------------------------------- /day-02/13-structs-pointers/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // Address of an employee 6 | type Address struct { 7 | city, state string 8 | } 9 | 10 | func (a *Address) Print() string { 11 | return fmt.Sprintf("%s %s", a.city, a.state) 12 | } 13 | 14 | // Employee refers to an employee record 15 | type Employee struct { 16 | firstName string "el nombre" 17 | lastName string "el apellido" 18 | age int "la edad" 19 | Address "dirección embebida" 20 | } 21 | 22 | // Print will display a formatted employee record 23 | func (e *Employee) Print() { 24 | fmt.Println("Employee Record:") 25 | fmt.Println("Name:", e.firstName, e.lastName) 26 | // Call "inherited" method with same name 27 | fmt.Println("Address:", e.Address.Print()) 28 | } 29 | 30 | // updateAge will change age to the new value 31 | func (e *Employee) updateAge(newAge int) { 32 | e.age = newAge 33 | } 34 | 35 | func main() { 36 | emp := Employee{ 37 | firstName: "Peter", 38 | lastName: "Parker", 39 | age: 22, 40 | Address: Address{ 41 | city: "New York", 42 | state: "New York", 43 | }, 44 | } 45 | fmt.Println(emp) 46 | fmt.Println("City:", emp.city) //Printing nested field 47 | 48 | // Using struct method 49 | emp.Print() 50 | 51 | // Using a pointer receiver 52 | fmt.Println("Before:", emp.age) 53 | emp.updateAge(34) 54 | fmt.Println("After:", emp.age) 55 | } 56 | -------------------------------------------------------------------------------- /day-02/14-interfaces/README.md.org: -------------------------------------------------------------------------------- 1 | * Interfaces 2 | :PROPERTIES: 3 | :CUSTOM_ID: interfaces 4 | :END: 5 | 6 | ** Interfaces en Golang 7 | :PROPERTIES: 8 | :CUSTOM_ID: interfaces-en-golang 9 | :END: 10 | 11 | En Golang, una interfaz es un conjunto de firmas de métodos. Si un 12 | tipo tiene una definición para esos métodos, se dice que implementa la 13 | interfaz. A diferencia de otros lenguajes, /la asociación de un tipo 14 | con una interfaz es implicita/. 15 | 16 | Un tipo puede implementar más de una interfaz. 17 | 18 | #+begin_src go 19 | type Permanent struct { 20 | empID int 21 | basicPay int 22 | pension int 23 | } 24 | 25 | type Contract struct { 26 | empID int 27 | basicPay int 28 | } 29 | 30 | func (p Permanent) calculateSalary() int { 31 | return p.basicPay + p.pension 32 | } 33 | 34 | func (p Contract) calculateSalary() int { 35 | return p.basicPay 36 | } 37 | 38 | type SalaryCalculator interface { 39 | calculateSalary() int 40 | } 41 | 42 | // In main 43 | sc := [...]SalaryCalculator{Permanent{1, 5000, 50}, Contract{3, 7000}} 44 | totalPayout := 0 45 | for _, v := range sc { 46 | totalPayout += v.calculateSalary() 47 | } 48 | fmt.Println(totalPayout) 49 | #+end_src 50 | 51 | ** Interfaces anidadas. 52 | :PROPERTIES: 53 | :CUSTOM_ID: interfaces-anidadas. 54 | :END: 55 | 56 | Las interfaces al igual que las estructuras pueden anidarse. 57 | 58 | #+begin_src go 59 | type Reader interface { 60 | Read(p []byte) (n int, err error) 61 | } 62 | 63 | type Seeker interface { 64 | Seek(offset int64, whence int) (int64, error) 65 | } 66 | 67 | type ReadSeeker interface { 68 | Reader 69 | Seeker 70 | } 71 | #+end_src 72 | 73 | Para que un tipo implemente la interfaz =ReadSeeker= tiene que 74 | implementar =Read= y =Seeker= a la vez. 75 | 76 | ** Interfaces y composición. 77 | :PROPERTIES: 78 | :CUSTOM_ID: interfaces-y-composición. 79 | :END: 80 | 81 | De la misma forma que una estructura "hereda" los métodos de otra en 82 | la composición, implementa las interfaces de aquellas que la componen. 83 | 84 | #+begin_src go 85 | 86 | type Animal interface { 87 | Name() string 88 | } 89 | 90 | type Dog struct{} 91 | 92 | func (d *Dog) Name() string { 93 | return "Dog" 94 | } 95 | 96 | func Bark(d *Dog) { 97 | fmt.Println("Woof!") 98 | } 99 | 100 | type GuideDog struct { 101 | *Dog 102 | } 103 | #+end_src 104 | 105 | En el ejemplo anterior el tipo =GuidedDog= implementa la interfaz 106 | =Animal= 107 | 108 | ** La interfaz vacía 109 | :PROPERTIES: 110 | :CUSTOM_ID: la-interfaz-vacía 111 | :END: 112 | 113 | El tipo =interface{}= representa un valor comodín al estilo del tipo 114 | =object= de C# o =void *= en C (técnicamente todos los tipos 115 | implementan una interfaz sin métodos) 116 | 117 | #+begin_src go 118 | func describe(i interface{}) { 119 | fmt.Printf("(%v, %T)\n", i, i) 120 | } 121 | #+end_src 122 | 123 | ** Type Assertions. 124 | :PROPERTIES: 125 | :CUSTOM_ID: type-assertions 126 | :END: 127 | 128 | A veces nos es necesario saber de qué tipo es el valor guardado en la 129 | interfaz. 130 | 131 | #+begin_src go 132 | var i interface{} = "hello" 133 | s := i.(string) 134 | fmt.Println(s) 135 | 136 | s, ok := i.(string) 137 | fmt.Println(s, ok) 138 | 139 | f, ok := i.(float64) 140 | fmt.Println(f, ok) 141 | 142 | f = i.(float64) // panic 143 | fmt.Println(f) 144 | 145 | #+end_src 146 | 147 | La sintaxis =variable.(Tipo)= es un *type assertion*, concepto muy 148 | parecido al *type casting* de otros lenguajes. 149 | 150 | *** Type assertions seguras e inseguras 151 | 152 | Los type assertions se ejecutan con alguna de las siguientes variantes 153 | 154 | #+begin_src go 155 | // variante segura 156 | v, ok := i.(Tipo) 157 | // variante insegura 158 | v := i.(Tipo) 159 | #+end_src 160 | 161 | La variante segura retorna un par =(Tipo,bool)= donde el segundo 162 | valor representa el estado de la operación. Un estado de =true= 163 | significa que se pudo efectuar la conversión, =false= que la 164 | conversión no es posible y el primer valor de la tupla estará con el 165 | valor nulo. 166 | 167 | En la variante insegura si no se puede efectuar la conversión el 168 | /runtime/ de Golang lanzará un =panic=. 169 | 170 | ** Type switches 171 | :PROPERTIES: 172 | :CUSTOM_ID: type-switches 173 | :END: 174 | 175 | Los *type switches* son una construcción especial que nos permite 176 | determinar el tipo de una variable y actuar en consecuencia 177 | 178 | #+begin_src go 179 | switch v := v.(type) { 180 | case string: 181 | fmt.Printf("%v is a string\n", v) 182 | case int: 183 | fmt.Printf("%v is an int\n", v) 184 | default: 185 | fmt.Printf("The type of v is unknown\n") 186 | } 187 | #+end_src 188 | 189 | En lugar de usar la sintaxis =v.(Tipo)= para la conversión se utiliza 190 | =v.(type)=. 191 | -------------------------------------------------------------------------------- /day-02/14-interfaces/empty-interface.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var i interface{} 7 | describe(i) 8 | 9 | i = 42 10 | describe(i) 11 | 12 | i = "hello" 13 | describe(i) 14 | } 15 | 16 | func describe(i interface{}) { 17 | fmt.Printf("(%v, %T)\n", i, i) 18 | } 19 | -------------------------------------------------------------------------------- /day-02/14-interfaces/interface-values.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | type I interface { 9 | M() 10 | } 11 | 12 | type T struct { 13 | S string 14 | } 15 | 16 | func (t *T) M() { 17 | fmt.Println(t.S) 18 | } 19 | 20 | type F float64 21 | 22 | func (f F) M() { 23 | fmt.Println(f) 24 | } 25 | 26 | func main() { 27 | var i I 28 | 29 | i = &T{"Hello"} 30 | describe(i) 31 | i.M() 32 | 33 | i = F(math.Pi) 34 | describe(i) 35 | i.M() 36 | } 37 | 38 | func describe(i I) { 39 | fmt.Printf("(%v, %T)\n", i, i) 40 | } 41 | -------------------------------------------------------------------------------- /day-02/14-interfaces/interfaces-struct-composition.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Human struct{} 6 | 7 | type Animal interface { 8 | Name() string 9 | } 10 | 11 | type Dog struct{} 12 | 13 | func (d *Dog) Name() string { 14 | return "Dog" 15 | } 16 | 17 | func Bark(d *Dog) { 18 | fmt.Println("Woof!") 19 | } 20 | 21 | type GuideDog struct { 22 | *Dog 23 | } 24 | 25 | func (gd *GuideDog) Help(h *Human) { 26 | fmt.Printf("Hey human, grab %s’s leash!\n", gd.Name()) 27 | } 28 | 29 | type Cat struct{} 30 | 31 | func (c *Cat) Name() string { 32 | return "Cat" 33 | } 34 | 35 | type CatDog struct { 36 | *Cat 37 | *Dog 38 | } 39 | 40 | func (cd *CatDog) Name() string { 41 | return fmt.Sprintf("%s%s", cd.Cat.Name(), cd.Dog.Name()) 42 | } 43 | 44 | func main() { 45 | //1 - Interfaces 46 | var animal Animal 47 | animal = &Dog{} // returns a pointer to a new Dog 48 | fmt.Println(animal.Name()) // Dog 49 | 50 | //2 - Struct embedding 51 | gd := &GuideDog{} 52 | gd.Help(nil) // prints “Hey human, grab Dog’s leash!” 53 | 54 | //3 - Multi Struct embedding 55 | cd := &CatDog{} 56 | fmt.Printf("My favorite animal is the %s!\n", cd.Name()) 57 | } 58 | -------------------------------------------------------------------------------- /day-02/14-interfaces/interfaces.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // Permanent represent an employee with a permanent position 6 | type Permanent struct { 7 | empID int 8 | basicPay int 9 | pension int 10 | } 11 | 12 | // Contract represent an employee with a contractual position 13 | type Contract struct { 14 | empID int 15 | basicPay int 16 | } 17 | 18 | func (p Permanent) calculateSalary() int { 19 | return p.basicPay + p.pension 20 | } 21 | 22 | func (p Contract) calculateSalary() int { 23 | return p.basicPay 24 | } 25 | 26 | // SalaryCalculator calculates salary 27 | type SalaryCalculator interface { 28 | calculateSalary() int 29 | } 30 | 31 | func main() { 32 | sc := [...]SalaryCalculator{Permanent{1, 5000, 50}, Contract{3, 7000}} 33 | totalPayout := 0 34 | for _, v := range sc { 35 | totalPayout += v.calculateSalary() 36 | } 37 | fmt.Println("Total Salary:", totalPayout) 38 | } 39 | -------------------------------------------------------------------------------- /day-02/14-interfaces/type-assertions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var i interface{} = "hello" 7 | 8 | s := i.(string) 9 | fmt.Println(s) 10 | 11 | s, ok := i.(string) 12 | fmt.Println(s, ok) 13 | 14 | f, ok := i.(float64) 15 | fmt.Println(f, ok) 16 | 17 | f = i.(float64) // panic 18 | fmt.Println(f) 19 | } 20 | -------------------------------------------------------------------------------- /day-02/15-concurrency/README.md.org: -------------------------------------------------------------------------------- 1 | * Concurrencia en Go. 2 | :PROPERTIES: 3 | :CUSTOM_ID: concurrencia-en-go. 4 | :END: 5 | 6 | ** Gorutinas 7 | :PROPERTIES: 8 | :CUSTOM_ID: gorutinas 9 | :END: 10 | 11 | Las gorutinas son funciones o métodos que se ejecutan de modo 12 | concurrente. Para los efectos se pueden considerar como hilos o 13 | threads ligeros. 14 | 15 | Las gorutinas se crean usando la palabre clave =go= antes de la 16 | llamada a una función. 17 | 18 | #+begin_src go 19 | // llamar name como una gorutina 20 | go name() 21 | 22 | // gorutina anónima como 23 | go func() { 24 | // código 25 | }() 26 | #+end_src 27 | 28 | *** Sincronizando gorutinas 29 | 30 | Las gorutinas se ejecutan de modo *concurrente*. Para sincronizar 31 | gorutinas es común utilizar elementos de sincronización como 32 | =sync.WaitGroup=. 33 | 34 | #+begin_src go 35 | func main() { 36 | wg := &sync.WaitGroup{} 37 | wg.Add(4) 38 | for i:=0; i < 4; i++ { 39 | go worker(wg) 40 | } 41 | // Sin esto es posible que main termine 42 | // antes de las gorutinas. 43 | wg.Wait() 44 | } 45 | 46 | func worker(w *sync.WaitGroup) { 47 | defer wg.Done() 48 | DoWork() 49 | } 50 | #+end_src 51 | 52 | ** Canales 53 | :PROPERTIES: 54 | :CUSTOM_ID: canales 55 | :END: 56 | 57 | Los canales son un mecanismo para sincronización y comunicación entre 58 | gorutinas. Los usuarios de sistemas basados en Unix pueden pensar en 59 | canales como /pipes/. 60 | 61 | Para crear un canal usamos la función =make=. 62 | 63 | #+begin_src go 64 | c := make(chan string) // canal sin buffer 65 | cb := make(chan string, 200) // canal con buffer de 200 66 | #+end_src 67 | 68 | *** Tipos de canales 69 | 70 | La comunicación vía canales en síncrona. Cuando se envían datos a un 71 | canal la gorutina se bloquea hasta que estos datos sean recibidos. Lo 72 | mismo ocurre si se intenta recibir sin haber datos disponibles. 73 | 74 | Los canales con buffer permiten acumular datos enviados y solo bloquean si 75 | 76 | 1. Se han enviado =n= datos, donde =n= es el tamaño del buffer. 77 | 2. Se intenta recibir desde el canal y el buffer no contiene datos. 78 | 79 | *** Terminando la comunicación. 80 | 81 | Para cerrar un canal se utiliza =close=. Un canal cerrado no puede 82 | usarse para enviar datos. Recibir datos de un canal cerrado siempre 83 | retornará el /valor cero/ del tipo de datos del canal. 84 | 85 | #+begin_src go 86 | // Enviar 87 | c <- "Hola" 88 | // Recibir 89 | v := <- c 90 | // Recibir mientras el canal esté abierto 91 | for v := range c { 92 | processValue(v) 93 | } 94 | #+end_src 95 | 96 | *** Multiplexing 97 | 98 | La sentencia =select= nos permite esperar por el resultado de más de 99 | un canal. 100 | 101 | #+begin_src go 102 | // Esperamos por mensaje o timeout 103 | for { 104 | select { 105 | case v := <-input: 106 | doOperation(v) 107 | // Canal de tipo <- chan Time 108 | case <- time.After(time.Duration(80) * time.Millisecond): 109 | fmt.Println("Timeout!") 110 | return results 111 | } 112 | } 113 | #+end_src 114 | 115 | ** Referencias 116 | :PROPERTIES: 117 | :CUSTOM_ID: referencias 118 | :END: 119 | 120 | 1. [[https://blog.golang.org/waza-talk][Concurrency is Not Parallelism]] 121 | 2. [[https://tour.golang.org/concurrency/1][Gorutines - A Tour of Go]] 122 | 3. [[https://tour.golang.org/concurrency/2][Channels - A Tour of Go]] 123 | 4. [[https://tour.golang.org/concurrency/3][Buffered Channels - A Tour of Go]] 124 | -------------------------------------------------------------------------------- /day-02/15-concurrency/concurrency/go.mod: -------------------------------------------------------------------------------- 1 | module sample15 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /day-02/15-concurrency/concurrency/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | numPiecesOfWork := 20 11 | numWorkers := 5 12 | workCh := make(chan int) 13 | wg := &sync.WaitGroup{} 14 | wg.Add(numWorkers) 15 | for i := 0; i < numWorkers; i++ { 16 | go worker(workCh, wg) 17 | } 18 | for i := 0; i < numPiecesOfWork; i++ { 19 | work := i % 10 20 | workCh <- work 21 | } 22 | close(workCh) 23 | v := <-workCh 24 | fmt.Println(v) 25 | wg.Wait() 26 | fmt.Println("done") 27 | } 28 | 29 | func worker(workCh <-chan int, wg *sync.WaitGroup) { 30 | defer wg.Done() 31 | for work := range workCh { 32 | doWork(work) 33 | } 34 | } 35 | 36 | func doWork(work int) { 37 | time.Sleep(time.Duration(work) * time.Millisecond) 38 | fmt.Println("slept for", work, "milliseconds") 39 | } 40 | -------------------------------------------------------------------------------- /day-03/16-json/README.md.org: -------------------------------------------------------------------------------- 1 | * Formatos de datos: JSON y XML 2 | :PROPERTIES: 3 | :CUSTOM_ID: formatos-de-datos-json-y-xml 4 | :END: 5 | 6 | ** El paquete =encoding=. 7 | :PROPERTIES: 8 | :CUSTOM_ID: encoding 9 | :END: 10 | 11 | El paquete =encoding= define interfaces para convertir datos 12 | desde/hacia bytes o representaciones de texto. 13 | 14 | - =ascii85= Formato utilizado por Adobe PDF y Postcript. 15 | - =asn1= Estructuras ASN.1 en formato DER. 16 | - =base32= Codificación Base32, RFC 4648. 17 | - =base64= Codificación Base64, RFC 4648. 18 | - =binary= Traducción entre formatos numéricos y codificación a bytes de los mismos. 19 | - =csv= Archivos con valores separados por coma (CSV). 20 | - =gob= Transmisión de flujos de datos entre emisor y receptor () 21 | - =hex= Valores hexadecimales. 22 | - =json= JSON como se define en la RFC 7159. 23 | - =pem= Formato PEM usado actualmente para representar llaves TLS y certificados. 24 | - =xml= Parser XML 1.0 con soporte para espacios de nombres. 25 | 26 | ** Serializando a JSON (Marshal). 27 | :PROPERTIES: 28 | :CUSTOM_ID: serializacion 29 | :END: 30 | 31 | =json.Marshal= codifica tipos Golang en JSON 32 | 33 | - =bool= como JSON Boolean. 34 | - valores numéricos como JSON Number. 35 | - =string= como JSON String, eliminando etiquetas HTML. 36 | - arreglos y /slices/ como JSON Array. 37 | - =struct= como JSON Object. 38 | - =nil= como =null= 39 | - =chan=, complex y literales de funciones provocan error. 40 | 41 | *** Serializando estructuras 42 | 43 | Las etiquetas definen como se serializan las estructuras. 44 | 45 | #+begin_src go 46 | type Address struct { 47 | // omitempty hace que los campos vacíos no 48 | // no se serialice. 49 | City string `json:"ciudad,omitempty"` 50 | State string `json:"estado,omitempty"` 51 | // Campos marcados con - no se serializan 52 | Zip strung `json:"-"` 53 | } 54 | 55 | type Employee struct { 56 | FirstName string `json:"nombre"` 57 | LastName string `json:"apellido"` 58 | Age int `json:"edad"` 59 | // no se serializa porque no es exportado 60 | id string 61 | Address 62 | } 63 | #+end_src 64 | 65 | #+REVEAL: split 66 | 67 | #+begin_src go 68 | emp1 := Employee{"Peter", "Parker", 22, Address{"Manhattan", "New York"}} 69 | d, _ := json.Marshal(&emp1) 70 | fmt.Println(string(d)) 71 | #+end_src 72 | 73 | ** Deserializando desde JSON (Unmarshal). 74 | :PROPERTIES: 75 | :CUSTOM_ID: deserializacion 76 | :END: 77 | 78 | =json.Unmarshal= es la contraparte de =json.Marshal=. 79 | 80 | #+begin_src go 81 | tony := `{"nombre":"Tony","apellido":"Stark","edad":44}` 82 | var emp3 Employee 83 | err = json.Unmarshal([]byte(tony), &emp3) 84 | if err != nil { 85 | log.Fatal("No se pudo deserializar") 86 | } 87 | #+end_src 88 | 89 | *** Consideraciones. 90 | 91 | 1. =Unmarshal= a un slice elimina los valores del slice. 92 | 2. =Unmarshal= a un array descarta los valores extra si el array 93 | destino es muy pequeño. 94 | 3. =Unmarshal= a un =map= conserva las llaves existentes. 95 | 4. =null= se traduce a =nil= o al valor que tenga el tipo sin 96 | inicializar 97 | -------------------------------------------------------------------------------- /day-03/16-json/json-ser-de/go.mod: -------------------------------------------------------------------------------- 1 | module sample16 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /day-03/16-json/json-ser-de/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | ) 8 | 9 | type Address struct { 10 | City string `json:"ciudad,omitempty"` 11 | State string `json:"estado,omitempty"` 12 | } 13 | 14 | type Employee struct { 15 | FirstName string `json:"nombre"` 16 | LastName string `json:"apellido"` 17 | Age int `json:"edad"` 18 | Address 19 | } 20 | 21 | func main() { 22 | emp1 := Employee{"Peter", "Parker", 22, Address{"Manhattan", "New York"}} 23 | emp2 := Employee{"Hulk", "Banner", 22, Address{}} 24 | d, err := json.Marshal(&emp1) 25 | if err != nil { 26 | log.Fatal("No se pudo serializar") 27 | } 28 | fmt.Println(string(d)) 29 | d, err = json.Marshal(&emp2) 30 | if err != nil { 31 | log.Fatal("No se pudo serializar") 32 | } 33 | fmt.Println(string(d)) 34 | tony := `{"nombre":"Tony","apellido":"Stark","edad":44}` 35 | var emp3 Employee 36 | err = json.Unmarshal([]byte(tony), &emp3) 37 | if err != nil { 38 | log.Fatal("No se pudo deserializar") 39 | } 40 | fmt.Println(emp3) 41 | } 42 | -------------------------------------------------------------------------------- /day-03/17-rest-api/README.md.org: -------------------------------------------------------------------------------- 1 | * Creando APIs REST 2 | :PROPERTIES: 3 | :CUSTOM_ID: creando-apis-rest 4 | :END: 5 | 6 | ** Servidor HTTP. 7 | :PROPERTIES: 8 | :CUSTOM_ID: servidor-http. 9 | :END: 10 | 11 | El paquete =net/http= contiene implementaciones de servidor y cliente 12 | para este protocolo. Crear un programa que responda a peticiones HTTP es 13 | una tarea sencilla. 14 | 15 | #+begin_src go 16 | func main() { 17 | http.HandleFunc("/hello", func(w http.ResponseWWriter, r *http.Request) { 18 | fmt.Fprintf(w, "Hello world") 19 | }) 20 | log.Fatal(http.ListenAndServe(":8080", nil)) 21 | } 22 | #+end_src 23 | 24 | Las estructuras =Server= y =ServeMux= son las encargadas de operar el 25 | servidor y el ciclo de petición y respuesta. La variable 26 | =DefaultServeMux= es utilizada por el servidor por defecto 27 | 28 | ** Creando nuestro propio ServeMux 29 | :PROPERTIES: 30 | :CUSTOM_ID: creando-nuestro-propio-servemux 31 | :END: 32 | 33 | Usar =DefaultServeMux= tiene sentido para proyectos pequeños, pero lo 34 | usual es que utilicemos nuestra propio =ServeMux= 35 | 36 | #+begin_src go 37 | mux := http.NewServeMux() 38 | mux.HandleFunc("/hello", func(w http.ResponseWriter, req *http.Request) { 39 | fmt.Fprintf(w, "Hello world") 40 | }) 41 | log.Fatal(http.ListenAndServe(":8080", mux)) 42 | #+end_src 43 | 44 | Alternativamente podemos crear nuestro propio =Server=. 45 | 46 | #+begin_src go 47 | // Create a server listening on port 8000 48 | s := &http.Server{ 49 | Addr: ":8000", 50 | Handler: mux, 51 | } 52 | 53 | // Continue to process new requests until an error occurs 54 | log.Fatal(s.ListenAndServe()) 55 | #+end_src 56 | 57 | ** APIS REST con ServeMux. 58 | :PROPERTIES: 59 | :CUSTOM_ID: apis-rest-con-servemux. 60 | :END: 61 | 62 | Tanto =Server= como =ServeMux= proveen el nivel mínimo de abstracción 63 | necesario para manejar peticiones HTTP. 64 | 65 | Tomemos por ejemplo el código para hacer un API REST sencilla 66 | 67 | #+begin_src go 68 | type H = func(w http.ResponseWriter, r *http.Request) 69 | 70 | func dispatchResource(get, post, put, delete H) H { 71 | return func(w http.ResponseWriter, r *http.Request) { 72 | w.Header().Set("Content-Type", "application/json") 73 | switch r.Method { 74 | case "GET": 75 | get(w, r) 76 | case "POST": 77 | post(w, r) 78 | case "PUT": 79 | put(w, r) 80 | case "DELETE": 81 | delete(w, r) 82 | default: 83 | w.WriteHeader(http.StatusNotFound) 84 | w.Write([]byte(`{"message": "not found"}`)) 85 | } 86 | } 87 | } 88 | 89 | func main() { 90 | // initialize mux 91 | getFunc := func(w http.ResponseWriter, r *http.Request) { 92 | w.WriteHeader(http.StatusOK) 93 | w.Write([]byte(`{"message": "get called"}`)) 94 | } 95 | postFunc := func(w http.ResponseWriter, r *http.Request) { 96 | w.WriteHeader(http.StatusCreated) 97 | w.Write([]byte(`{"message": "post called"}`)) 98 | } 99 | m.HandleFunc("/resource", dispatchResource(getFunc, postFunc, nil, nil)) 100 | // use mux 101 | } 102 | #+end_src 103 | 104 | ** Referencias 105 | :PROPERTIES: 106 | :CUSTOM_ID: referencias 107 | :END: 108 | 109 | - [[https://pkg.go.dev/net/http][Documentación de net/http]] 110 | - [[https://github.com/gorilla/mux][Gorilla Mux]] 111 | - [[https://github.com/gin-gonic/gin][Gin]] 112 | -------------------------------------------------------------------------------- /day-03/17-rest-api/hello-world-api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | http.HandleFunc("/hello", func(w http.ResponseWWriter, r *http.Request) { 10 | fmt.Fprintf(w, "Hello world") 11 | }) 12 | log.Fatal(http.ListenAndServe(":8080", nil)) 13 | } 14 | -------------------------------------------------------------------------------- /day-03/17-rest-api/helloworld-api-gorilla/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/gorilla/mux" 9 | ) 10 | 11 | func helloWorld(w http.ResponseWriter, r *http.Request) { 12 | fmt.Fprintf(w, "Hello World") 13 | } 14 | 15 | func main() { 16 | router := mux.NewRouter().StrictSlash(true) 17 | router.HandleFunc("/", helloWorld) 18 | log.Fatal(http.ListenAndServe(":8080", router)) 19 | } 20 | -------------------------------------------------------------------------------- /day-03/17-rest-api/rest-api-gorilla/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | type data struct { 14 | Num int `json:"Num"` 15 | Text string `json:"Text"` 16 | } 17 | 18 | func create(w http.ResponseWriter, r *http.Request) { 19 | var newData data 20 | reqBody, err := ioutil.ReadAll(r.Body) 21 | if err != nil { 22 | fmt.Fprintf(w, err.Error()) 23 | } 24 | 25 | json.Unmarshal(reqBody, &newData) 26 | 27 | w.WriteHeader(http.StatusCreated) 28 | 29 | json.NewEncoder(w).Encode(newData) 30 | } 31 | 32 | func getOne(w http.ResponseWriter, r *http.Request) { 33 | fmt.Fprintf(w, "The item with %v has been requested", mux.Vars(r)["num"]) 34 | } 35 | 36 | func getAll(w http.ResponseWriter, r *http.Request) { 37 | fmt.Fprintf(w, "The all items has been requested") 38 | } 39 | 40 | func update(w http.ResponseWriter, r *http.Request) { 41 | var updatedData data 42 | reqBody, err := ioutil.ReadAll(r.Body) 43 | if err != nil { 44 | fmt.Fprintf(w, err.Error()) 45 | } 46 | 47 | json.Unmarshal(reqBody, &updatedData) 48 | 49 | json.NewEncoder(w).Encode(updatedData) 50 | } 51 | 52 | func delete(w http.ResponseWriter, r *http.Request) { 53 | fmt.Fprintf(w, "The item with %v has been deleted successfully", mux.Vars(r)["num"]) 54 | } 55 | 56 | func main() { 57 | router := mux.NewRouter().StrictSlash(true) 58 | 59 | router.HandleFunc("/api", create).Methods("POST") 60 | router.HandleFunc("/api", getAll).Methods("GET") 61 | router.HandleFunc("/api/{num}", getOne).Methods("GET") 62 | router.HandleFunc("/api/{num}", update).Methods("PATCH") 63 | router.HandleFunc("/api/{num}", delete).Methods("DELETE") 64 | 65 | log.Fatal(http.ListenAndServe(":8080", router)) 66 | } 67 | -------------------------------------------------------------------------------- /day-03/17-rest-api/rest-api/go.mod: -------------------------------------------------------------------------------- 1 | module sample17 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /day-03/17-rest-api/rest-api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | type H = func(w http.ResponseWriter, r *http.Request) 9 | 10 | func dispatchResource(get, post, put, delete H) H { 11 | return func(w http.ResponseWriter, r *http.Request) { 12 | w.Header().Set("Content-Type", "application/json") 13 | switch r.Method { 14 | case "GET": 15 | get(w, r) 16 | case "POST": 17 | post(w, r) 18 | case "PUT": 19 | put(w, r) 20 | case "DELETE": 21 | delete(w, r) 22 | default: 23 | w.WriteHeader(http.StatusNotFound) 24 | w.Write([]byte(`{"message": "not found"}`)) 25 | } 26 | } 27 | } 28 | 29 | func main() { 30 | 31 | // Create a mux for routing incoming requests 32 | m := http.NewServeMux() 33 | 34 | getFunc := func(w http.ResponseWriter, r *http.Request) { 35 | w.WriteHeader(http.StatusOK) 36 | w.Write([]byte(`{"message": "get called"}`)) 37 | } 38 | postFunc := func(w http.ResponseWriter, r *http.Request) { 39 | w.WriteHeader(http.StatusCreated) 40 | w.Write([]byte(`{"message": "post called"}`)) 41 | } 42 | m.HandleFunc("/resource", dispatchResource(getFunc, postFunc, nil, nil)) 43 | 44 | // Create a server listening on port 8000 45 | s := &http.Server{ 46 | Addr: ":8000", 47 | Handler: m, 48 | } 49 | 50 | // Continue to process new requests until an error occurs 51 | log.Fatal(s.ListenAndServe()) 52 | } 53 | -------------------------------------------------------------------------------- /day-03/18-http-client/README.md.org: -------------------------------------------------------------------------------- 1 | * HTTP Client 2 | :PROPERTIES: 3 | :CUSTOM_ID: http-client 4 | :END: 5 | 6 | El paquete [[https://golang.org/pkg/net/http/][http]] contiene una implementacion de un cliente http que 7 | permite emular las acciones que realiza un web browser. 8 | 9 | Para este ejercicio se utiliza el tester HTTP https://httpbin.org 10 | ([[https://github.com/postmanlabs/httpbin][codigo fuente]], [[https://hub.docker.com/r/kennethreitz/httpbin/][imagen de Docker para uso local]]). 11 | 12 | ** Ejecutando un GET 13 | :PROPERTIES: 14 | :CUSTOM_ID: ejecutando-un-get 15 | :END: 16 | 17 | #+begin_src go 18 | resp, _ := http.Get("https://httpbin.org/get") 19 | defer resp.Body.Close() 20 | 21 | data, _ := ioutil.ReadAll(resp.Body) 22 | fmt.Println(string(data)) 23 | #+end_src 24 | 25 | ** Ejecutando un POST 26 | :PROPERTIES: 27 | :CUSTOM_ID: ejecutando-un-post 28 | :END: 29 | 30 | #+begin_src go 31 | payload := "Hello world!" 32 | resp, _ := http.Post("https://httpbin.org/post", "text/plain", strings.NewReader(payload)) 33 | defer resp.Body.Close() 34 | 35 | data, _ := ioutil.ReadAll(resp.Body) 36 | fmt.Println(string(data)) 37 | #+end_src 38 | 39 | Notar que si obtenemos un error de =http.Get= o =http.Post= debemos 40 | cerrar el body (utilizando =defer=). 41 | 42 | ** HTTP request timeouts 43 | :PROPERTIES: 44 | :CUSTOM_ID: http-request-timeouts 45 | :END: 46 | 47 | Podemos especificar un tiempo maximo de espera para que un request que 48 | tarda mucho termine automaticamente. 49 | 50 | Crear un nuevo objeto request: 51 | 52 | #+begin_src go 53 | req, _ := http.NewRequest(http.MethodGet, url, nil) 54 | #+end_src 55 | 56 | Crear un =context= con timeout: 57 | 58 | #+begin_src go 59 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 60 | defer cancel() 61 | #+end_src 62 | 63 | Ejecutar el request pasando por parametro el =context= con el timeout: 64 | 65 | #+begin_src go 66 | resp, _ := http.DefaultClient.Do(req.WithContext(ctx)) 67 | defer resp.Body.Close() 68 | #+end_src 69 | -------------------------------------------------------------------------------- /day-03/18-http-client/go.mod: -------------------------------------------------------------------------------- 1 | module sample18 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /day-03/18-http-client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | func httpGet(url string) (string, error) { 14 | resp, err := http.Get(url) 15 | if err != nil { 16 | return "", fmt.Errorf("HTTP error: %s", err) 17 | } 18 | defer resp.Body.Close() 19 | 20 | if resp.StatusCode != http.StatusOK { 21 | return "", fmt.Errorf("expected status OK but got %q instead", resp.Status) 22 | } 23 | 24 | data, err := ioutil.ReadAll(resp.Body) 25 | if err != nil { 26 | return "", fmt.Errorf("read error: %s\n", err) 27 | } 28 | 29 | return string(data), nil 30 | } 31 | 32 | func httpPost(url string) (string, error) { 33 | payload := "Hello world!" 34 | resp, err := http.Post(url, "text/plain", strings.NewReader(payload)) 35 | if err != nil { 36 | return "", fmt.Errorf("HTTP error: %s", err) 37 | } 38 | defer resp.Body.Close() 39 | 40 | if resp.StatusCode != http.StatusOK { 41 | return "", fmt.Errorf("expected status OK but got %q instead", resp.Status) 42 | } 43 | 44 | data, err := ioutil.ReadAll(resp.Body) 45 | if err != nil { 46 | return "", fmt.Errorf("read error: %s\n", err) 47 | } 48 | 49 | return string(data), nil 50 | } 51 | 52 | func getUserAgent(jsonData string) (string, error) { 53 | type response struct { 54 | Headers struct { 55 | UserAgent string `json:"User-Agent"` 56 | } `json:"headers"` 57 | } 58 | 59 | var resp response 60 | err := json.Unmarshal([]byte(jsonData), &resp) 61 | if err != nil { 62 | return "", fmt.Errorf("json parsing error: %s\n", err) 63 | } 64 | 65 | return resp.Headers.UserAgent, nil 66 | } 67 | 68 | func getWithTimeout(url string, timeout time.Duration) (string, error) { 69 | req, err := http.NewRequest(http.MethodGet, url, nil) 70 | if err != nil { 71 | return "", fmt.Errorf("envalid URL: %s", err) 72 | } 73 | 74 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 75 | defer cancel() 76 | 77 | resp, err := http.DefaultClient.Do(req.WithContext(ctx)) 78 | if err != nil { 79 | return "", fmt.Errorf("HTTP error: %s", err) 80 | } 81 | defer resp.Body.Close() 82 | 83 | if resp.StatusCode != http.StatusOK { 84 | return "", fmt.Errorf("expected status OK but got %q instead", resp.Status) 85 | } 86 | 87 | data, err := ioutil.ReadAll(resp.Body) 88 | if err != nil { 89 | return "", fmt.Errorf("read error: %s\n", err) 90 | } 91 | 92 | return string(data), nil 93 | } 94 | 95 | func main() { 96 | ////////////// 97 | // HTTP GET // 98 | ////////////// 99 | 100 | payload, err := httpGet("https://httpbin.org/get") 101 | if err != nil { 102 | fmt.Printf("GET error: %s\n", err) 103 | } 104 | 105 | fmt.Println("GET response:") 106 | fmt.Println(payload) 107 | 108 | /////////////// 109 | // HTTP POST // 110 | /////////////// 111 | 112 | payload, err = httpPost("https://httpbin.org/post") 113 | if err != nil { 114 | fmt.Printf("POST error: %s\n", err) 115 | } 116 | 117 | fmt.Println("POST response:") 118 | fmt.Println(payload) 119 | 120 | //////////////////// 121 | // Get User Agent // 122 | //////////////////// 123 | 124 | payload, err = httpGet("https://httpbin.org/headers") 125 | if err != nil { 126 | fmt.Printf("GET error: %s\n", err) 127 | } 128 | 129 | userAgent, err := getUserAgent(payload) 130 | if err != nil { 131 | fmt.Printf("Failed to extract user agent: %s\n", err) 132 | } 133 | 134 | fmt.Printf("Request user agent: %s\n\n", userAgent) 135 | 136 | ////////////////////// 137 | // Get with timeout // 138 | ////////////////////// 139 | 140 | httpbinTimeout := 5 141 | httpTimeout := 10 * time.Second 142 | payload, err = getWithTimeout( 143 | fmt.Sprintf("https://httpbin.org/delay/%d", httpbinTimeout), 144 | httpTimeout, 145 | ) 146 | if err != nil { 147 | fmt.Printf("GET with timeout error: %s\n", err) 148 | } 149 | 150 | fmt.Println("GET with timeout response:") 151 | fmt.Println(payload) 152 | } 153 | -------------------------------------------------------------------------------- /day-04/challenge1/1.1/README.md.org: -------------------------------------------------------------------------------- 1 | * Challenge 1.1 2 | :PROPERTIES: 3 | :CUSTOM_ID: challenge-1.1 4 | :END: 5 | Crear una funcion /Reverse/ que devuelva un string pasado por parametro. 6 | -------------------------------------------------------------------------------- /day-04/challenge1/1.1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Printf(Reverse("!oG ,olleH")) 7 | } 8 | 9 | func Reverse(s string) string { 10 | r := []rune(s) 11 | for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { 12 | r[i], r[j] = r[j], r[i] 13 | } 14 | return string(r) 15 | } 16 | -------------------------------------------------------------------------------- /day-04/challenge1/1.2/README.md.org: -------------------------------------------------------------------------------- 1 | * Challenge 1.2 2 | :PROPERTIES: 3 | :CUSTOM_ID: challenge-1.2 4 | :END: 5 | Modificar la funcion /Reverse/ para invertir el /casing/ de las vocales 6 | (solamente). 7 | -------------------------------------------------------------------------------- /day-04/challenge1/1.2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Printf(Reverse("!oG ,olleH")) 7 | } 8 | 9 | func Reverse(s string) string { 10 | r := []rune(s) 11 | for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { 12 | r[i], r[j] = ChangeVowelCase(r[j]), ChangeVowelCase(r[i]) 13 | } 14 | return string(r) 15 | } 16 | 17 | func ChangeVowelCase(s rune) rune { 18 | var ret string = string(s) 19 | 20 | switch ret { 21 | case "A": 22 | ret = "a" 23 | case "E": 24 | ret = "e" 25 | case "I": 26 | ret = "i" 27 | case "O": 28 | ret = "o" 29 | case "U": 30 | ret = "u" 31 | case "a": 32 | ret = "A" 33 | case "e": 34 | ret = "E" 35 | case "i": 36 | ret = "I" 37 | case "o": 38 | ret = "O" 39 | case "u": 40 | ret = "U" 41 | } 42 | 43 | return []rune(ret)[0] 44 | } 45 | -------------------------------------------------------------------------------- /day-04/challenge1/1.3/README.md.org: -------------------------------------------------------------------------------- 1 | * Challenge 1.3 2 | :PROPERTIES: 3 | :CUSTOM_ID: challenge-1.3 4 | :END: 5 | Crear un paquete /stringutil/ que contega la funcion /Reverse/ y se 6 | utilice en funcion la /main/. 7 | -------------------------------------------------------------------------------- /day-04/challenge1/1.3/reverseapp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "stringutil" 6 | ) 7 | 8 | func main() { 9 | fmt.Printf(stringutil.Reverse("!oG ,olleH")) 10 | } 11 | -------------------------------------------------------------------------------- /day-04/challenge1/1.3/stringutil/reverse.go: -------------------------------------------------------------------------------- 1 | package stringutil 2 | 3 | func Reverse(s string) string { 4 | r := []rune(s) 5 | for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { 6 | r[i], r[j] = r[j], r[i] 7 | } 8 | return string(r) 9 | } 10 | -------------------------------------------------------------------------------- /day-04/challenge1/1.4/README.md.org: -------------------------------------------------------------------------------- 1 | * Challenge 1.4 2 | :PROPERTIES: 3 | :CUSTOM_ID: challenge-1.4 4 | :END: 5 | Cambiar el programa para que el parametro string de la funcion /Reverse/ 6 | se obtenga desde la linea de comandos con un flag llamado /text/. 7 | -------------------------------------------------------------------------------- /day-04/challenge1/1.4/reverseapp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "stringutil" 7 | ) 8 | 9 | func main() { 10 | in := flag.String("text", "!oG ,olleH", "a string") 11 | flag.Parse() 12 | 13 | fmt.Printf(stringutil.Reverse(*in)) 14 | } 15 | -------------------------------------------------------------------------------- /day-04/challenge1/1.4/stringutil/reverse.go: -------------------------------------------------------------------------------- 1 | package stringutil 2 | 3 | func Reverse(s string) string { 4 | r := []rune(s) 5 | for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { 6 | r[i], r[j] = r[j], r[i] 7 | } 8 | return string(r) 9 | } 10 | -------------------------------------------------------------------------------- /day-04/challenge1/1.5/README.md.org: -------------------------------------------------------------------------------- 1 | * Challenge 1.5 2 | :PROPERTIES: 3 | :CUSTOM_ID: challenge-1.5 4 | :END: 5 | Crear un /test/, un /benchmark/, y un /example/ para la funcion 6 | /Reverse/. 7 | -------------------------------------------------------------------------------- /day-04/challenge1/1.5/reverseapp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "stringutil" 6 | ) 7 | 8 | func main() { 9 | fmt.Printf(stringutil.Reverse("!oG ,olleH")) 10 | } 11 | -------------------------------------------------------------------------------- /day-04/challenge1/1.5/stringutil/reverse.go: -------------------------------------------------------------------------------- 1 | // Additional string funcions 2 | package stringutil 3 | 4 | // Return the reverse of the input parameter string 5 | func Reverse(s string) string { 6 | r := []rune(s) 7 | for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { 8 | r[i], r[j] = r[j], r[i] 9 | } 10 | return string(r) 11 | } 12 | -------------------------------------------------------------------------------- /day-04/challenge1/1.5/stringutil/reverse_test.go: -------------------------------------------------------------------------------- 1 | package stringutil 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestReverse(t *testing.T) { 9 | cases := []struct { 10 | in, want string 11 | }{ 12 | {"Hello, world", "dlrow ,olleH"}, 13 | {"Hello, 世界", "界世 ,olleH"}, 14 | {"", ""}, 15 | } 16 | for _, c := range cases { 17 | got := Reverse(c.in) 18 | if got != c.want { 19 | t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want) 20 | } 21 | } 22 | } 23 | 24 | func BenchmarkReverse(b *testing.B) { 25 | for n := 0; n < b.N; n++ { 26 | Reverse("Hello, world") 27 | } 28 | } 29 | 30 | func ExampleReverse() { 31 | fmt.Println(Reverse("Hello, world")) 32 | // Output: dlrow ,olleH 33 | } 34 | -------------------------------------------------------------------------------- /day-04/challenge2/2.1/README.md.org: -------------------------------------------------------------------------------- 1 | * Challenge 2.1 2 | :PROPERTIES: 3 | :CUSTOM_ID: challenge-2.1 4 | :END: 5 | Modelar la funcionalidad de un sistema de precios para una aerolinea que 6 | calcule los ingresos netos de un vuelo en base a los pasajeros y el 7 | precio base del ticket. 8 | 9 | El precio base del ticket es el mismo para todos los pasajeros. 10 | 11 | Existen 3 tipos de pasajeros: * Base: Paga el 100% del precio base. * De 12 | ultimo minuto: Paga el 50% del precio base. * Empleado de la aerolinea: 13 | No paga el ticket. 14 | -------------------------------------------------------------------------------- /day-04/challenge2/2.1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Passenger interface { 6 | GetDiscount() float32 7 | } 8 | 9 | type BasePassenger struct { 10 | } 11 | 12 | func (*BasePassenger) GetDiscount() float32 { 13 | return 0 14 | } 15 | 16 | type LastMinutePassenger struct { 17 | } 18 | 19 | func (*LastMinutePassenger) GetDiscount() float32 { 20 | return 0.5 21 | } 22 | 23 | type EmployeeAirlinePassenger struct { 24 | } 25 | 26 | func (*EmployeeAirlinePassenger) GetDiscount() float32 { 27 | return 1 28 | } 29 | 30 | func NetIncome(passengers []Passenger, baseTicketPrice float32) float32 { 31 | var total float32 32 | for _, p := range passengers { 33 | total += baseTicketPrice - p.GetDiscount()*baseTicketPrice 34 | } 35 | return total 36 | } 37 | 38 | func main() { 39 | passengers := [...]Passenger{&BasePassenger{}, &LastMinutePassenger{}, &EmployeeAirlinePassenger{}} 40 | fmt.Println(NetIncome(passengers[:], 1000)) 41 | } 42 | -------------------------------------------------------------------------------- /day-04/challenge2/2.2/README.md.org: -------------------------------------------------------------------------------- 1 | * Challenge 2.2 2 | :PROPERTIES: 3 | :CUSTOM_ID: challenge-2.2 4 | :END: 5 | Crear un test para verificar el codigo creado en el challenge 2.1 6 | anterior. 7 | -------------------------------------------------------------------------------- /day-04/challenge2/2.2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Passenger interface { 6 | GetDiscount() float32 7 | } 8 | 9 | type BasePassenger struct { 10 | } 11 | 12 | func (*BasePassenger) GetDiscount() float32 { 13 | return 0 14 | } 15 | 16 | type LastMinutePassenger struct { 17 | } 18 | 19 | func (*LastMinutePassenger) GetDiscount() float32 { 20 | return 0.5 21 | } 22 | 23 | type EmployeeAirlinePassenger struct { 24 | } 25 | 26 | func (*EmployeeAirlinePassenger) GetDiscount() float32 { 27 | return 1 28 | } 29 | 30 | func NetIncome(passengers []Passenger, baseTicketPrice float32) float32 { 31 | var total float32 32 | for _, p := range passengers { 33 | total += baseTicketPrice - p.GetDiscount()*baseTicketPrice 34 | } 35 | return total 36 | } 37 | 38 | func main() { 39 | passengers := [...]Passenger{&BasePassenger{}, &LastMinutePassenger{}, &EmployeeAirlinePassenger{}} 40 | fmt.Println(NetIncome(passengers[:], 1000)) 41 | } 42 | -------------------------------------------------------------------------------- /day-04/challenge2/2.2/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestNetIncome(t *testing.T) { 6 | passengers := [...]Passenger{&BasePassenger{}, &LastMinutePassenger{}, &EmployeeAirlinePassenger{}} 7 | var in float32 = 1000 8 | var want float32 = 1500 9 | 10 | got := NetIncome(passengers[:], in) 11 | 12 | if got != want { 13 | t.Errorf("netIncome(%f) == %f, want %f", in, got, want) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /day-04/challenge2/2.3/README.md.org: -------------------------------------------------------------------------------- 1 | * Challenge 2.3 2 | :PROPERTIES: 3 | :CUSTOM_ID: challenge-2.3 4 | :END: 5 | Agregar un nuevo tipo de pasajero 'empleado de aerolinea de ultimo 6 | minuto' cuyo descuento sea la suma de los descuentos de los tipos de 7 | pasajero 'empleado de aerolinea' y 'ultimo minuto'. 8 | -------------------------------------------------------------------------------- /day-04/challenge2/2.3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Passenger interface { 6 | GetDiscount() float32 7 | } 8 | 9 | type BasePassenger struct { 10 | } 11 | 12 | func (*BasePassenger) GetDiscount() float32 { 13 | return 0 14 | } 15 | 16 | type LastMinutePassenger struct { 17 | } 18 | 19 | func (*LastMinutePassenger) GetDiscount() float32 { 20 | return 0.5 21 | } 22 | 23 | type EmployeeAirlinePassenger struct { 24 | } 25 | 26 | func (*EmployeeAirlinePassenger) GetDiscount() float32 { 27 | return 1 28 | } 29 | 30 | type EmployeeAirlineLastMinutePassenger struct { 31 | LastMinutePassenger 32 | EmployeeAirlinePassenger 33 | } 34 | 35 | func (elm *EmployeeAirlineLastMinutePassenger) GetDiscount() float32 { 36 | return elm.LastMinutePassenger.GetDiscount() + elm.EmployeeAirlinePassenger.GetDiscount() 37 | } 38 | 39 | func NetIncome(passengers []Passenger, baseTicketPrice float32) float32 { 40 | var total float32 41 | for _, p := range passengers { 42 | total += baseTicketPrice - p.GetDiscount()*baseTicketPrice 43 | } 44 | return total 45 | } 46 | 47 | func main() { 48 | passengers := [...]Passenger{&BasePassenger{}, &LastMinutePassenger{}, &EmployeeAirlinePassenger{}, &EmployeeAirlineLastMinutePassenger{}} 49 | fmt.Println(NetIncome(passengers[:], 1000)) 50 | } 51 | -------------------------------------------------------------------------------- /day-04/challenge2/2.3/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestNetIncome(t *testing.T) { 6 | passengers := [...]Passenger{&BasePassenger{}, &LastMinutePassenger{}, &EmployeeAirlinePassenger{}, &EmployeeAirlineLastMinutePassenger{}} 7 | var in float32 = 1000 8 | var want float32 = 2000 9 | 10 | got := NetIncome(passengers[:], in) 11 | 12 | if got != want { 13 | t.Errorf("netIncome(%f) == %f, want %f", in, got, want) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /day-05/challenge3/README.md.org: -------------------------------------------------------------------------------- 1 | * Challenge 3 2 | :PROPERTIES: 3 | :CUSTOM_ID: challenge-3 4 | :END: 5 | 6 | - Convertir las siguientes funciones para que utilicen /gorutines/ y 7 | /channels/. 8 | - Crear dos /benchmarks/ para comparar la velocidad de ejecucion entre 9 | las versiones con y sin uso de /gorutines/. 10 | 11 | #+begin_src go 12 | func Squares(number int) (sum int) { 13 | sum = 0 14 | for number != 0 { 15 | digit := number % 10 16 | sum += digit * digit 17 | number /= 10 18 | } 19 | return 20 | } 21 | 22 | func Cubes(number int) (sum int) { 23 | sum = 0 24 | for number != 0 { 25 | digit := number % 10 26 | sum += digit * digit * digit 27 | number /= 10 28 | } 29 | return 30 | } 31 | 32 | func SquaresPlusCubes(number int) int { 33 | return Squares(number) + Cubes(number) 34 | } 35 | #+end_src 36 | -------------------------------------------------------------------------------- /day-05/challenge3/math.go: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | func Squares(number int) (sum int) { 4 | sum = 0 5 | for number != 0 { 6 | digit := number % 10 7 | sum += digit * digit 8 | number /= 10 9 | } 10 | return 11 | } 12 | 13 | func Cubes(number int) (sum int) { 14 | sum = 0 15 | for number != 0 { 16 | digit := number % 10 17 | sum += digit * digit * digit 18 | number /= 10 19 | } 20 | return 21 | } 22 | 23 | func SquaresPlusCubes(number int) int { 24 | return Squares(number) + Cubes(number) 25 | } 26 | 27 | func SquaresMT(number int, c chan int) { 28 | sum := 0 29 | for number != 0 { 30 | digit := number % 10 31 | sum += digit * digit 32 | number /= 10 33 | } 34 | c <- sum 35 | } 36 | 37 | func CubesMT(number int, c chan int) { 38 | sum := 0 39 | for number != 0 { 40 | digit := number % 10 41 | sum += digit * digit * digit 42 | number /= 10 43 | } 44 | c <- sum 45 | } 46 | 47 | func SquaresPlusCubesMT(number int, c chan int) { 48 | sc := make(chan int) 49 | cc := make(chan int) 50 | go SquaresMT(number, sc) 51 | go CubesMT(number, cc) 52 | squares, cubes := <-sc, <-cc 53 | c <- squares + cubes 54 | } 55 | -------------------------------------------------------------------------------- /day-05/challenge3/math_test.go: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import "testing" 4 | 5 | const in int = 12345 6 | const want int = 280 7 | 8 | func TestSquaresPlusCubes(t *testing.T) { 9 | got := SquaresPlusCubes(in) 10 | if got != want { 11 | t.Errorf("SquaresPlusCubes(%d) == %d, want %d", in, got, want) 12 | } 13 | } 14 | 15 | func BenchmarkSquaresPlusCubes(b *testing.B) { 16 | for n := 0; n < b.N; n++ { 17 | SquaresPlusCubes(in) 18 | } 19 | } 20 | 21 | func TestSquaresPlusCubesMT(t *testing.T) { 22 | c := make(chan int) 23 | go SquaresPlusCubesMT(in, c) 24 | got := <-c 25 | 26 | if got != want { 27 | t.Errorf("SquaresPlusCubesMT(%d) == %d, want %d", in, got, want) 28 | } 29 | } 30 | 31 | func BenchmarkSquaresPlusCubesMT(b *testing.B) { 32 | for n := 0; n < b.N; n++ { 33 | c := make(chan int) 34 | go SquaresPlusCubesMT(in, c) 35 | got := <-c 36 | 37 | if got != want { 38 | b.Errorf("SquaresPlusCubesMT(%d) == %d, want %d", in, got, want) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /day-05/challenge4/4.1/README.md.org: -------------------------------------------------------------------------------- 1 | * Challenge 4.1 2 | :PROPERTIES: 3 | :CUSTOM_ID: challenge-4.1 4 | :END: 5 | Crear una API REST para una aplicacion de recordatorios (ToDo) que 6 | contenga todas las funciones 7 | [[https://en.wikipedia.org/wiki/Create,_read,_update_and_delete][CRUD]] 8 | para la siguiente entidad: 9 | 10 | #+begin_src go 11 | ID int 12 | Title string 13 | IsDone bool 14 | #+end_src 15 | 16 | Hints: 17 | 18 | - Los datos se pueden almacenar en un arreglo. 19 | - La funcion /strconv.Atoi/ del paquete 20 | /[[https://golang.org/pkg/strconv][strconv]]/ permite convertir un 21 | string a un integer. 22 | -------------------------------------------------------------------------------- /day-05/challenge4/4.1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "strconv" 10 | 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | type item struct { 15 | ID int `json:"id,omitempty"` 16 | Title string `json:"title,omitempty"` 17 | IsDone bool `json:"isdone,omitempty"` 18 | } 19 | 20 | var items = []item {} 21 | 22 | func homeEndpoint(w http.ResponseWriter, r *http.Request) { 23 | fmt.Fprintf(w, "Welcome to the To-Do Web API") 24 | } 25 | 26 | func createItemEndpoint(w http.ResponseWriter, r *http.Request) { 27 | var newItem item 28 | reqBody, err := ioutil.ReadAll(r.Body) 29 | if err != nil { 30 | fmt.Fprintf(w, err.Error()) 31 | } 32 | 33 | json.Unmarshal(reqBody, &newItem) 34 | items = append(items, newItem) 35 | w.WriteHeader(http.StatusCreated) 36 | 37 | json.NewEncoder(w).Encode(newItem) 38 | } 39 | 40 | func getItemEndpoint(w http.ResponseWriter, r *http.Request) { 41 | itemID, _ := strconv.Atoi(mux.Vars(r)["id"]) 42 | 43 | for _, singleItem := range items { 44 | if singleItem.ID == itemID { 45 | json.NewEncoder(w).Encode(singleItem) 46 | } 47 | } 48 | } 49 | 50 | func getItemsEndpoint(w http.ResponseWriter, r *http.Request) { 51 | json.NewEncoder(w).Encode(items) 52 | } 53 | 54 | func updateItemEndpoint(w http.ResponseWriter, r *http.Request) { 55 | itemID, _ := strconv.Atoi(mux.Vars(r)["id"]) 56 | var updatedItem item 57 | 58 | reqBody, err := ioutil.ReadAll(r.Body) 59 | if err != nil { 60 | fmt.Fprintf(w, err.Error()) 61 | } 62 | json.Unmarshal(reqBody, &updatedItem) 63 | 64 | for i, singleItem := range items { 65 | if singleItem.ID == itemID { 66 | singleItem.Title = updatedItem.Title 67 | singleItem.IsDone = updatedItem.IsDone 68 | items = append(items[:i], singleItem) 69 | json.NewEncoder(w).Encode(singleItem) 70 | } 71 | } 72 | } 73 | 74 | func deleteItemEndpoint(w http.ResponseWriter, r *http.Request) { 75 | itemID, _ := strconv.Atoi(mux.Vars(r)["id"]) 76 | 77 | for i, singleItem := range items { 78 | if singleItem.ID == itemID { 79 | items = append(items[:i], items[i+1:]...) 80 | fmt.Fprintf(w, "The item with ID %v has been deleted successfully", itemID) 81 | break 82 | } 83 | } 84 | } 85 | 86 | func main() { 87 | router := mux.NewRouter().StrictSlash(true) 88 | 89 | router.HandleFunc("/", homeEndpoint) 90 | router.HandleFunc("/api", createItemEndpoint).Methods("POST") 91 | router.HandleFunc("/api", getItemsEndpoint).Methods("GET") 92 | router.HandleFunc("/api/{id}", getItemEndpoint).Methods("GET") 93 | router.HandleFunc("/api/{id}", updateItemEndpoint).Methods("PATCH") 94 | router.HandleFunc("/api/{id}", deleteItemEndpoint).Methods("DELETE") 95 | 96 | log.Fatal(http.ListenAndServe(":8080", router)) 97 | } 98 | -------------------------------------------------------------------------------- /day-05/challenge4/4.2/README.md.org: -------------------------------------------------------------------------------- 1 | * Challenge 4.2 2 | :PROPERTIES: 3 | :CUSTOM_ID: challenge-4.2 4 | :END: 5 | Modificar la API Rest del challenge 4.1 para que pueda acceder a 6 | diferentes fuentes de datos (en memoria y MongoDB) usando el patron 7 | /[[https://medium.com/@pererikbergman/repository-design-pattern-e28c0f3e4a30][Repository]]/. 8 | 9 | Hints: 10 | 11 | - Se pueden copiar las funciones necesarias para operar con MongoDB 12 | desde el 13 | [[https://github.com/rfinochi/golang-workshop-src/tree/master/19-dbaccess][Ejercicio 14 | 19]]. 15 | -------------------------------------------------------------------------------- /day-05/challenge4/4.2/inmemoryrepository.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var items = []Item{} 4 | 5 | type InMemoryRepository struct { 6 | } 7 | 8 | func (InMemoryRepository) CreateItem(newItem Item) { 9 | items = append(items, newItem) 10 | } 11 | 12 | func (InMemoryRepository) UpdateItem(updatedItem Item) { 13 | for i, item := range items { 14 | if item.ID == updatedItem.ID { 15 | item.Title = updatedItem.Title 16 | item.IsDone = updatedItem.IsDone 17 | items = append(items[:i], item) 18 | } 19 | } 20 | } 21 | 22 | func (InMemoryRepository) GetItems() []Item { 23 | return items 24 | } 25 | 26 | func (InMemoryRepository) GetItem(id int) Item { 27 | var result Item 28 | 29 | for _, item := range items { 30 | if item.ID == id { 31 | result = item 32 | break 33 | } 34 | } 35 | 36 | return result 37 | } 38 | 39 | func (InMemoryRepository) DeleteItem(id int) { 40 | for i, item := range items { 41 | if item.ID == id { 42 | items = append(items[:i], items[i+1:]...) 43 | break 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /day-05/challenge4/4.2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "strconv" 10 | 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | var repositoryType string 15 | 16 | func homeEndpoint(w http.ResponseWriter, r *http.Request) { 17 | fmt.Fprintf(w, "Welcome to the To-Do Web API") 18 | } 19 | 20 | func createItemEndpoint(w http.ResponseWriter, r *http.Request) { 21 | var newItem Item 22 | reqBody, err := ioutil.ReadAll(r.Body) 23 | if err != nil { 24 | fmt.Fprintf(w, err.Error()) 25 | } 26 | 27 | json.Unmarshal(reqBody, &newItem) 28 | 29 | repo := createRepository() 30 | repo.CreateItem(newItem) 31 | 32 | w.WriteHeader(http.StatusCreated) 33 | fmt.Fprintf(w, "OK") 34 | } 35 | 36 | func getItemEndpoint(w http.ResponseWriter, r *http.Request) { 37 | id, _ := strconv.Atoi(mux.Vars(r)["id"]) 38 | 39 | repo := createRepository() 40 | json.NewEncoder(w).Encode(repo.GetItem(id)) 41 | } 42 | 43 | func getItemsEndpoint(w http.ResponseWriter, r *http.Request) { 44 | repo := createRepository() 45 | json.NewEncoder(w).Encode(repo.GetItems()) 46 | } 47 | 48 | func updateItemEndpoint(w http.ResponseWriter, r *http.Request) { 49 | id, _ := strconv.Atoi(mux.Vars(r)["id"]) 50 | var updatedItem Item 51 | 52 | reqBody, err := ioutil.ReadAll(r.Body) 53 | if err != nil { 54 | fmt.Fprintf(w, err.Error()) 55 | } 56 | json.Unmarshal(reqBody, &updatedItem) 57 | 58 | updatedItem.ID = id 59 | 60 | repo := createRepository() 61 | repo.UpdateItem(updatedItem) 62 | 63 | fmt.Fprintf(w, "OK") 64 | } 65 | 66 | func deleteItemEndpoint(w http.ResponseWriter, r *http.Request) { 67 | id, _ := strconv.Atoi(mux.Vars(r)["id"]) 68 | 69 | repo := createRepository() 70 | repo.DeleteItem(id) 71 | 72 | fmt.Fprintf(w, "OK") 73 | } 74 | 75 | func createRepository() TodoRepository { 76 | if repositoryType == "Mongo" { 77 | return &MongoRepository{} 78 | } else { 79 | return &InMemoryRepository{} 80 | } 81 | } 82 | 83 | func main() { 84 | repositoryType = "" 85 | 86 | router := mux.NewRouter().StrictSlash(true) 87 | 88 | router.HandleFunc("/", homeEndpoint) 89 | router.HandleFunc("/api", createItemEndpoint).Methods("POST") 90 | router.HandleFunc("/api", getItemsEndpoint).Methods("GET") 91 | router.HandleFunc("/api/{id}", getItemEndpoint).Methods("GET") 92 | router.HandleFunc("/api/{id}", updateItemEndpoint).Methods("PATCH") 93 | router.HandleFunc("/api/{id}", deleteItemEndpoint).Methods("DELETE") 94 | 95 | log.Fatal(http.ListenAndServe(":8080", router)) 96 | } 97 | -------------------------------------------------------------------------------- /day-05/challenge4/4.2/mongorepository.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "go.mongodb.org/mongo-driver/bson" 7 | "go.mongodb.org/mongo-driver/mongo" 8 | "go.mongodb.org/mongo-driver/mongo/options" 9 | ) 10 | 11 | type MongoRepository struct { 12 | } 13 | 14 | func (MongoRepository) CreateItem(newItem Item) { 15 | ctx, client := connnect() 16 | 17 | collection := client.Database("todo").Collection("items") 18 | collection.InsertOne(ctx, newItem) 19 | 20 | disconnect(ctx, client) 21 | } 22 | 23 | func (MongoRepository) UpdateItem(item Item) { 24 | update := bson.M{"$set": bson.M{"title": item.Title, "isdone": item.IsDone}} 25 | 26 | ctx, client := connnect() 27 | 28 | collection := client.Database("todo").Collection("items") 29 | collection.UpdateOne(ctx, Item{ID: item.ID}, update) 30 | 31 | disconnect(ctx, client) 32 | } 33 | 34 | func (MongoRepository) GetItems() (items []Item) { 35 | ctx, client := connnect() 36 | 37 | collection := client.Database("todo").Collection("items") 38 | cursor, _ := collection.Find(ctx, bson.M{}) 39 | 40 | defer cursor.Close(ctx) 41 | for cursor.Next(ctx) { 42 | var oneItem Item 43 | cursor.Decode(&oneItem) 44 | items = append(items, oneItem) 45 | } 46 | 47 | disconnect(ctx, client) 48 | 49 | return 50 | } 51 | 52 | func (MongoRepository) GetItem(id int) (item Item) { 53 | ctx, client := connnect() 54 | 55 | collection := client.Database("todo").Collection("items") 56 | collection.FindOne(ctx, Item{ID: id}).Decode(&item) 57 | 58 | disconnect(ctx, client) 59 | 60 | return 61 | } 62 | 63 | func (MongoRepository) DeleteItem(id int) { 64 | ctx, client := connnect() 65 | 66 | collection := client.Database("todo").Collection("items") 67 | collection.DeleteMany(ctx, Item{ID: id}) 68 | 69 | disconnect(ctx, client) 70 | 71 | return 72 | } 73 | 74 | func connnect() (context.Context, *mongo.Client) { 75 | ctx := context.Background() 76 | client, _ := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017")) 77 | 78 | return ctx, client 79 | } 80 | 81 | func disconnect(ctx context.Context, client *mongo.Client) { 82 | defer client.Disconnect(ctx) 83 | } 84 | -------------------------------------------------------------------------------- /day-05/challenge4/4.2/todorepository.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type Item struct { 4 | ID int `json:"id,omitempty bson:"id,omitempty"` 5 | Title string `json:"title,omitempty" bson:"title,omitempty"` 6 | IsDone bool `json:"isdone,omitempty" bson:"isdone,omitempty"` 7 | } 8 | 9 | type TodoRepository interface { 10 | CreateItem(Item) 11 | UpdateItem(Item) 12 | GetItems() []Item 13 | GetItem(int) Item 14 | DeleteItem(int) 15 | } 16 | -------------------------------------------------------------------------------- /day-05/challenge4/4.3/README.md.org: -------------------------------------------------------------------------------- 1 | * Challenge 4.3 2 | :PROPERTIES: 3 | :CUSTOM_ID: challenge-4.3 4 | :END: 5 | Modificar la API Rest del challenge 4.2 para usar Gin. 6 | -------------------------------------------------------------------------------- /day-05/challenge4/4.3/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rfinochi/golang-workshop-todo 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/gin-gonic/contrib v0.0.0-20191209060500-d6e26eeaa607 7 | github.com/gin-gonic/gin v1.6.3 8 | github.com/klauspost/compress v1.10.5 // indirect 9 | github.com/pkg/errors v0.9.1 // indirect 10 | github.com/stretchr/testify v1.5.1 11 | github.com/xdg/stringprep v1.0.0 // indirect 12 | go.mongodb.org/mongo-driver v1.3.3 13 | ) 14 | -------------------------------------------------------------------------------- /day-05/challenge4/4.3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | "strconv" 9 | 10 | "github.com/gin-gonic/contrib/static" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func main() { 15 | router := SetupRouter() 16 | 17 | port := os.Getenv("PORT") 18 | if port == "" { 19 | port = os.Getenv("HTTP_PLATFORM_PORT") 20 | if port == "" { 21 | port = "8080" 22 | log.Printf("Defaulting to port %s", port) 23 | } 24 | } 25 | 26 | router.Run(fmt.Sprintf(":%s", port)) 27 | } 28 | 29 | func SetupRouter() *gin.Engine { 30 | router := gin.Default() 31 | 32 | router.Use(static.Serve("/", static.LocalFile("./views", true))) 33 | 34 | api := router.Group("/api") 35 | api.GET("/", getItemsEndpoint) 36 | api.GET("/:id", getItemEndpoint) 37 | api.POST("/", postItemEndpoint) 38 | api.PUT("/", putItemEndpoint) 39 | api.PATCH("/:id", updateItemEndpoint) 40 | api.DELETE("/:id", deleteItemEndpoint) 41 | 42 | return router 43 | } 44 | 45 | func postItemEndpoint(c *gin.Context) { 46 | var newItem Item 47 | c.BindJSON(&newItem) 48 | 49 | repo := createRepository() 50 | repo.CreateItem(newItem) 51 | 52 | c.Header("Content-Type", "application/json") 53 | c.JSON(http.StatusCreated, gin.H{"message": "OK"}) 54 | } 55 | 56 | func putItemEndpoint(c *gin.Context) { 57 | var newItem Item 58 | c.BindJSON(&newItem) 59 | 60 | repo := createRepository() 61 | repo.CreateItem(newItem) 62 | 63 | c.Header("Content-Type", "application/json") 64 | c.JSON(http.StatusCreated, gin.H{"message": "OK"}) 65 | } 66 | 67 | func getItemEndpoint(c *gin.Context) { 68 | id, _ := strconv.Atoi(c.Param("id")) 69 | 70 | repo := createRepository() 71 | c.Header("Content-Type", "application/json") 72 | c.JSON(http.StatusOK, repo.GetItem(id)) 73 | } 74 | 75 | func getItemsEndpoint(c *gin.Context) { 76 | repo := createRepository() 77 | c.JSON(http.StatusOK, repo.GetItems()) 78 | } 79 | 80 | func updateItemEndpoint(c *gin.Context) { 81 | id, _ := strconv.Atoi(c.Param("id")) 82 | 83 | var updatedItem Item 84 | c.BindJSON(&updatedItem) 85 | 86 | repo := createRepository() 87 | updatedItem.ID = id 88 | repo.UpdateItem(updatedItem) 89 | 90 | c.Header("Content-Type", "application/json") 91 | c.JSON(http.StatusOK, gin.H{"message": "OK"}) 92 | } 93 | 94 | func deleteItemEndpoint(c *gin.Context) { 95 | id, _ := strconv.Atoi(c.Param("id")) 96 | 97 | repo := createRepository() 98 | repo.DeleteItem(id) 99 | 100 | c.Header("Content-Type", "application/json") 101 | c.JSON(http.StatusOK, gin.H{"message": "OK"}) 102 | } 103 | 104 | func createRepository() TodoRepository { 105 | repositoryType := os.Getenv("REPOSITORY_TYPE") 106 | 107 | if repositoryType == "Mongo" { 108 | return &MongoRepository{} 109 | } else { 110 | return &MemoryRepository{} 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /day-05/challenge4/4.3/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "net/http/httptest" 9 | "os" 10 | "testing" 11 | 12 | "github.com/gin-gonic/gin" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | var router *gin.Engine 17 | 18 | func init() { 19 | router = SetupRouter() 20 | } 21 | 22 | func TestCompleteApi(t *testing.T) { 23 | os.Setenv("REPOSITORY_TYPE", "Memory") 24 | 25 | doAllAPIRequests(t) 26 | } 27 | 28 | func doAllAPIRequests(t *testing.T) { 29 | doGetItems(router, t, "", true, 0) 30 | doGetItem(router, t, 0, "", false) 31 | 32 | doPostItem(router, t, "POST", `{"id":1,"title":"Test_1","isdone":true}`) 33 | doGetItems(router, t, "Test_1", true, 1) 34 | doGetItem(router, t, 1, "Test_1", true) 35 | 36 | doPostItem(router, t, "PUT", `{"id":2,"title":"Test_2","isdone":true}`) 37 | doGetItems(router, t, "Test_1", true, 2) 38 | doGetItem(router, t, 2, "Test_2", true) 39 | 40 | doDeleteItem(router, t, 2) 41 | doGetItems(router, t, "Test_1", true, 1) 42 | 43 | doPatchItem(router, t, 1, `{"id":1,"title":"Test_3","isdone":false}`) 44 | doGetItems(router, t, "Test_3", false, 1) 45 | doGetItem(router, t, 1, "Test_3", false) 46 | } 47 | 48 | func doPostItem(r http.Handler, t *testing.T, method string, payload string) { 49 | request := doRequest(r, method, "/api/", payload) 50 | 51 | assert.Equal(t, http.StatusCreated, request.Code) 52 | 53 | var response map[string]string 54 | 55 | err := json.Unmarshal([]byte(request.Body.String()), &response) 56 | value, exists := response["message"] 57 | 58 | assert.Nil(t, err) 59 | assert.True(t, exists) 60 | assert.Equal(t, "OK", value) 61 | } 62 | 63 | func doGetItem(r http.Handler, t *testing.T, id int, title string, isdone bool) { 64 | request := doRequest(r, "GET", fmt.Sprintf("/api/%v", id), "") 65 | 66 | assert.Equal(t, http.StatusOK, request.Code) 67 | 68 | var response Item 69 | 70 | err := json.Unmarshal([]byte(request.Body.String()), &response) 71 | 72 | assert.Nil(t, err) 73 | assert.Equal(t, response.ID, id) 74 | assert.Equal(t, response.Title, title) 75 | assert.Equal(t, response.IsDone, isdone) 76 | } 77 | 78 | func doGetItems(r http.Handler, t *testing.T, title string, isdone bool, length int) { 79 | request := doRequest(r, "GET", "/api/", "") 80 | 81 | assert.Equal(t, http.StatusOK, request.Code) 82 | 83 | var response []Item 84 | 85 | err := json.Unmarshal([]byte(request.Body.String()), &response) 86 | 87 | assert.Nil(t, err) 88 | assert.Equal(t, len(response), length) 89 | if length > 0 { 90 | assert.Equal(t, response[0].ID, 1) 91 | assert.Equal(t, response[0].Title, title) 92 | assert.Equal(t, response[0].IsDone, isdone) 93 | } 94 | } 95 | 96 | func doDeleteItem(r http.Handler, t *testing.T, id int) { 97 | request := doRequest(r, "DELETE", fmt.Sprintf("/api/%v", id), "") 98 | 99 | assert.Equal(t, http.StatusOK, request.Code) 100 | 101 | var response map[string]string 102 | 103 | err := json.Unmarshal([]byte(request.Body.String()), &response) 104 | value, exists := response["message"] 105 | 106 | assert.Nil(t, err) 107 | assert.True(t, exists) 108 | assert.Equal(t, "OK", value) 109 | } 110 | 111 | func doPatchItem(r http.Handler, t *testing.T, id int, payload string) { 112 | request := doRequest(r, "PATCH", fmt.Sprintf("/api/%v", id), payload) 113 | 114 | assert.Equal(t, http.StatusOK, request.Code) 115 | 116 | var response map[string]string 117 | 118 | err := json.Unmarshal([]byte(request.Body.String()), &response) 119 | value, exists := response["message"] 120 | 121 | assert.Nil(t, err) 122 | assert.True(t, exists) 123 | assert.Equal(t, "OK", value) 124 | } 125 | 126 | func doRequest(r http.Handler, method string, path string, payload string) *httptest.ResponseRecorder { 127 | var req *http.Request 128 | 129 | if method == "POST" || method == "PATCH" || method == "PUT" { 130 | req, _ = http.NewRequest(method, path, bytes.NewBuffer([]byte(payload))) 131 | req.Header.Set("Content-Type", "application/json") 132 | } else { 133 | req, _ = http.NewRequest(method, path, nil) 134 | } 135 | 136 | w := httptest.NewRecorder() 137 | 138 | r.ServeHTTP(w, req) 139 | 140 | return w 141 | } 142 | -------------------------------------------------------------------------------- /day-05/challenge4/4.3/memoryrepository.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var items = []Item{} 4 | 5 | type MemoryRepository struct { 6 | } 7 | 8 | func (MemoryRepository) CreateItem(newItem Item) { 9 | items = append(items, newItem) 10 | } 11 | 12 | func (MemoryRepository) UpdateItem(updatedItem Item) { 13 | for i, item := range items { 14 | if item.ID == updatedItem.ID { 15 | item.Title = updatedItem.Title 16 | item.IsDone = updatedItem.IsDone 17 | items = append(items[:i], item) 18 | } 19 | } 20 | } 21 | 22 | func (MemoryRepository) GetItems() []Item { 23 | return items 24 | } 25 | 26 | func (MemoryRepository) GetItem(id int) Item { 27 | var result Item 28 | 29 | for _, item := range items { 30 | if item.ID == id { 31 | result = item 32 | break 33 | } 34 | } 35 | 36 | return result 37 | } 38 | 39 | func (MemoryRepository) DeleteItem(id int) { 40 | for i, item := range items { 41 | if item.ID == id { 42 | items = append(items[:i], items[i+1:]...) 43 | break 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /day-05/challenge4/4.3/mongorepository.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "go.mongodb.org/mongo-driver/bson" 8 | "go.mongodb.org/mongo-driver/mongo" 9 | "go.mongodb.org/mongo-driver/mongo/options" 10 | ) 11 | 12 | var uri string 13 | 14 | type MongoRepository struct { 15 | } 16 | 17 | func init() { 18 | var ok bool 19 | 20 | if uri, ok = os.LookupEnv("MONGO_URI"); !ok { 21 | uri = "mongodb://localhost:27017" 22 | } 23 | } 24 | 25 | func (MongoRepository) CreateItem(newItem Item) { 26 | ctx, client := connnect() 27 | 28 | collection := client.Database("todo").Collection("items") 29 | collection.InsertOne(ctx, newItem) 30 | 31 | disconnect(ctx, client) 32 | } 33 | 34 | func (MongoRepository) UpdateItem(item Item) { 35 | update := bson.M{"$set": bson.M{"title": item.Title, "isdone": item.IsDone}} 36 | 37 | ctx, client := connnect() 38 | 39 | collection := client.Database("todo").Collection("items") 40 | collection.UpdateOne(ctx, Item{ID: item.ID}, update) 41 | 42 | disconnect(ctx, client) 43 | } 44 | 45 | func (MongoRepository) GetItems() (items []Item) { 46 | ctx, client := connnect() 47 | 48 | collection := client.Database("todo").Collection("items") 49 | cursor, _ := collection.Find(ctx, bson.M{}) 50 | 51 | defer cursor.Close(ctx) 52 | for cursor.Next(ctx) { 53 | var oneItem Item 54 | cursor.Decode(&oneItem) 55 | items = append(items, oneItem) 56 | } 57 | 58 | disconnect(ctx, client) 59 | 60 | return 61 | } 62 | 63 | func (MongoRepository) GetItem(id int) (item Item) { 64 | ctx, client := connnect() 65 | 66 | collection := client.Database("todo").Collection("items") 67 | collection.FindOne(ctx, Item{ID: id}).Decode(&item) 68 | 69 | disconnect(ctx, client) 70 | 71 | return 72 | } 73 | 74 | func (MongoRepository) DeleteItem(id int) { 75 | ctx, client := connnect() 76 | 77 | collection := client.Database("todo").Collection("items") 78 | collection.DeleteMany(ctx, Item{ID: id}) 79 | 80 | disconnect(ctx, client) 81 | } 82 | 83 | func connnect() (context.Context, *mongo.Client) { 84 | ctx := context.Background() 85 | client, _ := mongo.Connect(ctx, options.Client().ApplyURI(uri)) 86 | 87 | return ctx, client 88 | } 89 | 90 | func disconnect(ctx context.Context, client *mongo.Client) { 91 | defer client.Disconnect(ctx) 92 | } 93 | -------------------------------------------------------------------------------- /day-05/challenge4/4.3/todorepository.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Item godoc 4 | type Item struct { 5 | ID int `json:"id,omitempty bson:"id,omitempty" datastore:"id"` 6 | Title string `json:"title,omitempty" bson:"title,omitempty" datastore:"title"` 7 | IsDone bool `json:"isdone,omitempty" bson:"isdone,omitempty" datastore:"isdone"` 8 | } 9 | 10 | // TodoRepository godoc 11 | type TodoRepository interface { 12 | CreateItem(Item) 13 | UpdateItem(Item) 14 | GetItems() []Item 15 | GetItem(int) Item 16 | DeleteItem(int) 17 | } 18 | -------------------------------------------------------------------------------- /day-05/challenge4/4.3/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sample To-Do Web API 5 | 6 | 7 | 8 |

Sample To-Do Web API

9 |

API

10 |

API Doc

11 | 12 | 13 | -------------------------------------------------------------------------------- /day-06/19-dbaccess/mongo/dbaccess.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "go.mongodb.org/mongo-driver/bson" 7 | "go.mongodb.org/mongo-driver/bson/primitive" 8 | "go.mongodb.org/mongo-driver/mongo" 9 | "go.mongodb.org/mongo-driver/mongo/options" 10 | ) 11 | 12 | type Item struct { 13 | ID int `json:"id,omitempty" bson:"id,omitempty"` 14 | Title string `json:"title,omitempty" bson:"title,omitempty"` 15 | IsDone bool `json:"isdone,omitempty" bson:"isdone,omitempty"` 16 | } 17 | 18 | type MongoDB struct { 19 | *mongo.Client 20 | } 21 | 22 | func NewMongoDB(ctx context.Context) (*MongoDB, error) { 23 | client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017")) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return &MongoDB{client}, nil 28 | } 29 | 30 | func (m *MongoDB) Disconnect(ctx context.Context) { 31 | defer m.Client.Disconnect(ctx) 32 | } 33 | 34 | func (m *MongoDB) CreateItem(ctx context.Context, newItem Item) string { 35 | 36 | collection := m.Database("todo").Collection("items") 37 | result, _ := collection.InsertOne(ctx, newItem) 38 | 39 | return result.InsertedID.(primitive.ObjectID).Hex() 40 | } 41 | 42 | func (m *MongoDB) UpdateItem(ctx context.Context, item Item) { 43 | update := bson.M{"$set": bson.M{"title": item.Title, "isdone": item.IsDone}} 44 | 45 | collection := m.Database("todo").Collection("items") 46 | collection.UpdateOne(ctx, Item{ID: item.ID}, update) 47 | } 48 | 49 | func (m *MongoDB) GetItems(ctx context.Context) (items []Item) { 50 | collection := m.Database("todo").Collection("items") 51 | cursor, _ := collection.Find(ctx, bson.M{}) 52 | 53 | defer cursor.Close(ctx) 54 | for cursor.Next(ctx) { 55 | var oneItem Item 56 | cursor.Decode(&oneItem) 57 | items = append(items, oneItem) 58 | } 59 | 60 | return 61 | } 62 | 63 | func (m *MongoDB) GetItem(ctx context.Context, id int) (item Item) { 64 | 65 | collection := m.Database("todo").Collection("items") 66 | collection.FindOne(ctx, Item{ID: id}).Decode(&item) 67 | return 68 | } 69 | 70 | func (m *MongoDB) DeleteItem(ctx context.Context, id int) { 71 | collection := m.Database("todo").Collection("items") 72 | collection.DeleteMany(ctx, Item{ID: id}) 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /day-06/19-dbaccess/mongo/go.mod: -------------------------------------------------------------------------------- 1 | module sample19mongo 2 | 3 | go 1.15 4 | 5 | require go.mongodb.org/mongo-driver v1.4.5 6 | -------------------------------------------------------------------------------- /day-06/19-dbaccess/mongo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | item := Item{1, "Test_1", false} 11 | ctx := context.TODO() 12 | m, err := NewMongoDB(ctx) 13 | if err != nil { 14 | log.Fatalf("Got error from MongoDB %s", err) 15 | } 16 | defer m.Disconnect(ctx) 17 | result := m.CreateItem(ctx, item) 18 | fmt.Println(result) 19 | 20 | items := m.GetItems(ctx) 21 | fmt.Println(items) 22 | 23 | fmt.Println(m.GetItem(ctx, 1)) 24 | 25 | item.Title = "Test_1U" 26 | item.IsDone = true 27 | 28 | m.UpdateItem(ctx, item) 29 | fmt.Println(m.GetItem(ctx, 1)) 30 | 31 | m.DeleteItem(ctx, 1) 32 | fmt.Println(m.GetItem(ctx, 1)) 33 | } 34 | -------------------------------------------------------------------------------- /day-06/19-dbaccess/sqlite/data.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/day-06/19-dbaccess/sqlite/data.db -------------------------------------------------------------------------------- /day-06/19-dbaccess/sqlite/go.mod: -------------------------------------------------------------------------------- 1 | module sample19 2 | 3 | go 1.15 4 | 5 | require github.com/mattn/go-sqlite3 v1.14.6 6 | -------------------------------------------------------------------------------- /day-06/19-dbaccess/sqlite/go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= 2 | github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 3 | -------------------------------------------------------------------------------- /day-06/19-dbaccess/sqlite/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | _ "github.com/mattn/go-sqlite3" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | db, err := sql.Open("sqlite3", "./data.db") 12 | checkErr(err) 13 | 14 | defer db.Close() // Cerrar la base de datos siempre 15 | // Insertar 16 | stmt, err := db.Prepare("INSERT INTO userinfo(username, departname, created) values(?,?,?)") 17 | checkErr(err) 18 | 19 | res, err := stmt.Exec("pparker", "Avengers", "2021-01-01") 20 | checkErr(err) 21 | 22 | id, err := res.LastInsertId() 23 | checkErr(err) 24 | 25 | fmt.Println(id) 26 | // Actualizar 27 | stmt, err = db.Prepare("update userinfo set username=? where uid=?") 28 | checkErr(err) 29 | 30 | res, err = stmt.Exec("spiderman", id) 31 | checkErr(err) 32 | 33 | affect, err := res.RowsAffected() 34 | checkErr(err) 35 | 36 | fmt.Println(affect) 37 | 38 | // Consultar 39 | rows, err := db.Query("SELECT * FROM userinfo") 40 | checkErr(err) 41 | var uid int 42 | var username string 43 | var department string 44 | var created time.Time 45 | 46 | for rows.Next() { 47 | err = rows.Scan(&uid, &username, &department, &created) 48 | checkErr(err) 49 | fmt.Println(uid) 50 | fmt.Println(username) 51 | fmt.Println(department) 52 | fmt.Println(created) 53 | } 54 | 55 | rows.Close() // Close libera recursos del iterador 56 | 57 | // Eliminar 58 | stmt, err = db.Prepare("delete from userinfo where uid=?") 59 | checkErr(err) 60 | 61 | res, err = stmt.Exec(id) 62 | checkErr(err) 63 | 64 | affect, err = res.RowsAffected() 65 | checkErr(err) 66 | 67 | fmt.Println(affect) 68 | } 69 | 70 | func checkErr(err error) { 71 | if err != nil { 72 | panic(err) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /day-06/20-echo/README.md.org: -------------------------------------------------------------------------------- 1 | * Labstack Echo 2 | :PROPERTIES: 3 | :CUSTOM_ID: labstack-echo 4 | :END: 5 | 6 | ** Introducción 7 | 8 | *Echo* es un microframework Golang para crear servicios web. 9 | 10 | ** Rutas. 11 | 12 | :PROPERTIES: 13 | :CUSTOM_ID: rutas-middleware 14 | :END: 15 | 16 | 17 | *** Handlers 18 | 19 | =echo.Context= representa el acceso al estado de la solicitud (ruta, 20 | parámetros, /handlers/, etc) y contiene los métodos para generar las 21 | respuesta 22 | 23 | #+begin_src go 24 | func updateUser (c echo.Context) (err error) { 25 | // ... 26 | } 27 | #+end_src 28 | 29 | *** Rutas REST 30 | #+begin_src go 31 | import "github.com/labstack/echo/v4" 32 | 33 | func main(){ 34 | e := echo.New() 35 | e.POST("/users", createUser) 36 | e.GET("/users/:id", findUser) 37 | e.PUT("/users/:id", updateUser) 38 | e.DELETE("/users/:id", deleteUser) 39 | e.Any("/",home) 40 | } 41 | #+end_src 42 | 43 | *** Grupos 44 | 45 | Establecer opciones similares para varias rutas. 46 | #+begin_src go 47 | //Todas las URLs con /v2/* 48 | g := e.Group("/v2") 49 | e.POST("/users", createUserV2) 50 | e.GET("/users/:id", findUserV2) 51 | e.PUT("/users/:id", updateUserV2) 52 | e.DELETE("/users/:id", deleteUserV2) 53 | #+end_src 54 | 55 | ** Middleware 56 | 57 | Funciones que se procesan antes de un handler. 58 | 59 | #+begin_src go 60 | func MyMiddleware(next echo.HandlerFunc) echo.HandlerFunc { 61 | return func(c echo.Context) error { 62 | //Hacer algo 63 | return next(c) 64 | } 65 | } 66 | #+end_src 67 | 68 | *** Incluyendo middleware 69 | 70 | #+begin_src go 71 | //Antes de ejecutarse el router 72 | e.Pre(MyMiddleware) 73 | //Después de ejecutar el router 74 | e.Use(MyMiddleware) 75 | // A nivel de grupo 76 | admin := e.Group("/admin", MyMiddleware) 77 | //A nivel de ruta 78 | e.GET("/", , ) 79 | #+end_src 80 | 81 | ** Obteniendo datos 82 | 83 | *** Desde el contexto 84 | 85 | #+begin_src go 86 | func(c echo.Context) error { 87 | name := c.FormValue("name") // valor de formulario 88 | printFull := c.QueryParam("full") // valor de query 89 | return c.String(http.StatusOK, name) 90 | } 91 | #+end_src 92 | 93 | *** Usando =Bind= 94 | 95 | #+begin_src go 96 | type User struct { 97 | Name string `json:"name" form:"name" query:"name"` 98 | Email string `json:"email" form:"email" query:"email"` 99 | } 100 | func handle(c echo.Context) (err error) { 101 | u := new(User) 102 | if err = c.Bind(u); err != nil { 103 | return 104 | } 105 | // Hacer algo con el usuario 106 | } 107 | #+end_src 108 | 109 | ** Respuestas 110 | :PROPERTIES: 111 | :CUSTOM_ID: echo-response 112 | :END: 113 | 114 | =echo.Context= es también utilizada para generar respuestas. 115 | 116 | #+begin_src go 117 | // Retornar una cadena 118 | c.String(http.StatusOK, "Hello, World!") 119 | // Retornar HTML 120 | c.HTML(http.StatusOK, "

Hello, World!

") 121 | // Retorna JSON, serializa el valor de u 122 | c.JSON(http.StatusOK, u) 123 | // Retorna XML, serializa el valor de u 124 | c.XML(http.StatusOK, u) 125 | // Retorna el contenido del fichero 126 | c.File("") 127 | // Retorna el contenido del fichero como flujo de datos 128 | c.Stream(http.StatusOK, "", file) 129 | // Redirige 130 | c.Redirect(http.StatusMovedPermanently, "") 131 | #+end_src 132 | -------------------------------------------------------------------------------- /day-06/20-echo/echo/cmd/litter/litter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "sample20/internal/handler" 6 | 7 | "github.com/labstack/echo/v4" 8 | "github.com/labstack/echo/v4/middleware" 9 | "github.com/labstack/gommon/log" 10 | "gopkg.in/mgo.v2" 11 | ) 12 | 13 | func main() { 14 | 15 | e := echo.New() 16 | e.Logger.SetLevel(log.ERROR) 17 | key := os.Getenv("LITTER_SECRET") 18 | if key == "" { 19 | e.Logger.Fatal("Cannot start without secret key") 20 | } 21 | e.Use(middleware.Logger()) 22 | e.Use(middleware.JWTWithConfig(middleware.JWTConfig{ 23 | SigningKey: []byte(key), 24 | Skipper: func(c echo.Context) bool { 25 | // Skip authentication for signup and login requests 26 | if c.Path() == "/login" || c.Path() == "/signup" { 27 | return true 28 | } 29 | return false 30 | }, 31 | })) 32 | 33 | // Database connection 34 | db, err := mgo.Dial("localhost") 35 | if err != nil { 36 | e.Logger.Fatal(err) 37 | } 38 | 39 | // Create indices 40 | if err = db.Copy().DB("twitter").C("users").EnsureIndex(mgo.Index{ 41 | Key: []string{"email"}, 42 | Unique: true, 43 | }); err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | // Initialize handler 48 | h := &handler.Handler{DB: db, Key: key} 49 | 50 | // Routes 51 | e.POST("/signup", h.Signup) 52 | e.POST("/login", h.Login) 53 | e.POST("/follow/:id", h.Follow) 54 | e.POST("/posts", h.CreatePost) 55 | e.GET("/feed", h.FetchPost) 56 | 57 | // Start server 58 | e.Logger.Fatal(e.Start(":1323")) 59 | } 60 | -------------------------------------------------------------------------------- /day-06/20-echo/echo/go.mod: -------------------------------------------------------------------------------- 1 | module sample20 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/labstack/echo/v4 v4.1.17 8 | github.com/labstack/gommon v0.3.0 9 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 10 | ) 11 | -------------------------------------------------------------------------------- /day-06/20-echo/echo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 4 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 5 | github.com/labstack/echo/v4 v4.1.17 h1:PQIBaRplyRy3OjwILGkPg89JRtH2x5bssi59G2EL3fo= 6 | github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2svdyFBg6CHQ= 7 | github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= 8 | github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= 9 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 10 | github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= 11 | github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 12 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 13 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 14 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 15 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 16 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 17 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 18 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 19 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 20 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 21 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 22 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 23 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 24 | github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= 25 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 26 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 27 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 28 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= 29 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 30 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 31 | golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= 32 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 33 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 34 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 35 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 36 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 37 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 40 | golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirSiguP9Q/veMtkYyf0o8= 41 | golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 43 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 44 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 45 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 46 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 47 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 48 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= 49 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= 50 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 51 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 52 | -------------------------------------------------------------------------------- /day-06/20-echo/echo/internal/handler/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "gopkg.in/mgo.v2" 5 | ) 6 | 7 | type ( 8 | Handler struct { 9 | DB *mgo.Session 10 | Key string 11 | } 12 | ) 13 | -------------------------------------------------------------------------------- /day-06/20-echo/echo/internal/handler/post.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "net/http" 5 | "sample20/internal/model" 6 | "strconv" 7 | 8 | "github.com/labstack/echo/v4" 9 | "gopkg.in/mgo.v2" 10 | "gopkg.in/mgo.v2/bson" 11 | ) 12 | 13 | func (h *Handler) CreatePost(c echo.Context) (err error) { 14 | u := &model.User{ 15 | ID: bson.ObjectIdHex(userIDFromToken(c)), 16 | } 17 | p := &model.Post{ 18 | ID: bson.NewObjectId(), 19 | From: u.ID.Hex(), 20 | } 21 | if err = c.Bind(p); err != nil { 22 | return 23 | } 24 | 25 | // Validation 26 | if p.To == "" || p.Message == "" { 27 | return &echo.HTTPError{Code: http.StatusBadRequest, Message: "invalid to or message fields"} 28 | } 29 | 30 | // Find user from database 31 | db := h.DB.Clone() 32 | defer db.Close() 33 | if err = db.DB("twitter").C("users").FindId(u.ID).One(u); err != nil { 34 | if err == mgo.ErrNotFound { 35 | return echo.ErrNotFound 36 | } 37 | return 38 | } 39 | 40 | // Save post in database 41 | if err = db.DB("twitter").C("posts").Insert(p); err != nil { 42 | return 43 | } 44 | return c.JSON(http.StatusCreated, p) 45 | } 46 | 47 | func (h *Handler) FetchPost(c echo.Context) (err error) { 48 | userID := userIDFromToken(c) 49 | page, _ := strconv.Atoi(c.QueryParam("page")) 50 | limit, _ := strconv.Atoi(c.QueryParam("limit")) 51 | 52 | // Defaults 53 | if page == 0 { 54 | page = 1 55 | } 56 | if limit == 0 { 57 | limit = 100 58 | } 59 | 60 | // Retrieve posts from database 61 | posts := []*model.Post{} 62 | db := h.DB.Clone() 63 | if err = db.DB("twitter").C("posts"). 64 | Find(bson.M{"to": userID}). 65 | Skip((page - 1) * limit). 66 | Limit(limit). 67 | All(&posts); err != nil { 68 | return 69 | } 70 | defer db.Close() 71 | 72 | return c.JSON(http.StatusOK, posts) 73 | } 74 | -------------------------------------------------------------------------------- /day-06/20-echo/echo/internal/handler/user.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/dgrijalva/jwt-go" 8 | "github.com/labstack/echo/v4" 9 | "gopkg.in/mgo.v2" 10 | "gopkg.in/mgo.v2/bson" 11 | "sample20/internal/model" 12 | ) 13 | 14 | func (h *Handler) Signup(c echo.Context) (err error) { 15 | // Bind 16 | u := &model.User{ID: bson.NewObjectId()} 17 | if err = c.Bind(u); err != nil { 18 | return 19 | } 20 | 21 | // Validate 22 | if u.Email == "" || u.Password == "" { 23 | return &echo.HTTPError{Code: http.StatusBadRequest, Message: "invalid email or password"} 24 | } 25 | 26 | // Save user 27 | db := h.DB.Clone() 28 | defer db.Close() 29 | if err = db.DB("twitter").C("users").Insert(u); err != nil { 30 | return 31 | } 32 | 33 | return c.JSON(http.StatusCreated, u) 34 | } 35 | 36 | func (h *Handler) Login(c echo.Context) (err error) { 37 | // Bind 38 | u := new(model.User) 39 | if err = c.Bind(u); err != nil { 40 | return 41 | } 42 | 43 | // Find user 44 | db := h.DB.Clone() 45 | defer db.Close() 46 | if err = db.DB("twitter").C("users"). 47 | Find(bson.M{"email": u.Email, "password": u.Password}).One(u); err != nil { 48 | if err == mgo.ErrNotFound { 49 | return &echo.HTTPError{Code: http.StatusUnauthorized, Message: "invalid email or password"} 50 | } 51 | return 52 | } 53 | 54 | //----- 55 | // JWT 56 | //----- 57 | 58 | // Create token 59 | token := jwt.New(jwt.SigningMethodHS256) 60 | 61 | // Set claims 62 | claims := token.Claims.(jwt.MapClaims) 63 | claims["id"] = u.ID 64 | claims["exp"] = time.Now().Add(time.Hour * 72).Unix() 65 | 66 | // Generate encoded token and send it as response 67 | u.Token, err = token.SignedString([]byte(h.Key)) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | u.Password = "" // Don't send password 73 | return c.JSON(http.StatusOK, u) 74 | } 75 | 76 | func (h *Handler) Follow(c echo.Context) (err error) { 77 | userID := userIDFromToken(c) 78 | id := c.Param("id") 79 | 80 | // Add a follower to user 81 | db := h.DB.Clone() 82 | defer db.Close() 83 | if err = db.DB("twitter").C("users"). 84 | UpdateId(bson.ObjectIdHex(id), bson.M{"$addToSet": bson.M{"followers": userID}}); err != nil { 85 | if err == mgo.ErrNotFound { 86 | return echo.ErrNotFound 87 | } 88 | } 89 | 90 | return 91 | } 92 | 93 | func userIDFromToken(c echo.Context) string { 94 | user := c.Get("user").(*jwt.Token) 95 | claims := user.Claims.(jwt.MapClaims) 96 | return claims["id"].(string) 97 | } 98 | -------------------------------------------------------------------------------- /day-06/20-echo/echo/internal/model/post.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "gopkg.in/mgo.v2/bson" 5 | ) 6 | 7 | type ( 8 | Post struct { 9 | ID bson.ObjectId `json:"id" bson:"_id,omitempty"` 10 | To string `json:"to" bson:"to"` 11 | From string `json:"from" bson:"from"` 12 | Message string `json:"message" bson:"message"` 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /day-06/20-echo/echo/internal/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "gopkg.in/mgo.v2/bson" 5 | ) 6 | 7 | type ( 8 | User struct { 9 | ID bson.ObjectId `json:"id" bson:"_id,omitempty"` 10 | Email string `json:"email" bson:"email"` 11 | Password string `json:"password,omitempty" bson:"password"` 12 | Token string `json:"token,omitempty" bson:"-"` 13 | Followers []string `json:"followers,omitempty" bson:"followers,omitempty"` 14 | } 15 | ) 16 | -------------------------------------------------------------------------------- /day-07/21-sarama/README.md.org: -------------------------------------------------------------------------------- 1 | * Golang y Kafka 2 | 3 | ** Enviando mensajes con =sarama= 4 | 5 | =sarama= es una biblioteca desarrollada por Shopify para la 6 | comunicación con Apache Kafka 7 | 8 | Para utilizar =sarama= solo tenemos que importar el módulo 9 | 10 | #+begin_src go 11 | import "github.com/Shopify/sarama" 12 | #+end_src 13 | 14 | *** Configuración 15 | 16 | #+begin_src go 17 | config := sarama.NewConfig() 18 | config.Producer.RequiredAcks = sarama.WaitForAll // Wait for all in-sync replicas to ack the message 19 | config.Producer.Retry.Max = 10 // Retry up to 10 times to produce the message 20 | config.Producer.Return.Successes = true 21 | tlsConfig := createTlsConfiguration() 22 | if tlsConfig != nil { 23 | config.Net.TLS.Config = tlsConfig // de crypto/tls 24 | config.Net.TLS.Enable = true 25 | } 26 | #+end_src 27 | 28 | *** Enviando mensajes síncronos 29 | 30 | =sarama.SyncProducer= bloquea la gorutina en espera de confirmación 31 | 32 | #+begin_src go 33 | brokerlist := [...]string{"host1:por1", "host2:port2"} 34 | producer := sarama.NewSyncProducer(brokerList, config) 35 | partition, offset, err := s.DataCollector.SendMessage(&sarama.ProducerMessage{ 36 | Key: sarama.StringEncoder("llave"), 37 | Topic: "topic", 38 | Value: sarama.StringEncoder("Mensaje"), 39 | }) 40 | producer.Close() //Liberar recursos 41 | #+end_src 42 | 43 | *** Enviando mensajes asíncronos. 44 | 45 | =sarama.AsyncProducer= envía de forma asíncrona. 46 | 47 | #+begin_src go 48 | brokerlist := [...]string{"host1:por1", "host2:port2"} 49 | producer, err := sarama.NewAsyncProducer(brokerList, config) 50 | producer.Input() <- &sarama.ProducerMessage{ 51 | Key: sarama.StringEncoder("llave"), 52 | Topic: "topic", 53 | Value: sarama.StringEncoder("Mensaje"),, 54 | } 55 | producer.AsyncClose() // Liberar recursos 56 | #+end_src 57 | 58 | ** Recibiendo mensajes con =sarama= 59 | 60 | =sarama= permite dos modos de recepción de mensajes. 61 | 62 | 1. Obtener mensajes desde una sola partición utilizando =sarama.Consumer= 63 | 2. Obtener mensajes de un clúster con =sarama.ConsumerGroup= 64 | 65 | En la carpeta =day-07/21-sarama/consumergroup= se incluye un ejemplo 66 | de consumidor usando =sarama.ConsumerGroup= 67 | 68 | ** Interceptors 69 | 70 | Los /interceptors/ permiten procesar un mensaje antes de que sea 71 | enviado o recibido. 72 | 73 | *** Interceptando mensajes enviados 74 | 75 | #+begin_src go 76 | // 77 | func (m *myinterp) OnSend(p *sarama.ProducerMessage) { 78 | // se procesa el mensaje 79 | } 80 | // Configuración del productor 81 | conf.Producer.Interceptors = []sarama.ProducerInterceptor{&myinterp{}} 82 | #+end_src 83 | 84 | *** Interceptando mensajes recibidos. 85 | 86 | #+begin_src go 87 | // Implementar sarama.ConsumerInterceptor 88 | func (m *myinterp) OnConsume(p *sarama.ConsumerMessage) { 89 | // se procesa el mensaje 90 | } 91 | // Configuración del consumidor 92 | conf.Consumer.Interceptors = []sarama.ConsumerInterceptor{&myinterp{}} 93 | #+end_src 94 | -------------------------------------------------------------------------------- /day-07/21-sarama/consumergroup/README.md: -------------------------------------------------------------------------------- 1 | # Consumergroup example 2 | 3 | This example shows you how to use the Sarama consumer group consumer. The example simply starts consuming the given Kafka topics and logs the consumed messages. 4 | 5 | ```bash 6 | $ go run main.go -brokers="127.0.0.1:9092" -topics="sarama" -group="example" 7 | ``` -------------------------------------------------------------------------------- /day-07/21-sarama/consumergroup/go.mod: -------------------------------------------------------------------------------- 1 | module sample21 2 | 3 | go 1.15 4 | 5 | require github.com/Shopify/sarama v1.27.0 6 | -------------------------------------------------------------------------------- /day-07/21-sarama/http_server/.gitignore: -------------------------------------------------------------------------------- 1 | http_server 2 | http_server.test 3 | -------------------------------------------------------------------------------- /day-07/21-sarama/http_server/README.md: -------------------------------------------------------------------------------- 1 | # HTTP server example 2 | 3 | This HTTP server example shows you how to use the AsyncProducer and SyncProducer, and how to test them using mocks. The server simply sends the data of the HTTP request's query string to Kafka, and send a 200 result if that succeeds. For every request, it will send an access log entry to Kafka as well in the background. 4 | 5 | If you need to know whether a message was successfully sent to the Kafka cluster before you can send your HTTP response, using the `SyncProducer` is probably the simplest way to achieve this. If you don't care, e.g. for the access log, using the `AsyncProducer` will let you fire and forget. You can send the HTTP response, while the message is being produced in the background. 6 | 7 | One important thing to note is that both the `SyncProducer` and `AsyncProducer` are **thread-safe**. Go's `http.Server` handles requests concurrently in different goroutines, but you can use a single producer safely. This will actually achieve efficiency gains as the producer will be able to batch messages from concurrent requests together. 8 | -------------------------------------------------------------------------------- /day-07/21-sarama/http_server/go.mod: -------------------------------------------------------------------------------- 1 | module http_server 2 | 3 | go 1.15 4 | 5 | require github.com/Shopify/sarama v1.27.2 6 | -------------------------------------------------------------------------------- /day-07/21-sarama/http_server/http_server_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/Shopify/sarama" 10 | "github.com/Shopify/sarama/mocks" 11 | ) 12 | 13 | // In normal operation, we expect one access log entry, 14 | // and one data collector entry. Let's assume both will succeed. 15 | // We should return a HTTP 200 status. 16 | func TestCollectSuccessfully(t *testing.T) { 17 | dataCollectorMock := mocks.NewSyncProducer(t, nil) 18 | dataCollectorMock.ExpectSendMessageAndSucceed() 19 | 20 | accessLogProducerMock := mocks.NewAsyncProducer(t, nil) 21 | accessLogProducerMock.ExpectInputAndSucceed() 22 | 23 | // Now, use dependency injection to use the mocks. 24 | s := &Server{ 25 | DataCollector: dataCollectorMock, 26 | AccessLogProducer: accessLogProducerMock, 27 | } 28 | 29 | // The Server's Close call is important; it will call Close on 30 | // the two mock producers, which will then validate whether all 31 | // expectations are resolved. 32 | defer safeClose(t, s) 33 | 34 | req, err := http.NewRequest("GET", "http://example.com/?data", nil) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | res := httptest.NewRecorder() 39 | s.Handler().ServeHTTP(res, req) 40 | 41 | if res.Code != 200 { 42 | t.Errorf("Expected HTTP status 200, found %d", res.Code) 43 | } 44 | 45 | if string(res.Body.Bytes()) != "Your data is stored with unique identifier important/0/1" { 46 | t.Error("Unexpected response body", res.Body) 47 | } 48 | } 49 | 50 | // Now, let's see if we handle the case of not being able to produce 51 | // to the data collector properly. In this case we should return a 500 status. 52 | func TestCollectionFailure(t *testing.T) { 53 | dataCollectorMock := mocks.NewSyncProducer(t, nil) 54 | dataCollectorMock.ExpectSendMessageAndFail(sarama.ErrRequestTimedOut) 55 | 56 | accessLogProducerMock := mocks.NewAsyncProducer(t, nil) 57 | accessLogProducerMock.ExpectInputAndSucceed() 58 | 59 | s := &Server{ 60 | DataCollector: dataCollectorMock, 61 | AccessLogProducer: accessLogProducerMock, 62 | } 63 | defer safeClose(t, s) 64 | 65 | req, err := http.NewRequest("GET", "http://example.com/?data", nil) 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | res := httptest.NewRecorder() 70 | s.Handler().ServeHTTP(res, req) 71 | 72 | if res.Code != 500 { 73 | t.Errorf("Expected HTTP status 500, found %d", res.Code) 74 | } 75 | } 76 | 77 | // We don't expect any data collector calls because the path is wrong, 78 | // so we are not setting any expectations on the dataCollectorMock. It 79 | // will still generate an access log entry though. 80 | func TestWrongPath(t *testing.T) { 81 | dataCollectorMock := mocks.NewSyncProducer(t, nil) 82 | 83 | accessLogProducerMock := mocks.NewAsyncProducer(t, nil) 84 | accessLogProducerMock.ExpectInputAndSucceed() 85 | 86 | s := &Server{ 87 | DataCollector: dataCollectorMock, 88 | AccessLogProducer: accessLogProducerMock, 89 | } 90 | defer safeClose(t, s) 91 | 92 | req, err := http.NewRequest("GET", "http://example.com/wrong?data", nil) 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | res := httptest.NewRecorder() 97 | 98 | s.Handler().ServeHTTP(res, req) 99 | 100 | if res.Code != 404 { 101 | t.Errorf("Expected HTTP status 404, found %d", res.Code) 102 | } 103 | } 104 | 105 | func safeClose(t *testing.T, o io.Closer) { 106 | if err := o.Close(); err != nil { 107 | t.Error(err) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /day-07/21-sarama/interceptors/go.mod: -------------------------------------------------------------------------------- 1 | module sample21 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/Shopify/sarama v1.27.0 7 | go.opentelemetry.io/otel v0.10.0 8 | go.opentelemetry.io/otel/exporters/stdout v0.10.0 9 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /day-07/21-sarama/interceptors/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "strings" 10 | "time" 11 | 12 | "github.com/Shopify/sarama" 13 | "go.opentelemetry.io/otel/exporters/stdout" 14 | ) 15 | 16 | var ( 17 | brokers = flag.String("brokers", "localhost:9092", "The Kafka brokers to connect to, as a comma separated list") 18 | topic = flag.String("topic", "default_topic", "The Kafka topic to use") 19 | logger = log.New(os.Stdout, "[OTelInterceptor] ", log.LstdFlags) 20 | ) 21 | 22 | func main() { 23 | flag.Parse() 24 | 25 | if *brokers == "" { 26 | logger.Fatalln("at least one broker is required") 27 | } 28 | splitBrokers := strings.Split(*brokers, ",") 29 | sarama.Logger = log.New(os.Stdout, "[Sarama] ", log.LstdFlags) 30 | 31 | // oTel stdout example 32 | pusher, err := stdout.InstallNewPipeline([]stdout.Option{ 33 | stdout.WithQuantiles([]float64{0.5, 0.9, 0.99}), 34 | }, nil) 35 | if err != nil { 36 | logger.Fatalf("failed to initialize stdout export pipeline: %v", err) 37 | } 38 | defer pusher.Stop() 39 | 40 | // simple sarama producer that adds a new producer interceptor 41 | conf := sarama.NewConfig() 42 | conf.Version = sarama.V0_11_0_0 43 | conf.Producer.Interceptors = []sarama.ProducerInterceptor{NewOTelInterceptor(splitBrokers)} 44 | 45 | producer, err := sarama.NewAsyncProducer(splitBrokers, conf) 46 | if err != nil { 47 | panic("Couldn't create a Kafka producer") 48 | } 49 | defer producer.AsyncClose() 50 | 51 | // kill -2, trap SIGINT to trigger a shutdown 52 | signals := make(chan os.Signal, 1) 53 | signal.Notify(signals, os.Interrupt) 54 | 55 | // ticker 56 | bulkSize := 2 57 | duration := 5 * time.Second 58 | ticker := time.NewTicker(duration) 59 | logger.Printf("Starting to produce %v messages every %v", bulkSize, duration) 60 | for { 61 | select { 62 | case t := <-ticker.C: 63 | now := t.Format(time.RFC3339) 64 | logger.Printf("\nproducing %v messages to topic %s at %s", bulkSize, *topic, now) 65 | for i := 0; i < bulkSize; i++ { 66 | producer.Input() <- &sarama.ProducerMessage{ 67 | Topic: *topic, Key: nil, 68 | Value: sarama.StringEncoder(fmt.Sprintf("test message %v/%v from kafka-client-go-test at %s", i+1, bulkSize, now)), 69 | } 70 | } 71 | case <-signals: 72 | logger.Println("terminating the program") 73 | logger.Println("Bye :)") 74 | return 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /day-07/21-sarama/interceptors/trace_interceptor.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/Shopify/sarama" 8 | "go.opentelemetry.io/otel/api/global" 9 | "go.opentelemetry.io/otel/api/kv" 10 | "go.opentelemetry.io/otel/api/trace" 11 | ) 12 | 13 | type OTelInterceptor struct { 14 | tracer trace.Tracer 15 | fixedAttrs []kv.KeyValue 16 | } 17 | 18 | // NewOTelInterceptor processes span for intercepted messages and add some 19 | // headers with the span data. 20 | func NewOTelInterceptor(brokers []string) *OTelInterceptor { 21 | oi := OTelInterceptor{} 22 | oi.tracer = global.TraceProvider().Tracer("shopify.com/sarama/examples/interceptors") 23 | 24 | // These are based on the spec, which was reachable as of 2020-05-15 25 | // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md 26 | oi.fixedAttrs = []kv.KeyValue{ 27 | kv.String("messaging.destination_kind", "topic"), 28 | kv.String("span.otel.kind", "PRODUCER"), 29 | kv.String("messaging.system", "kafka"), 30 | kv.String("net.transport", "IP.TCP"), 31 | kv.String("messaging.url", strings.Join(brokers, ",")), 32 | } 33 | return &oi 34 | } 35 | 36 | const ( 37 | MessageIDHeaderName = "message_id" 38 | SpanHeaderName = "span_id" 39 | TraceHeaderName = "trace_id" 40 | ) 41 | 42 | func shouldIgnoreMsg(msg *sarama.ProducerMessage) bool { 43 | // check message hasn't been here before (retries) 44 | var traceFound, spanFound, msgIDFound bool 45 | for _, h := range msg.Headers { 46 | if string(h.Key) == TraceHeaderName { 47 | traceFound = true 48 | continue 49 | } 50 | if string(h.Key) == SpanHeaderName { 51 | spanFound = true 52 | continue 53 | } 54 | if string(h.Key) == MessageIDHeaderName { 55 | msgIDFound = true 56 | } 57 | } 58 | return traceFound && spanFound && msgIDFound 59 | } 60 | 61 | func (oi *OTelInterceptor) OnSend(msg *sarama.ProducerMessage) { 62 | if shouldIgnoreMsg(msg) { 63 | return 64 | } 65 | _ = oi.tracer.WithSpan(context.TODO(), msg.Topic, 66 | func(ctx context.Context) error { 67 | span := trace.SpanFromContext(ctx) 68 | spanContext := span.SpanContext() 69 | attWithTopic := append( 70 | oi.fixedAttrs, 71 | kv.String("messaging.destination", msg.Topic), 72 | kv.String("messaging.message_id", spanContext.SpanID.String()), 73 | ) 74 | span.SetAttributes(attWithTopic...) 75 | 76 | // remove existing partial tracing headers if exists 77 | noTraceHeaders := msg.Headers[:0] 78 | for _, h := range msg.Headers { 79 | key := string(h.Key) 80 | if key != TraceHeaderName && key != SpanHeaderName && key != MessageIDHeaderName { 81 | noTraceHeaders = append(noTraceHeaders, h) 82 | } 83 | } 84 | traceHeaders := []sarama.RecordHeader{ 85 | {Key: []byte(TraceHeaderName), Value: []byte(spanContext.TraceID.String())}, 86 | {Key: []byte(SpanHeaderName), Value: []byte(spanContext.SpanID.String())}, 87 | {Key: []byte(MessageIDHeaderName), Value: []byte(spanContext.SpanID.String())}, 88 | } 89 | msg.Headers = append(noTraceHeaders, traceHeaders...) 90 | return nil 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /day-07/21-sarama/producer-consumer/cmd/consumer/consumer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Shopify/sarama" 6 | "strings" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | wg sync.WaitGroup 12 | ) 13 | 14 | func main() { 15 | //Create consumer 16 | consumer, err := sarama.NewConsumer(strings.Split("192.168.1.125:9092", ","), nil) 17 | if err != nil { 18 | fmt.Println("Failed to start consumer: %s", err) 19 | return 20 | } 21 | //Set partition 22 | partitionList, err := consumer.Partitions("nginx_log") 23 | if err != nil { 24 | fmt.Println("Failed to get the list of partitions: ", err) 25 | return 26 | } 27 | fmt.Println(partitionList) 28 | //Cyclic partition 29 | for partition := range partitionList { 30 | pc, err := consumer.ConsumePartition("nginx_log", int32(partition), sarama.OffsetNewest) 31 | if err != nil { 32 | fmt.Printf("Failed to start consumer for partition %d: %s\n", partition, err) 33 | return 34 | } 35 | defer pc.AsyncClose() 36 | wg.Add(1) 37 | go func(pc sarama.PartitionConsumer) { 38 | defer wg.Done() 39 | for msg := range pc.Messages() { 40 | fmt.Printf("Partition:%d, Offset:%d, Key:%s, Value:%s", msg.Partition, msg.Offset, string(msg.Key), string(msg.Value)) 41 | fmt.Println() 42 | } 43 | 44 | }(pc) 45 | } 46 | //time.Sleep(time.Hour) 47 | wg.Wait() 48 | consumer.Close() 49 | } 50 | -------------------------------------------------------------------------------- /day-07/21-sarama/producer-consumer/cmd/producer/producer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Shopify/sarama" 6 | ) 7 | 8 | /* 9 | Initialize NewConfig configuration sarama.NewConfig 10 | Create producer sarama.NewSyncProducer 11 | Create message sarama.ProducerMessage 12 | Send message client.SendMessage 13 | */ 14 | func main() { 15 | config := sarama.NewConfig() 16 | config.Producer.RequiredAcks = sarama.WaitForAll 17 | config.Producer.Partitioner = sarama.NewRandomPartitioner 18 | config.Producer.Return.Successes = true 19 | 20 | msg := &sarama.ProducerMessage{} 21 | msg.Topic = "TestTopic" 22 | msg.Value = sarama.StringEncoder("this is a test") 23 | 24 | client, err := sarama.NewSyncProducer([]string{"172.0.0.1:9092"}, config) 25 | if err != nil { 26 | fmt.Println("producer close, err:", err) 27 | return 28 | } 29 | 30 | defer client.Close() 31 | 32 | pid, offset, err := client.SendMessage(msg) 33 | if err != nil { 34 | fmt.Println("send message failed,", err) 35 | return 36 | } 37 | 38 | fmt.Printf("pid:%v offset:%v\n", pid, offset) 39 | } 40 | -------------------------------------------------------------------------------- /day-07/21-sarama/producer-consumer/go.mod: -------------------------------------------------------------------------------- 1 | module sample21 2 | 3 | go 1.15 4 | 5 | require github.com/Shopify/sarama v1.27.2 6 | -------------------------------------------------------------------------------- /day-07/22-concurrency-patterns/README.md.org: -------------------------------------------------------------------------------- 1 | * Patrones de concurrencia 2 | 3 | ** Futures 4 | 5 | Las /futures/ representan el resultado de un cálculo que se ejecuta de 6 | forma concurrente. Este patrón es nos permite ejecutar una operación 7 | costosa sin que impacte la ejecución del proceso principal. 8 | 9 | *** Interfaz para Future 10 | 11 | Definamos una interfaz que exprese el comportamiento que queremos en un /future/ 12 | 13 | #+begin_src go 14 | type Value interface{} 15 | 16 | type Future interface { 17 | Get(c context.Context) (Value, error) 18 | } 19 | #+end_src 20 | 21 | Usamos =context.Context= para dar la posibilidad de cancelar la espera. 22 | 23 | *** Implementando Futures 24 | 25 | Una implementación simple para /future/ es usar un canal que comunique 26 | el estado de completamiento o error. 27 | 28 | #+begin_src go 29 | type result struct { 30 | value Value 31 | err error 32 | } 33 | 34 | type futureImpl struct { 35 | result chan *result 36 | } 37 | #+end_src 38 | 39 | Por conveniencia las estructuras son privadas. 40 | 41 | #+REVEAL: split 42 | 43 | El método =Get= bloquea la gorutina actual en espera del resultado del 44 | /future/ o la señal =Done= del contexto. 45 | 46 | #+begin_src go 47 | func (f *futureImpl) Get(c context.Context) (Value, error) { 48 | select { 49 | case <-ctx.Done(): 50 | return nil, ctx.Err() 51 | case result := <-f.result: 52 | return result.value, result.err 53 | } 54 | } 55 | #+end_src 56 | 57 | #+REVEAL: split 58 | 59 | Finalmente una función para crear nuevas /futures/. 60 | 61 | #+begin_src go 62 | func NewFuture(f func() (Value, error)) Future { 63 | fut := &futureImpl { 64 | result: make(chan *result) 65 | } 66 | go func(){ 67 | defer close(fut.result) 68 | value, err := f() 69 | f.result <- &result{value, err} 70 | }() 71 | return fut 72 | } 73 | #+end_src 74 | -------------------------------------------------------------------------------- /reveal/css/layout.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Layout helpers. 3 | */ 4 | 5 | // Stretch an element vertically based on available space 6 | .reveal .stretch, 7 | .reveal .r-stretch { 8 | max-width: none; 9 | max-height: none; 10 | } 11 | 12 | .reveal pre.stretch code, 13 | .reveal pre.r-stretch code { 14 | height: 100%; 15 | max-height: 100%; 16 | box-sizing: border-box; 17 | } 18 | 19 | // Text that auto-fits it's container 20 | .reveal .r-fit-text { 21 | display: inline-block; // https://github.com/rikschennink/fitty#performance 22 | white-space: nowrap; 23 | } 24 | 25 | // Stack multiple elements on top of each other 26 | .reveal .r-stack { 27 | display: grid; 28 | } 29 | 30 | .reveal .r-stack > * { 31 | grid-area: 1/1; 32 | margin: auto; 33 | } 34 | 35 | // Horizontal and vertical stacks 36 | .reveal .r-vstack, 37 | .reveal .r-hstack { 38 | display: flex; 39 | 40 | img, video { 41 | min-width: 0; 42 | min-height: 0; 43 | object-fit: contain; 44 | } 45 | } 46 | 47 | .reveal .r-vstack { 48 | flex-direction: column; 49 | align-items: center; 50 | justify-content: center; 51 | } 52 | 53 | .reveal .r-hstack { 54 | flex-direction: row; 55 | align-items: center; 56 | justify-content: center; 57 | } 58 | 59 | // Naming based on tailwindcss 60 | .reveal .items-stretch { align-items: stretch; } 61 | .reveal .items-start { align-items: flex-start; } 62 | .reveal .items-center { align-items: center; } 63 | .reveal .items-end { align-items: flex-end; } 64 | 65 | .reveal .justify-between { justify-content: space-between; } 66 | .reveal .justify-around { justify-content: space-around; } 67 | .reveal .justify-start { justify-content: flex-start; } 68 | .reveal .justify-center { justify-content: center; } 69 | .reveal .justify-end { justify-content: flex-end; } 70 | -------------------------------------------------------------------------------- /reveal/css/print/paper.scss: -------------------------------------------------------------------------------- 1 | /* Default Print Stylesheet Template 2 | by Rob Glazebrook of CSSnewbie.com 3 | Last Updated: June 4, 2008 4 | 5 | Feel free (nay, compelled) to edit, append, and 6 | manipulate this file as you see fit. */ 7 | 8 | @media print { 9 | html:not(.print-pdf) { 10 | 11 | background: #fff; 12 | width: auto; 13 | height: auto; 14 | overflow: visible; 15 | 16 | body { 17 | background: #fff; 18 | font-size: 20pt; 19 | width: auto; 20 | height: auto; 21 | border: 0; 22 | margin: 0 5%; 23 | padding: 0; 24 | overflow: visible; 25 | float: none !important; 26 | } 27 | 28 | .nestedarrow, 29 | .controls, 30 | .fork-reveal, 31 | .share-reveal, 32 | .state-background, 33 | .reveal .progress, 34 | .reveal .backgrounds, 35 | .reveal .slide-number { 36 | display: none !important; 37 | } 38 | 39 | body, p, td, li { 40 | font-size: 20pt!important; 41 | color: #000; 42 | } 43 | 44 | h1,h2,h3,h4,h5,h6 { 45 | color: #000!important; 46 | height: auto; 47 | line-height: normal; 48 | text-align: left; 49 | letter-spacing: normal; 50 | } 51 | 52 | /* Need to reduce the size of the fonts for printing */ 53 | h1 { font-size: 28pt !important; } 54 | h2 { font-size: 24pt !important; } 55 | h3 { font-size: 22pt !important; } 56 | h4 { font-size: 22pt !important; font-variant: small-caps; } 57 | h5 { font-size: 21pt !important; } 58 | h6 { font-size: 20pt !important; font-style: italic; } 59 | 60 | a:link, 61 | a:visited { 62 | color: #000 !important; 63 | font-weight: bold; 64 | text-decoration: underline; 65 | } 66 | 67 | ul, ol, div, p { 68 | visibility: visible; 69 | position: static; 70 | width: auto; 71 | height: auto; 72 | display: block; 73 | overflow: visible; 74 | margin: 0; 75 | text-align: left !important; 76 | } 77 | .reveal pre, 78 | .reveal table { 79 | margin-left: 0; 80 | margin-right: 0; 81 | } 82 | .reveal pre code { 83 | padding: 20px; 84 | } 85 | .reveal blockquote { 86 | margin: 20px 0; 87 | } 88 | .reveal .slides { 89 | position: static !important; 90 | width: auto !important; 91 | height: auto !important; 92 | 93 | left: 0 !important; 94 | top: 0 !important; 95 | margin-left: 0 !important; 96 | margin-top: 0 !important; 97 | padding: 0 !important; 98 | zoom: 1 !important; 99 | transform: none !important; 100 | 101 | overflow: visible !important; 102 | display: block !important; 103 | 104 | text-align: left !important; 105 | perspective: none; 106 | 107 | perspective-origin: 50% 50%; 108 | } 109 | .reveal .slides section { 110 | visibility: visible !important; 111 | position: static !important; 112 | width: auto !important; 113 | height: auto !important; 114 | display: block !important; 115 | overflow: visible !important; 116 | 117 | left: 0 !important; 118 | top: 0 !important; 119 | margin-left: 0 !important; 120 | margin-top: 0 !important; 121 | padding: 60px 20px !important; 122 | z-index: auto !important; 123 | 124 | opacity: 1 !important; 125 | 126 | page-break-after: always !important; 127 | 128 | transform-style: flat !important; 129 | transform: none !important; 130 | transition: none !important; 131 | } 132 | .reveal .slides section.stack { 133 | padding: 0 !important; 134 | } 135 | .reveal section:last-of-type { 136 | page-break-after: avoid !important; 137 | } 138 | .reveal section .fragment { 139 | opacity: 1 !important; 140 | visibility: visible !important; 141 | 142 | transform: none !important; 143 | } 144 | .reveal section img { 145 | display: block; 146 | margin: 15px 0px; 147 | background: rgba(255,255,255,1); 148 | border: 1px solid #666; 149 | box-shadow: none; 150 | } 151 | 152 | .reveal section small { 153 | font-size: 0.8em; 154 | } 155 | 156 | .reveal .hljs { 157 | max-height: 100%; 158 | white-space: pre-wrap; 159 | word-wrap: break-word; 160 | word-break: break-word; 161 | font-size: 15pt; 162 | } 163 | 164 | .reveal .hljs .hljs-ln-numbers { 165 | white-space: nowrap; 166 | } 167 | 168 | .reveal .hljs td { 169 | font-size: inherit !important; 170 | color: inherit !important; 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /reveal/css/print/pdf.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * This stylesheet is used to print reveal.js 3 | * presentations to PDF. 4 | * 5 | * https://revealjs.com/pdf-export/ 6 | */ 7 | 8 | html.print-pdf { 9 | * { 10 | -webkit-print-color-adjust: exact; 11 | } 12 | 13 | & { 14 | width: 100%; 15 | height: 100%; 16 | overflow: visible; 17 | } 18 | 19 | body { 20 | margin: 0 auto !important; 21 | border: 0; 22 | padding: 0; 23 | float: none !important; 24 | overflow: visible; 25 | } 26 | 27 | /* Remove any elements not needed in print. */ 28 | .nestedarrow, 29 | .reveal .controls, 30 | .reveal .progress, 31 | .reveal .playback, 32 | .reveal.overview, 33 | .state-background { 34 | display: none !important; 35 | } 36 | 37 | .reveal pre code { 38 | overflow: hidden !important; 39 | font-family: Courier, 'Courier New', monospace !important; 40 | } 41 | 42 | .reveal { 43 | width: auto !important; 44 | height: auto !important; 45 | overflow: hidden !important; 46 | } 47 | .reveal .slides { 48 | position: static; 49 | width: 100% !important; 50 | height: auto !important; 51 | zoom: 1 !important; 52 | pointer-events: initial; 53 | 54 | left: auto; 55 | top: auto; 56 | margin: 0 !important; 57 | padding: 0 !important; 58 | 59 | overflow: visible; 60 | display: block; 61 | 62 | perspective: none; 63 | perspective-origin: 50% 50%; 64 | } 65 | 66 | .reveal .slides .pdf-page { 67 | position: relative; 68 | overflow: hidden; 69 | z-index: 1; 70 | 71 | page-break-after: always; 72 | } 73 | 74 | .reveal .slides section { 75 | visibility: visible !important; 76 | display: block !important; 77 | position: absolute !important; 78 | 79 | margin: 0 !important; 80 | padding: 0 !important; 81 | box-sizing: border-box !important; 82 | min-height: 1px; 83 | 84 | opacity: 1 !important; 85 | 86 | transform-style: flat !important; 87 | transform: none !important; 88 | } 89 | 90 | .reveal section.stack { 91 | position: relative !important; 92 | margin: 0 !important; 93 | padding: 0 !important; 94 | page-break-after: avoid !important; 95 | height: auto !important; 96 | min-height: auto !important; 97 | } 98 | 99 | .reveal img { 100 | box-shadow: none; 101 | } 102 | 103 | 104 | /* Slide backgrounds are placed inside of their slide when exporting to PDF */ 105 | .reveal .backgrounds { 106 | display: none; 107 | } 108 | .reveal .slide-background { 109 | display: block !important; 110 | position: absolute; 111 | top: 0; 112 | left: 0; 113 | width: 100%; 114 | height: 100%; 115 | z-index: auto !important; 116 | } 117 | 118 | /* Display slide speaker notes when 'showNotes' is enabled */ 119 | .reveal.show-notes { 120 | max-width: none; 121 | max-height: none; 122 | } 123 | .reveal .speaker-notes-pdf { 124 | display: block; 125 | width: 100%; 126 | height: auto; 127 | max-height: none; 128 | top: auto; 129 | right: auto; 130 | bottom: auto; 131 | left: auto; 132 | z-index: 100; 133 | } 134 | 135 | /* Layout option which makes notes appear on a separate page */ 136 | .reveal .speaker-notes-pdf[data-layout="separate-page"] { 137 | position: relative; 138 | color: inherit; 139 | background-color: transparent; 140 | padding: 20px; 141 | page-break-after: always; 142 | border: 0; 143 | } 144 | 145 | /* Display slide numbers when 'slideNumber' is enabled */ 146 | .reveal .slide-number-pdf { 147 | display: block; 148 | position: absolute; 149 | font-size: 14px; 150 | } 151 | 152 | /* This accessibility tool is not useful in PDF and breaks it visually */ 153 | .aria-status { 154 | display: none; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /reveal/css/theme/README.md: -------------------------------------------------------------------------------- 1 | ## Dependencies 2 | 3 | Themes are written using Sass to keep things modular and reduce the need for repeated selectors across files. Make sure that you have the reveal.js development environment installed before proceeding: https://revealjs.com/installation/#full-setup 4 | 5 | ## Creating a Theme 6 | 7 | To create your own theme, start by duplicating a ```.scss``` file in [/css/theme/source](https://github.com/hakimel/reveal.js/blob/master/css/theme/source). It will be automatically compiled from Sass to CSS (see the [gulpfile](https://github.com/hakimel/reveal.js/blob/master/gulpfile.js)) when you run `npm run build -- css-themes`. 8 | 9 | Each theme file does four things in the following order: 10 | 11 | 1. **Include [/css/theme/template/mixins.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/mixins.scss)** 12 | Shared utility functions. 13 | 14 | 2. **Include [/css/theme/template/settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss)** 15 | Declares a set of custom variables that the template file (step 4) expects. Can be overridden in step 3. 16 | 17 | 3. **Override** 18 | This is where you override the default theme. Either by specifying variables (see [settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss) for reference) or by adding any selectors and styles you please. 19 | 20 | 4. **Include [/css/theme/template/theme.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/theme.scss)** 21 | The template theme file which will generate final CSS output based on the currently defined variables. 22 | -------------------------------------------------------------------------------- /reveal/css/theme/source/beige.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Beige theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | 15 | // Include theme-specific fonts 16 | @import url(./fonts/league-gothic/league-gothic.css); 17 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 18 | 19 | 20 | // Override theme settings (see ../template/settings.scss) 21 | $mainColor: #333; 22 | $headingColor: #333; 23 | $headingTextShadow: none; 24 | $backgroundColor: #f7f3de; 25 | $linkColor: #8b743d; 26 | $linkColorHover: lighten( $linkColor, 20% ); 27 | $selectionBackgroundColor: rgba(79, 64, 28, 0.99); 28 | $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); 29 | 30 | // Background generator 31 | @mixin bodyBackground() { 32 | @include radial-gradient( rgba(247,242,211,1), rgba(255,255,255,1) ); 33 | } 34 | 35 | // Change text colors against dark slide backgrounds 36 | @include dark-bg-text-color(#fff); 37 | 38 | 39 | // Theme template ------------------------------ 40 | @import "../template/theme"; 41 | // --------------------------------------------- 42 | -------------------------------------------------------------------------------- /reveal/css/theme/source/black.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Black theme for reveal.js. This is the opposite of the 'white' theme. 3 | * 4 | * By Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(./fonts/source-sans-pro/source-sans-pro.css); 16 | 17 | 18 | // Override theme settings (see ../template/settings.scss) 19 | $backgroundColor: #191919; 20 | 21 | $mainColor: #fff; 22 | $headingColor: #fff; 23 | 24 | $mainFontSize: 42px; 25 | $mainFont: 'Source Sans Pro', Helvetica, sans-serif; 26 | $headingFont: 'Source Sans Pro', Helvetica, sans-serif; 27 | $headingTextShadow: none; 28 | $headingLetterSpacing: normal; 29 | $headingTextTransform: uppercase; 30 | $headingFontWeight: 600; 31 | $linkColor: #42affa; 32 | $linkColorHover: lighten( $linkColor, 15% ); 33 | $selectionBackgroundColor: lighten( $linkColor, 25% ); 34 | 35 | $heading1Size: 2.5em; 36 | $heading2Size: 1.6em; 37 | $heading3Size: 1.3em; 38 | $heading4Size: 1.0em; 39 | 40 | // Change text colors against light slide backgrounds 41 | @include light-bg-text-color(#222); 42 | 43 | 44 | // Theme template ------------------------------ 45 | @import "../template/theme"; 46 | // --------------------------------------------- 47 | -------------------------------------------------------------------------------- /reveal/css/theme/source/blood.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Blood theme for reveal.js 3 | * Author: Walther http://github.com/Walther 4 | * 5 | * Designed to be used with highlight.js theme 6 | * "monokai_sublime.css" available from 7 | * https://github.com/isagalaev/highlight.js/ 8 | * 9 | * For other themes, change $codeBackground accordingly. 10 | * 11 | */ 12 | 13 | // Default mixins and settings ----------------- 14 | @import "../template/mixins"; 15 | @import "../template/settings"; 16 | // --------------------------------------------- 17 | 18 | // Include theme-specific fonts 19 | 20 | @import url(https://fonts.googleapis.com/css?family=Ubuntu:300,700,300italic,700italic); 21 | 22 | // Colors used in the theme 23 | $blood: #a23; 24 | $coal: #222; 25 | $codeBackground: #23241f; 26 | 27 | $backgroundColor: $coal; 28 | 29 | // Main text 30 | $mainFont: Ubuntu, 'sans-serif'; 31 | $mainColor: #eee; 32 | 33 | // Headings 34 | $headingFont: Ubuntu, 'sans-serif'; 35 | $headingTextShadow: 2px 2px 2px $coal; 36 | 37 | // h1 shadow, borrowed humbly from 38 | // (c) Default theme by Hakim El Hattab 39 | $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); 40 | 41 | // Links 42 | $linkColor: $blood; 43 | $linkColorHover: lighten( $linkColor, 20% ); 44 | 45 | // Text selection 46 | $selectionBackgroundColor: $blood; 47 | $selectionColor: #fff; 48 | 49 | // Change text colors against dark slide backgrounds 50 | @include light-bg-text-color(#222); 51 | 52 | 53 | // Theme template ------------------------------ 54 | @import "../template/theme"; 55 | // --------------------------------------------- 56 | 57 | // some overrides after theme template import 58 | 59 | .reveal p { 60 | font-weight: 300; 61 | text-shadow: 1px 1px $coal; 62 | } 63 | 64 | section.has-light-background { 65 | p, h1, h2, h3, h4 { 66 | text-shadow: none; 67 | } 68 | } 69 | 70 | .reveal h1, 71 | .reveal h2, 72 | .reveal h3, 73 | .reveal h4, 74 | .reveal h5, 75 | .reveal h6 { 76 | font-weight: 700; 77 | } 78 | 79 | .reveal p code { 80 | background-color: $codeBackground; 81 | display: inline-block; 82 | border-radius: 7px; 83 | } 84 | 85 | .reveal small code { 86 | vertical-align: baseline; 87 | } -------------------------------------------------------------------------------- /reveal/css/theme/source/league.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * League theme for reveal.js. 3 | * 4 | * This was the default theme pre-3.0.0. 5 | * 6 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 7 | */ 8 | 9 | 10 | // Default mixins and settings ----------------- 11 | @import "../template/mixins"; 12 | @import "../template/settings"; 13 | // --------------------------------------------- 14 | 15 | 16 | 17 | // Include theme-specific fonts 18 | @import url(./fonts/league-gothic/league-gothic.css); 19 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 20 | 21 | // Override theme settings (see ../template/settings.scss) 22 | $headingTextShadow: 0px 0px 6px rgba(0,0,0,0.2); 23 | $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); 24 | 25 | // Background generator 26 | @mixin bodyBackground() { 27 | @include radial-gradient( rgba(28,30,32,1), rgba(85,90,95,1) ); 28 | } 29 | 30 | // Change text colors against light slide backgrounds 31 | @include light-bg-text-color(#222); 32 | 33 | 34 | // Theme template ------------------------------ 35 | @import "../template/theme"; 36 | // --------------------------------------------- 37 | -------------------------------------------------------------------------------- /reveal/css/theme/source/moon.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Solarized Dark theme for reveal.js. 3 | * Author: Achim Staebler 4 | */ 5 | 6 | 7 | // Default mixins and settings ----------------- 8 | @import "../template/mixins"; 9 | @import "../template/settings"; 10 | // --------------------------------------------- 11 | 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(./fonts/league-gothic/league-gothic.css); 16 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 17 | 18 | /** 19 | * Solarized colors by Ethan Schoonover 20 | */ 21 | html * { 22 | color-profile: sRGB; 23 | rendering-intent: auto; 24 | } 25 | 26 | // Solarized colors 27 | $base03: #002b36; 28 | $base02: #073642; 29 | $base01: #586e75; 30 | $base00: #657b83; 31 | $base0: #839496; 32 | $base1: #93a1a1; 33 | $base2: #eee8d5; 34 | $base3: #fdf6e3; 35 | $yellow: #b58900; 36 | $orange: #cb4b16; 37 | $red: #dc322f; 38 | $magenta: #d33682; 39 | $violet: #6c71c4; 40 | $blue: #268bd2; 41 | $cyan: #2aa198; 42 | $green: #859900; 43 | 44 | // Override theme settings (see ../template/settings.scss) 45 | $mainColor: $base1; 46 | $headingColor: $base2; 47 | $headingTextShadow: none; 48 | $backgroundColor: $base03; 49 | $linkColor: $blue; 50 | $linkColorHover: lighten( $linkColor, 20% ); 51 | $selectionBackgroundColor: $magenta; 52 | 53 | // Change text colors against light slide backgrounds 54 | @include light-bg-text-color(#222); 55 | 56 | // Theme template ------------------------------ 57 | @import "../template/theme"; 58 | // --------------------------------------------- 59 | -------------------------------------------------------------------------------- /reveal/css/theme/source/night.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Black theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(https://fonts.googleapis.com/css?family=Montserrat:700); 16 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400,700,400italic,700italic); 17 | 18 | 19 | // Override theme settings (see ../template/settings.scss) 20 | $backgroundColor: #111; 21 | 22 | $mainFont: 'Open Sans', sans-serif; 23 | $linkColor: #e7ad52; 24 | $linkColorHover: lighten( $linkColor, 20% ); 25 | $headingFont: 'Montserrat', Impact, sans-serif; 26 | $headingTextShadow: none; 27 | $headingLetterSpacing: -0.03em; 28 | $headingTextTransform: none; 29 | $selectionBackgroundColor: #e7ad52; 30 | 31 | // Change text colors against light slide backgrounds 32 | @include light-bg-text-color(#222); 33 | 34 | 35 | // Theme template ------------------------------ 36 | @import "../template/theme"; 37 | // --------------------------------------------- -------------------------------------------------------------------------------- /reveal/css/theme/source/serif.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple theme for reveal.js presentations, similar 3 | * to the default theme. The accent color is brown. 4 | * 5 | * This theme is Copyright (C) 2012-2013 Owen Versteeg, http://owenversteeg.com - it is MIT licensed. 6 | */ 7 | 8 | 9 | // Default mixins and settings ----------------- 10 | @import "../template/mixins"; 11 | @import "../template/settings"; 12 | // --------------------------------------------- 13 | 14 | 15 | 16 | // Override theme settings (see ../template/settings.scss) 17 | $mainFont: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 18 | $mainColor: #000; 19 | $headingFont: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 20 | $headingColor: #383D3D; 21 | $headingTextShadow: none; 22 | $headingTextTransform: none; 23 | $backgroundColor: #F0F1EB; 24 | $linkColor: #51483D; 25 | $linkColorHover: lighten( $linkColor, 20% ); 26 | $selectionBackgroundColor: #26351C; 27 | 28 | .reveal a { 29 | line-height: 1.3em; 30 | } 31 | 32 | // Change text colors against dark slide backgrounds 33 | @include dark-bg-text-color(#fff); 34 | 35 | 36 | // Theme template ------------------------------ 37 | @import "../template/theme"; 38 | // --------------------------------------------- 39 | -------------------------------------------------------------------------------- /reveal/css/theme/source/simple.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple theme for reveal.js presentations, similar 3 | * to the default theme. The accent color is darkblue. 4 | * 5 | * This theme is Copyright (C) 2012 Owen Versteeg, https://github.com/StereotypicalApps. It is MIT licensed. 6 | * reveal.js is Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 7 | */ 8 | 9 | 10 | // Default mixins and settings ----------------- 11 | @import "../template/mixins"; 12 | @import "../template/settings"; 13 | // --------------------------------------------- 14 | 15 | 16 | 17 | // Include theme-specific fonts 18 | @import url(https://fonts.googleapis.com/css?family=News+Cycle:400,700); 19 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 20 | 21 | 22 | // Override theme settings (see ../template/settings.scss) 23 | $mainFont: 'Lato', sans-serif; 24 | $mainColor: #000; 25 | $headingFont: 'News Cycle', Impact, sans-serif; 26 | $headingColor: #000; 27 | $headingTextShadow: none; 28 | $headingTextTransform: none; 29 | $backgroundColor: #fff; 30 | $linkColor: #00008B; 31 | $linkColorHover: lighten( $linkColor, 20% ); 32 | $selectionBackgroundColor: rgba(0, 0, 0, 0.99); 33 | 34 | // Change text colors against dark slide backgrounds 35 | @include dark-bg-text-color(#fff); 36 | 37 | 38 | // Theme template ------------------------------ 39 | @import "../template/theme"; 40 | // --------------------------------------------- -------------------------------------------------------------------------------- /reveal/css/theme/source/sky.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Sky theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | 15 | // Include theme-specific fonts 16 | @import url(https://fonts.googleapis.com/css?family=Quicksand:400,700,400italic,700italic); 17 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); 18 | 19 | 20 | // Override theme settings (see ../template/settings.scss) 21 | $mainFont: 'Open Sans', sans-serif; 22 | $mainColor: #333; 23 | $headingFont: 'Quicksand', sans-serif; 24 | $headingColor: #333; 25 | $headingLetterSpacing: -0.08em; 26 | $headingTextShadow: none; 27 | $backgroundColor: #f7fbfc; 28 | $linkColor: #3b759e; 29 | $linkColorHover: lighten( $linkColor, 20% ); 30 | $selectionBackgroundColor: #134674; 31 | 32 | // Fix links so they are not cut off 33 | .reveal a { 34 | line-height: 1.3em; 35 | } 36 | 37 | // Background generator 38 | @mixin bodyBackground() { 39 | @include radial-gradient( #add9e4, #f7fbfc ); 40 | } 41 | 42 | // Change text colors against dark slide backgrounds 43 | @include dark-bg-text-color(#fff); 44 | 45 | 46 | 47 | // Theme template ------------------------------ 48 | @import "../template/theme"; 49 | // --------------------------------------------- 50 | -------------------------------------------------------------------------------- /reveal/css/theme/source/solarized.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Solarized Light theme for reveal.js. 3 | * Author: Achim Staebler 4 | */ 5 | 6 | 7 | // Default mixins and settings ----------------- 8 | @import "../template/mixins"; 9 | @import "../template/settings"; 10 | // --------------------------------------------- 11 | 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(./fonts/league-gothic/league-gothic.css); 16 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 17 | 18 | 19 | /** 20 | * Solarized colors by Ethan Schoonover 21 | */ 22 | html * { 23 | color-profile: sRGB; 24 | rendering-intent: auto; 25 | } 26 | 27 | // Solarized colors 28 | $base03: #002b36; 29 | $base02: #073642; 30 | $base01: #586e75; 31 | $base00: #657b83; 32 | $base0: #839496; 33 | $base1: #93a1a1; 34 | $base2: #eee8d5; 35 | $base3: #fdf6e3; 36 | $yellow: #b58900; 37 | $orange: #cb4b16; 38 | $red: #dc322f; 39 | $magenta: #d33682; 40 | $violet: #6c71c4; 41 | $blue: #268bd2; 42 | $cyan: #2aa198; 43 | $green: #859900; 44 | 45 | // Override theme settings (see ../template/settings.scss) 46 | $mainColor: $base00; 47 | $headingColor: $base01; 48 | $headingTextShadow: none; 49 | $backgroundColor: $base3; 50 | $linkColor: $blue; 51 | $linkColorHover: lighten( $linkColor, 20% ); 52 | $selectionBackgroundColor: $magenta; 53 | 54 | // Background generator 55 | // @mixin bodyBackground() { 56 | // @include radial-gradient( rgba($base3,1), rgba(lighten($base3, 20%),1) ); 57 | // } 58 | 59 | 60 | 61 | // Theme template ------------------------------ 62 | @import "../template/theme"; 63 | // --------------------------------------------- 64 | -------------------------------------------------------------------------------- /reveal/css/theme/source/white.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * White theme for reveal.js. This is the opposite of the 'black' theme. 3 | * 4 | * By Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(./fonts/source-sans-pro/source-sans-pro.css); 16 | 17 | 18 | // Override theme settings (see ../template/settings.scss) 19 | $backgroundColor: #fff; 20 | 21 | $mainColor: #222; 22 | $headingColor: #222; 23 | 24 | $mainFontSize: 42px; 25 | $mainFont: 'Source Sans Pro', Helvetica, sans-serif; 26 | $headingFont: 'Source Sans Pro', Helvetica, sans-serif; 27 | $headingTextShadow: none; 28 | $headingLetterSpacing: normal; 29 | $headingTextTransform: uppercase; 30 | $headingFontWeight: 600; 31 | $linkColor: #2a76dd; 32 | $linkColorHover: lighten( $linkColor, 15% ); 33 | $selectionBackgroundColor: lighten( $linkColor, 25% ); 34 | 35 | $heading1Size: 2.5em; 36 | $heading2Size: 1.6em; 37 | $heading3Size: 1.3em; 38 | $heading4Size: 1.0em; 39 | 40 | // Change text colors against dark slide backgrounds 41 | @include dark-bg-text-color(#fff); 42 | 43 | 44 | // Theme template ------------------------------ 45 | @import "../template/theme"; 46 | // --------------------------------------------- 47 | -------------------------------------------------------------------------------- /reveal/css/theme/template/exposer.scss: -------------------------------------------------------------------------------- 1 | // Exposes theme's variables for easy re-use in CSS for plugin authors 2 | 3 | :root { 4 | --background-color: #{$backgroundColor}; 5 | --main-font: #{$mainFont}; 6 | --main-font-size: #{$mainFontSize}; 7 | --main-color: #{$mainColor}; 8 | --block-margin: #{$blockMargin}; 9 | --heading-margin: #{$headingMargin}; 10 | --heading-font: #{$headingFont}; 11 | --heading-color: #{$headingColor}; 12 | --heading-line-height: #{$headingLineHeight}; 13 | --heading-letter-spacing: #{$headingLetterSpacing}; 14 | --heading-text-transform: #{$headingTextTransform}; 15 | --heading-text-shadow: #{$headingTextShadow}; 16 | --heading-font-weight: #{$headingFontWeight}; 17 | --heading1-text-shadow: #{$heading1TextShadow}; 18 | --heading1-size: #{$heading1Size}; 19 | --heading2-size: #{$heading2Size}; 20 | --heading3-size: #{$heading3Size}; 21 | --heading4-size: #{$heading4Size}; 22 | --code-font: #{$codeFont}; 23 | --link-color: #{$linkColor}; 24 | --link-color-hover: #{$linkColorHover}; 25 | --selection-background-color: #{$selectionBackgroundColor}; 26 | --selection-color: #{$selectionColor}; 27 | } 28 | -------------------------------------------------------------------------------- /reveal/css/theme/template/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin vertical-gradient( $top, $bottom ) { 2 | background: $top; 3 | background: -moz-linear-gradient( top, $top 0%, $bottom 100% ); 4 | background: -webkit-gradient( linear, left top, left bottom, color-stop(0%,$top), color-stop(100%,$bottom) ); 5 | background: -webkit-linear-gradient( top, $top 0%, $bottom 100% ); 6 | background: -o-linear-gradient( top, $top 0%, $bottom 100% ); 7 | background: -ms-linear-gradient( top, $top 0%, $bottom 100% ); 8 | background: linear-gradient( top, $top 0%, $bottom 100% ); 9 | } 10 | 11 | @mixin horizontal-gradient( $top, $bottom ) { 12 | background: $top; 13 | background: -moz-linear-gradient( left, $top 0%, $bottom 100% ); 14 | background: -webkit-gradient( linear, left top, right top, color-stop(0%,$top), color-stop(100%,$bottom) ); 15 | background: -webkit-linear-gradient( left, $top 0%, $bottom 100% ); 16 | background: -o-linear-gradient( left, $top 0%, $bottom 100% ); 17 | background: -ms-linear-gradient( left, $top 0%, $bottom 100% ); 18 | background: linear-gradient( left, $top 0%, $bottom 100% ); 19 | } 20 | 21 | @mixin radial-gradient( $outer, $inner, $type: circle ) { 22 | background: $outer; 23 | background: -moz-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 24 | background: -webkit-gradient( radial, center center, 0px, center center, 100%, color-stop(0%,$inner), color-stop(100%,$outer) ); 25 | background: -webkit-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 26 | background: -o-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 27 | background: -ms-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 28 | background: radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 29 | } 30 | 31 | @mixin light-bg-text-color( $color ) { 32 | section.has-light-background { 33 | &, h1, h2, h3, h4, h5, h6 { 34 | color: $color; 35 | } 36 | } 37 | } 38 | 39 | @mixin dark-bg-text-color( $color ) { 40 | section.has-dark-background { 41 | &, h1, h2, h3, h4, h5, h6 { 42 | color: $color; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /reveal/css/theme/template/settings.scss: -------------------------------------------------------------------------------- 1 | // Base settings for all themes that can optionally be 2 | // overridden by the super-theme 3 | 4 | // Background of the presentation 5 | $backgroundColor: #2b2b2b; 6 | 7 | // Primary/body text 8 | $mainFont: 'Lato', sans-serif; 9 | $mainFontSize: 40px; 10 | $mainColor: #eee; 11 | 12 | // Vertical spacing between blocks of text 13 | $blockMargin: 20px; 14 | 15 | // Headings 16 | $headingMargin: 0 0 $blockMargin 0; 17 | $headingFont: 'League Gothic', Impact, sans-serif; 18 | $headingColor: #eee; 19 | $headingLineHeight: 1.2; 20 | $headingLetterSpacing: normal; 21 | $headingTextTransform: uppercase; 22 | $headingTextShadow: none; 23 | $headingFontWeight: normal; 24 | $heading1TextShadow: $headingTextShadow; 25 | 26 | $heading1Size: 3.77em; 27 | $heading2Size: 2.11em; 28 | $heading3Size: 1.55em; 29 | $heading4Size: 1.00em; 30 | 31 | $codeFont: monospace; 32 | 33 | // Links and actions 34 | $linkColor: #13DAEC; 35 | $linkColorHover: lighten( $linkColor, 20% ); 36 | 37 | // Text selection 38 | $selectionBackgroundColor: #FF5E99; 39 | $selectionColor: #fff; 40 | 41 | // Generates the presentation background, can be overridden 42 | // to return a background image or gradient 43 | @mixin bodyBackground() { 44 | background: $backgroundColor; 45 | } 46 | -------------------------------------------------------------------------------- /reveal/dist/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v4.0 | 20180602 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | main, menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, main, menu, nav, section { 29 | display: block; 30 | } -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/league-gothic/LICENSE: -------------------------------------------------------------------------------- 1 | SIL Open Font License (OFL) 2 | http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL 3 | -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/league-gothic/league-gothic.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'League Gothic'; 3 | src: url('./league-gothic.eot'); 4 | src: url('./league-gothic.eot?#iefix') format('embedded-opentype'), 5 | url('./league-gothic.woff') format('woff'), 6 | url('./league-gothic.ttf') format('truetype'); 7 | 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/league-gothic/league-gothic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/league-gothic/league-gothic.eot -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/league-gothic/league-gothic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/league-gothic/league-gothic.ttf -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/league-gothic/league-gothic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/league-gothic/league-gothic.woff -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/source-sans-pro/LICENSE: -------------------------------------------------------------------------------- 1 | SIL Open Font License 2 | 3 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name ‘Source’. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 4 | 5 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 6 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 7 | 8 | —————————————————————————————- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | —————————————————————————————- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. 14 | 15 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. 16 | 17 | DEFINITIONS 18 | “Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. 19 | 20 | “Reserved Font Name” refers to any names specified as such after the copyright statement(s). 21 | 22 | “Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s). 23 | 24 | “Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. 25 | 26 | “Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. 27 | 28 | PERMISSION & CONDITIONS 29 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 30 | 31 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 32 | 33 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 34 | 35 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 36 | 37 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 38 | 39 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. 40 | 41 | TERMINATION 42 | This license becomes null and void if any of the above conditions are not met. 43 | 44 | DISCLAIMER 45 | THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.eot -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.ttf -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.woff -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.eot -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.ttf -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.woff -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.eot -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.ttf -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.woff -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.eot -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.ttf -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorodm/golang-workshop/ed9d76aeb6395d543c96ce8e31f89870a0575c08/reveal/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.woff -------------------------------------------------------------------------------- /reveal/dist/theme/fonts/source-sans-pro/source-sans-pro.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Source Sans Pro'; 3 | src: url('./source-sans-pro-regular.eot'); 4 | src: url('./source-sans-pro-regular.eot?#iefix') format('embedded-opentype'), 5 | url('./source-sans-pro-regular.woff') format('woff'), 6 | url('./source-sans-pro-regular.ttf') format('truetype'); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: 'Source Sans Pro'; 13 | src: url('./source-sans-pro-italic.eot'); 14 | src: url('./source-sans-pro-italic.eot?#iefix') format('embedded-opentype'), 15 | url('./source-sans-pro-italic.woff') format('woff'), 16 | url('./source-sans-pro-italic.ttf') format('truetype'); 17 | font-weight: normal; 18 | font-style: italic; 19 | } 20 | 21 | @font-face { 22 | font-family: 'Source Sans Pro'; 23 | src: url('./source-sans-pro-semibold.eot'); 24 | src: url('./source-sans-pro-semibold.eot?#iefix') format('embedded-opentype'), 25 | url('./source-sans-pro-semibold.woff') format('woff'), 26 | url('./source-sans-pro-semibold.ttf') format('truetype'); 27 | font-weight: 600; 28 | font-style: normal; 29 | } 30 | 31 | @font-face { 32 | font-family: 'Source Sans Pro'; 33 | src: url('./source-sans-pro-semibolditalic.eot'); 34 | src: url('./source-sans-pro-semibolditalic.eot?#iefix') format('embedded-opentype'), 35 | url('./source-sans-pro-semibolditalic.woff') format('woff'), 36 | url('./source-sans-pro-semibolditalic.ttf') format('truetype'); 37 | font-weight: 600; 38 | font-style: italic; 39 | } 40 | -------------------------------------------------------------------------------- /reveal/plugin/highlight/monokai.css: -------------------------------------------------------------------------------- 1 | /* 2 | Monokai style - ported by Luigi Maselli - http://grigio.org 3 | */ 4 | 5 | .hljs { 6 | display: block; 7 | overflow-x: auto; 8 | padding: 0.5em; 9 | background: #272822; 10 | color: #ddd; 11 | } 12 | 13 | .hljs-tag, 14 | .hljs-keyword, 15 | .hljs-selector-tag, 16 | .hljs-literal, 17 | .hljs-strong, 18 | .hljs-name { 19 | color: #f92672; 20 | } 21 | 22 | .hljs-code { 23 | color: #66d9ef; 24 | } 25 | 26 | .hljs-class .hljs-title { 27 | color: white; 28 | } 29 | 30 | .hljs-attribute, 31 | .hljs-symbol, 32 | .hljs-regexp, 33 | .hljs-link { 34 | color: #bf79db; 35 | } 36 | 37 | .hljs-string, 38 | .hljs-bullet, 39 | .hljs-subst, 40 | .hljs-title, 41 | .hljs-section, 42 | .hljs-emphasis, 43 | .hljs-type, 44 | .hljs-built_in, 45 | .hljs-builtin-name, 46 | .hljs-selector-attr, 47 | .hljs-selector-pseudo, 48 | .hljs-addition, 49 | .hljs-variable, 50 | .hljs-template-tag, 51 | .hljs-template-variable { 52 | color: #a6e22e; 53 | } 54 | 55 | .hljs-comment, 56 | .hljs-quote, 57 | .hljs-deletion, 58 | .hljs-meta { 59 | color: #75715e; 60 | } 61 | 62 | .hljs-keyword, 63 | .hljs-selector-tag, 64 | .hljs-literal, 65 | .hljs-doctag, 66 | .hljs-title, 67 | .hljs-section, 68 | .hljs-type, 69 | .hljs-selector-id { 70 | font-weight: bold; 71 | } 72 | -------------------------------------------------------------------------------- /reveal/plugin/highlight/zenburn.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Zenburn style from voldmar.ru (c) Vladimir Epifanov 4 | based on dark.css by Ivan Sagalaev 5 | 6 | */ 7 | 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | background: #3f3f3f; 13 | color: #dcdcdc; 14 | } 15 | 16 | .hljs-keyword, 17 | .hljs-selector-tag, 18 | .hljs-tag { 19 | color: #e3ceab; 20 | } 21 | 22 | .hljs-template-tag { 23 | color: #dcdcdc; 24 | } 25 | 26 | .hljs-number { 27 | color: #8cd0d3; 28 | } 29 | 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-attribute { 33 | color: #efdcbc; 34 | } 35 | 36 | .hljs-literal { 37 | color: #efefaf; 38 | } 39 | 40 | .hljs-subst { 41 | color: #8f8f8f; 42 | } 43 | 44 | .hljs-title, 45 | .hljs-name, 46 | .hljs-selector-id, 47 | .hljs-selector-class, 48 | .hljs-section, 49 | .hljs-type { 50 | color: #efef8f; 51 | } 52 | 53 | .hljs-symbol, 54 | .hljs-bullet, 55 | .hljs-link { 56 | color: #dca3a3; 57 | } 58 | 59 | .hljs-deletion, 60 | .hljs-string, 61 | .hljs-built_in, 62 | .hljs-builtin-name { 63 | color: #cc9393; 64 | } 65 | 66 | .hljs-addition, 67 | .hljs-comment, 68 | .hljs-quote, 69 | .hljs-meta { 70 | color: #7f9f7f; 71 | } 72 | 73 | 74 | .hljs-emphasis { 75 | font-style: italic; 76 | } 77 | 78 | .hljs-strong { 79 | font-weight: bold; 80 | } 81 | -------------------------------------------------------------------------------- /reveal/plugin/math/math.esm.js: -------------------------------------------------------------------------------- 1 | function e(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function n(n){for(var r=1;r { 8 | 9 | // The reveal.js instance this plugin is attached to 10 | let deck; 11 | 12 | let defaultOptions = { 13 | messageStyle: 'none', 14 | tex2jax: { 15 | inlineMath: [ [ '$', '$' ], [ '\\(', '\\)' ] ], 16 | skipTags: [ 'script', 'noscript', 'style', 'textarea', 'pre' ] 17 | }, 18 | skipStartupTypeset: true 19 | }; 20 | 21 | function loadScript( url, callback ) { 22 | 23 | let head = document.querySelector( 'head' ); 24 | let script = document.createElement( 'script' ); 25 | script.type = 'text/javascript'; 26 | script.src = url; 27 | 28 | // Wrapper for callback to make sure it only fires once 29 | let finish = () => { 30 | if( typeof callback === 'function' ) { 31 | callback.call(); 32 | callback = null; 33 | } 34 | } 35 | 36 | script.onload = finish; 37 | 38 | // IE 39 | script.onreadystatechange = () => { 40 | if ( this.readyState === 'loaded' ) { 41 | finish(); 42 | } 43 | } 44 | 45 | // Normal browsers 46 | head.appendChild( script ); 47 | 48 | } 49 | 50 | return { 51 | id: 'math', 52 | 53 | init: function( reveal ) { 54 | 55 | deck = reveal; 56 | 57 | let revealOptions = deck.getConfig().math || {}; 58 | 59 | let options = { ...defaultOptions, ...revealOptions }; 60 | let mathjax = options.mathjax || 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js'; 61 | let config = options.config || 'TeX-AMS_HTML-full'; 62 | let url = mathjax + '?config=' + config; 63 | 64 | options.tex2jax = { ...defaultOptions.tex2jax, ...revealOptions.tex2jax }; 65 | 66 | options.mathjax = options.config = null; 67 | 68 | loadScript( url, function() { 69 | 70 | MathJax.Hub.Config( options ); 71 | 72 | // Typeset followed by an immediate reveal.js layout since 73 | // the typesetting process could affect slide height 74 | MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, deck.getRevealElement() ] ); 75 | MathJax.Hub.Queue( deck.layout ); 76 | 77 | // Reprocess equations in slides when they turn visible 78 | deck.on( 'slidechanged', function( event ) { 79 | 80 | MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, event.currentSlide ] ); 81 | 82 | } ); 83 | 84 | } ); 85 | 86 | } 87 | } 88 | 89 | }; 90 | 91 | export default Plugin; 92 | -------------------------------------------------------------------------------- /reveal/plugin/zoom/zoom.esm.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * reveal.js Zoom plugin 3 | */ 4 | var e={id:"zoom",init:function(e){e.getRevealElement().addEventListener("mousedown",(function(o){var n=/Linux/.test(window.navigator.platform)?"ctrl":"alt",i=(e.getConfig().zoomKey?e.getConfig().zoomKey:n)+"Key",d=e.getConfig().zoomLevel?e.getConfig().zoomLevel:2;o[i]&&!e.isOverview()&&(o.preventDefault(),t.to({x:o.clientX,y:o.clientY,scale:d,pan:!1}))}))}},t=function(){var e=1,o=0,n=0,i=-1,d=-1,s="WebkitTransform"in document.body.style||"MozTransform"in document.body.style||"msTransform"in document.body.style||"OTransform"in document.body.style||"transform"in document.body.style;function r(t,o){var n=y();if(t.width=t.width||1,t.height=t.height||1,t.x-=(window.innerWidth-t.width*o)/2,t.y-=(window.innerHeight-t.height*o)/2,s)if(1===o)document.body.style.transform="",document.body.style.OTransform="",document.body.style.msTransform="",document.body.style.MozTransform="",document.body.style.WebkitTransform="";else{var i=n.x+"px "+n.y+"px",d="translate("+-t.x+"px,"+-t.y+"px) scale("+o+")";document.body.style.transformOrigin=i,document.body.style.OTransformOrigin=i,document.body.style.msTransformOrigin=i,document.body.style.MozTransformOrigin=i,document.body.style.WebkitTransformOrigin=i,document.body.style.transform=d,document.body.style.OTransform=d,document.body.style.msTransform=d,document.body.style.MozTransform=d,document.body.style.WebkitTransform=d}else 1===o?(document.body.style.position="",document.body.style.left="",document.body.style.top="",document.body.style.width="",document.body.style.height="",document.body.style.zoom=""):(document.body.style.position="relative",document.body.style.left=-(n.x+t.x)/o+"px",document.body.style.top=-(n.y+t.y)/o+"px",document.body.style.width=100*o+"%",document.body.style.height=100*o+"%",document.body.style.zoom=o);e=o,document.documentElement.classList&&(1!==e?document.documentElement.classList.add("zoomed"):document.documentElement.classList.remove("zoomed"))}function m(){var t=.12*window.innerWidth,i=.12*window.innerHeight,d=y();nwindow.innerHeight-i&&window.scroll(d.x,d.y+(1-(window.innerHeight-n)/i)*(14/e)),owindow.innerWidth-t&&window.scroll(d.x+(1-(window.innerWidth-o)/t)*(14/e),d.y)}function y(){return{x:void 0!==window.scrollX?window.scrollX:window.pageXOffset,y:void 0!==window.scrollY?window.scrollY:window.pageYOffset}}return s&&(document.body.style.transition="transform 0.8s ease",document.body.style.OTransition="-o-transform 0.8s ease",document.body.style.msTransition="-ms-transform 0.8s ease",document.body.style.MozTransition="-moz-transform 0.8s ease",document.body.style.WebkitTransition="-webkit-transform 0.8s ease"),document.addEventListener("keyup",(function(o){1!==e&&27===o.keyCode&&t.out()})),document.addEventListener("mousemove",(function(t){1!==e&&(o=t.clientX,n=t.clientY)})),{to:function(o){if(1!==e)t.out();else{if(o.x=o.x||0,o.y=o.y||0,o.element){var n=o.element.getBoundingClientRect();o.x=n.left-20,o.y=n.top-20,o.width=n.width+40,o.height=n.height+40}void 0!==o.width&&void 0!==o.height&&(o.scale=Math.max(Math.min(window.innerWidth/o.width,window.innerHeight/o.height),1)),o.scale>1&&(o.x*=o.scale,o.y*=o.scale,r(o,o.scale),!1!==o.pan&&(i=setTimeout((function(){d=setInterval(m,1e3/60)}),800)))}},out:function(){clearTimeout(i),clearInterval(d),r({x:0,y:0},1),e=1},magnify:function(e){this.to(e)},reset:function(){this.out()},zoomLevel:function(){return e}}}();export default function(){return e} 5 | -------------------------------------------------------------------------------- /reveal/plugin/zoom/zoom.js: -------------------------------------------------------------------------------- 1 | !function(e,o){"object"==typeof exports&&"undefined"!=typeof module?module.exports=o():"function"==typeof define&&define.amd?define(o):(e="undefined"!=typeof globalThis?globalThis:e||self).RevealZoom=o()}(this,(function(){"use strict"; 2 | /*! 3 | * reveal.js Zoom plugin 4 | */var e={id:"zoom",init:function(e){e.getRevealElement().addEventListener("mousedown",(function(t){var n=/Linux/.test(window.navigator.platform)?"ctrl":"alt",i=(e.getConfig().zoomKey?e.getConfig().zoomKey:n)+"Key",d=e.getConfig().zoomLevel?e.getConfig().zoomLevel:2;t[i]&&!e.isOverview()&&(t.preventDefault(),o.to({x:t.clientX,y:t.clientY,scale:d,pan:!1}))}))}},o=function(){var e=1,t=0,n=0,i=-1,d=-1,s="WebkitTransform"in document.body.style||"MozTransform"in document.body.style||"msTransform"in document.body.style||"OTransform"in document.body.style||"transform"in document.body.style;function r(o,t){var n=l();if(o.width=o.width||1,o.height=o.height||1,o.x-=(window.innerWidth-o.width*t)/2,o.y-=(window.innerHeight-o.height*t)/2,s)if(1===t)document.body.style.transform="",document.body.style.OTransform="",document.body.style.msTransform="",document.body.style.MozTransform="",document.body.style.WebkitTransform="";else{var i=n.x+"px "+n.y+"px",d="translate("+-o.x+"px,"+-o.y+"px) scale("+t+")";document.body.style.transformOrigin=i,document.body.style.OTransformOrigin=i,document.body.style.msTransformOrigin=i,document.body.style.MozTransformOrigin=i,document.body.style.WebkitTransformOrigin=i,document.body.style.transform=d,document.body.style.OTransform=d,document.body.style.msTransform=d,document.body.style.MozTransform=d,document.body.style.WebkitTransform=d}else 1===t?(document.body.style.position="",document.body.style.left="",document.body.style.top="",document.body.style.width="",document.body.style.height="",document.body.style.zoom=""):(document.body.style.position="relative",document.body.style.left=-(n.x+o.x)/t+"px",document.body.style.top=-(n.y+o.y)/t+"px",document.body.style.width=100*t+"%",document.body.style.height=100*t+"%",document.body.style.zoom=t);e=t,document.documentElement.classList&&(1!==e?document.documentElement.classList.add("zoomed"):document.documentElement.classList.remove("zoomed"))}function m(){var o=.12*window.innerWidth,i=.12*window.innerHeight,d=l();nwindow.innerHeight-i&&window.scroll(d.x,d.y+(1-(window.innerHeight-n)/i)*(14/e)),twindow.innerWidth-o&&window.scroll(d.x+(1-(window.innerWidth-t)/o)*(14/e),d.y)}function l(){return{x:void 0!==window.scrollX?window.scrollX:window.pageXOffset,y:void 0!==window.scrollY?window.scrollY:window.pageYOffset}}return s&&(document.body.style.transition="transform 0.8s ease",document.body.style.OTransition="-o-transform 0.8s ease",document.body.style.msTransition="-ms-transform 0.8s ease",document.body.style.MozTransition="-moz-transform 0.8s ease",document.body.style.WebkitTransition="-webkit-transform 0.8s ease"),document.addEventListener("keyup",(function(t){1!==e&&27===t.keyCode&&o.out()})),document.addEventListener("mousemove",(function(o){1!==e&&(t=o.clientX,n=o.clientY)})),{to:function(t){if(1!==e)o.out();else{if(t.x=t.x||0,t.y=t.y||0,t.element){var n=t.element.getBoundingClientRect();t.x=n.left-20,t.y=n.top-20,t.width=n.width+40,t.height=n.height+40}void 0!==t.width&&void 0!==t.height&&(t.scale=Math.max(Math.min(window.innerWidth/t.width,window.innerHeight/t.height),1)),t.scale>1&&(t.x*=t.scale,t.y*=t.scale,r(t,t.scale),!1!==t.pan&&(i=setTimeout((function(){d=setInterval(m,1e3/60)}),800)))}},out:function(){clearTimeout(i),clearInterval(d),r({x:0,y:0},1),e=1},magnify:function(e){this.to(e)},reset:function(){this.out()},zoomLevel:function(){return e}}}();return function(){return e}})); 5 | --------------------------------------------------------------------------------