├── .gitattributes ├── .gitignore ├── Appendix ├── 01_no-imports │ ├── glide.lock │ ├── glide.yaml │ ├── init │ ├── main.go │ └── src │ │ ├── packagea │ │ └── featurea.go │ │ └── packageb │ │ └── featureb.go ├── 02_dependency-with-import │ ├── glide.yaml │ ├── init │ ├── main.go │ └── src │ │ ├── packagea │ │ └── featurea.go │ │ └── packageb │ │ └── featureb.go ├── 03_with-third-party-import │ ├── glide.lock │ ├── glide.yaml │ ├── init │ ├── main.go │ └── src │ │ ├── packagea │ │ └── featurea.go │ │ └── packageb │ │ └── featureb.go └── README.md ├── Chapter01 ├── 01_oop │ └── cars.go ├── 02_fib │ ├── 01_simple.go │ ├── 01_simple_test.go │ ├── 02_memoize.go │ ├── 02_memoize_test.go │ ├── 03_channel.go │ └── 03_channel_test.go ├── 03_sum │ ├── 01_loop.go │ ├── 01_loop_test.go │ ├── 02_recursive.go │ ├── 02_recursive_test.go │ ├── 03_tailcall.go │ └── 03_tailcall_test.go ├── chapter1.go └── misc │ └── func_literal.go ├── Chapter02 ├── 01_iterator │ ├── init │ └── main.go └── misc │ └── ips.log ├── Chapter03 └── hof │ ├── glide.yaml │ ├── init │ ├── main.go │ └── src │ └── hof │ ├── cars.csv │ ├── cars.go │ ├── generator.go │ ├── more_cars.csv │ ├── restful.go │ ├── types.go │ └── utils.go ├── Chapter04 ├── 01_duck │ ├── init │ └── main.go └── 02_misc │ ├── main.go │ └── misc.go ├── Chapter05 ├── 01_reader_writer │ ├── glide.lock │ ├── glide.yaml │ ├── init │ └── main.go ├── 02_decorator │ ├── glide.lock │ ├── glide.yaml │ ├── init │ ├── main.go │ ├── src │ │ ├── decorator │ │ │ ├── decorator.go │ │ │ ├── requestor.go │ │ │ └── simple_log.go │ │ └── easy_metrics │ │ │ └── metrics.go │ └── trace-log.txt └── misc │ ├── logs │ ├── console-log-book.txt │ ├── results.log │ ├── trace-log-book.txt │ └── trace-log.txt │ └── misc.go ├── Chapter06 ├── 01_dependency-rule-good │ ├── glide.lock │ ├── glide.yaml │ ├── init │ ├── main.go │ └── src │ │ ├── packagea │ │ └── featurea.go │ │ └── packageb │ │ └── featureb.go ├── 02_circulardep │ ├── glide.lock │ ├── glide.yaml │ ├── init │ ├── main.go │ └── src │ │ ├── packagea │ │ └── featurea.go │ │ └── packageb │ │ └── featureb.go ├── 03_observer │ ├── glide.lock │ ├── glide.yaml │ ├── init │ ├── main.go │ └── src │ │ └── observer │ │ ├── observer.go │ │ └── subject.go └── 04_onion │ ├── .gitignore │ ├── activate │ ├── config.toml │ ├── init │ ├── main.go │ └── src │ ├── domain │ ├── api.go │ ├── domain.go │ ├── file.go │ └── log_file.go │ ├── infrastructure │ ├── gcphandler.go │ ├── infrastructure_test │ │ └── localhandler_test.go │ └── localhandler.go │ ├── interfaces │ ├── gcpstorage.go │ ├── impl.go_STOP │ ├── interfaces_test │ │ └── webservice_test.go │ ├── localstorage.go │ ├── webservice.go │ └── webservice_helpers.go │ ├── usecases │ └── usecases.go │ └── utils │ ├── logger.go │ ├── options.go │ ├── utils.go │ └── utils_test │ └── utils_test.go ├── Chapter07 └── func-param │ ├── config.toml │ ├── glide.lock │ ├── glide.yaml │ ├── init │ ├── main.go │ ├── src │ ├── server │ │ ├── server.go │ │ └── server_options.go │ └── utils │ │ ├── logger.go │ │ ├── options.go │ │ ├── utils.go │ │ └── utils_test │ │ └── utils_test.go │ ├── trace-debug-log.txt │ └── trace-log.txt ├── Chapter08 ├── 01_imperative │ ├── ex1.go │ ├── glide.lock │ ├── glide.yaml │ └── init ├── 02_concurrent │ ├── ex1.go │ ├── glide.lock │ ├── glide.yaml │ └── init ├── 03_buffered │ ├── ex1.go │ ├── glide.lock │ ├── glide.yaml │ └── init ├── 04_buffered_cpus │ ├── ex1.go │ ├── glide.lock │ ├── glide.yaml │ └── init └── main.go ├── Chapter09 ├── misc │ ├── ex1 │ │ └── anonymous.go │ ├── ex2 │ │ ├── lambda.go │ │ └── lambda.js │ ├── ex3 │ │ └── lambda.go │ └── ex4 │ │ └── typeinfer.go └── wip │ └── fpingo │ ├── config.toml │ ├── glide.lock │ ├── glide.yaml │ ├── init │ ├── lambda.go │ └── simple-functor.go ├── LICENSE ├── README.md └── fp-go-master.zip /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /Appendix/01_no-imports/glide.lock: -------------------------------------------------------------------------------- 1 | hash: 08a9e1dbeba0a2ab1a80f08116c1644d398a346e60386239774342f3f8f5ba12 2 | updated: 2017-10-03T17:59:32.240409573-04:00 3 | imports: [] 4 | testImports: [] 5 | -------------------------------------------------------------------------------- /Appendix/01_no-imports/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: [] 3 | ignore: 4 | - ./packagea 5 | - ./packageb 6 | -------------------------------------------------------------------------------- /Appendix/01_no-imports/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (it will create one if you don't have one) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Appendix/01_no-imports/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | a "packagea" 5 | ) 6 | 7 | func main() { 8 | a.Atask() 9 | } 10 | 11 | -------------------------------------------------------------------------------- /Appendix/01_no-imports/src/packagea/featurea.go: -------------------------------------------------------------------------------- 1 | package packagea 2 | 3 | import b "packageb" 4 | 5 | func Atask() { 6 | println("A") 7 | b.Btask() 8 | } 9 | 10 | -------------------------------------------------------------------------------- /Appendix/01_no-imports/src/packageb/featureb.go: -------------------------------------------------------------------------------- 1 | package packageb 2 | 3 | func Btask() { 4 | println("B") 5 | } 6 | -------------------------------------------------------------------------------- /Appendix/02_dependency-with-import/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: [] 3 | ignore: 4 | - ./packagea 5 | - ./packageb 6 | -------------------------------------------------------------------------------- /Appendix/02_dependency-with-import/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (it will create one if you don't have one) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Appendix/02_dependency-with-import/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | a "packagea" 5 | ) 6 | 7 | func main() { 8 | a.Atask() 9 | } 10 | 11 | -------------------------------------------------------------------------------- /Appendix/02_dependency-with-import/src/packagea/featurea.go: -------------------------------------------------------------------------------- 1 | package packagea 2 | 3 | import ( 4 | b "packageb" 5 | "fmt" 6 | ) 7 | 8 | func Atask() { 9 | fmt.Println("A") 10 | b.Btask() 11 | } 12 | 13 | -------------------------------------------------------------------------------- /Appendix/02_dependency-with-import/src/packageb/featureb.go: -------------------------------------------------------------------------------- 1 | package packageb 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | func Btask() { 8 | log.Println("B") 9 | } 10 | -------------------------------------------------------------------------------- /Appendix/03_with-third-party-import/glide.lock: -------------------------------------------------------------------------------- 1 | hash: a0fcf3a6183f8cabd79714b20ba31c7a704544e8dc016086809a25ecd8b7d519 2 | updated: 2017-10-03T19:04:05.145422547-04:00 3 | imports: 4 | - name: github.com/go-goodies/go_utils 5 | version: 8080b1735a88ccb3a2bb26f32bad85315797ddf2 6 | - name: github.com/margnus1/go-deepcopy 7 | version: e90e9caf2f249f9b952935f1ed9a93a941d6c38a 8 | - name: github.com/nu7hatch/gouuid 9 | version: 179d4d0c4d8d407a32af483c2354df1d2c91e6c3 10 | testImports: [] 11 | -------------------------------------------------------------------------------- /Appendix/03_with-third-party-import/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: 3 | - package: github.com/go-goodies/go_utils 4 | ignore: 5 | - ./packagea 6 | - ./packageb 7 | -------------------------------------------------------------------------------- /Appendix/03_with-third-party-import/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (it will create one if you don't have one) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Appendix/03_with-third-party-import/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | a "packagea" 5 | ) 6 | 7 | func main() { 8 | a.Atask() 9 | } 10 | 11 | -------------------------------------------------------------------------------- /Appendix/03_with-third-party-import/src/packagea/featurea.go: -------------------------------------------------------------------------------- 1 | package packagea 2 | 3 | import ( 4 | b "packageb" 5 | "fmt" 6 | u "github.com/go-goodies/go_utils" 7 | ) 8 | 9 | func Atask() { 10 | fmt.Println(u.PadLeft("A", 3)) 11 | b.Btask() 12 | } 13 | 14 | -------------------------------------------------------------------------------- /Appendix/03_with-third-party-import/src/packageb/featureb.go: -------------------------------------------------------------------------------- 1 | package packageb 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func Btask() { 8 | fmt.Println("B") 9 | } 10 | -------------------------------------------------------------------------------- /Appendix/README.md: -------------------------------------------------------------------------------- 1 | For details and documenation of source code read the book, Learning Functional Programming in Go. 2 | 3 | Book can be purchased at the following: 4 | 5 | https://www.packtpub.com/application-development/learning-functional-programming-go 6 | https://www.amazon.com/Learning-Functional-Programming-Lex-Sheehan-ebook/dp/B0725B8MYW 7 | -------------------------------------------------------------------------------- /Chapter01/01_oop/cars.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | type Car struct { 9 | Model string 10 | } 11 | type Cars []Car 12 | 13 | func (cars *Cars) Add(car Car) { 14 | myCars = append(myCars, car) 15 | } 16 | 17 | func (cars *Cars) Find(model string) (*Car, error) { 18 | for _, car := range *cars { 19 | if car.Model == model { 20 | return &car, nil 21 | } 22 | } 23 | return nil, errors.New("car not found") 24 | } 25 | 26 | var myCars Cars 27 | 28 | func main() { 29 | myCars.Add(Car{"IS250"}) 30 | myCars.Add(Car{"Blazer"}) 31 | myCars.Add(Car{"Highlander"}) 32 | 33 | car, err := myCars.Find("Highlander") 34 | if err != nil { 35 | fmt.Printf("ERROR: %v", car) 36 | } else { 37 | fmt.Printf("Found %v", car) 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /Chapter01/02_fib/01_simple.go: -------------------------------------------------------------------------------- 1 | package fib 2 | 3 | func FibSimple(n int) int { 4 | if n == 0 || n == 1 { 5 | return 1 6 | } else { 7 | return FibSimple(n-2) + FibSimple(n-1) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Chapter01/02_fib/01_simple_test.go: -------------------------------------------------------------------------------- 1 | // File: chapter1/_01_fib/ex1_test.go 2 | package fib 3 | 4 | import "testing" 5 | 6 | var fibTests = []struct { 7 | a int 8 | expected int 9 | }{ 10 | {1, 1}, 11 | {2, 2}, 12 | {3, 3}, 13 | {4, 5}, 14 | {20, 10946}, 15 | {42, 433494437}, 16 | } 17 | 18 | func TestSimple(t *testing.T) { 19 | for _, ft := range fibTests { 20 | if v := FibSimple(ft.a); v != ft.expected { 21 | t.Errorf("FibSimple(%d) returned %d, expected %d", ft.a, v, ft.expected) 22 | } 23 | } 24 | } 25 | 26 | func BenchmarkFibSimple(b *testing.B) { 27 | fn := FibSimple 28 | for i := 0; i < b.N; i++ { 29 | _ = fn(8) 30 | } 31 | } 32 | 33 | func benchmarkFibSimple(i int, b *testing.B) { 34 | for n := 0; n < b.N; n++ { 35 | FibSimple(i) 36 | } 37 | } 38 | 39 | func BenchmarkFibSimple1(b *testing.B) { benchmarkFibSimple(1, b) } 40 | func BenchmarkFibSimple2(b *testing.B) { benchmarkFibSimple(2, b) } 41 | func BenchmarkFibSimple3(b *testing.B) { benchmarkFibSimple(3, b) } 42 | func BenchmarkFibSimple10(b *testing.B) { benchmarkFibSimple(4, b) } 43 | func BenchmarkFibSimple20(b *testing.B) { benchmarkFibSimple(20, b) } 44 | func BenchmarkFibSimple40(b *testing.B) { benchmarkFibSimple(42, b) } -------------------------------------------------------------------------------- /Chapter01/02_fib/02_memoize.go: -------------------------------------------------------------------------------- 1 | package fib 2 | 3 | func Memoize(mf Memoized) Memoized { 4 | cache := make(map[int]int) 5 | return func(key int) int { 6 | if val, found := cache[key]; found { 7 | return val 8 | } 9 | temp := mf(key) 10 | cache[key] = temp 11 | return temp 12 | } 13 | } 14 | 15 | type Memoized func(int) int 16 | var fibMem Memoized 17 | func FibMemoized(n int) int { 18 | n += 1 19 | fibMem = Memoize(func(n int) int { 20 | if n == 0 || n == 1 { 21 | return n 22 | } 23 | return fibMem(n - 2) + fibMem(n - 1) 24 | }) 25 | return fibMem(n) 26 | } 27 | -------------------------------------------------------------------------------- /Chapter01/02_fib/02_memoize_test.go: -------------------------------------------------------------------------------- 1 | package fib 2 | 3 | import "testing" 4 | 5 | func TestMemoized(t *testing.T) { 6 | for _, ft := range fibTests { 7 | if v := FibMemoized(ft.a); v != ft.expected { 8 | t.Errorf("FibMemoized(%d) returned %d, expected %d", ft.a, v, ft.expected) 9 | } 10 | } 11 | } 12 | 13 | func BenchmarkFibMemoized(b *testing.B) { 14 | fn := FibMemoized 15 | for i := 0; i < b.N; i++ { 16 | _ = fn(8) 17 | } 18 | } 19 | 20 | func benchmarkFibMemoized(i int, b *testing.B) { 21 | for n := 0; n < b.N; n++ { 22 | FibMemoized(i) 23 | } 24 | } 25 | 26 | func BenchmarkFibMemoized1(b *testing.B) { benchmarkFibMemoized(1, b) } 27 | func BenchmarkFibMemoized2(b *testing.B) { benchmarkFibMemoized(2, b) } 28 | func BenchmarkFibMemoized3(b *testing.B) { benchmarkFibMemoized(3, b) } 29 | func BenchmarkFibMemoized10(b *testing.B) { benchmarkFibMemoized(4, b) } 30 | func BenchmarkFibMemoized20(b *testing.B) { benchmarkFibMemoized(20, b) } 31 | func BenchmarkFibMemoized40(b *testing.B) { benchmarkFibMemoized(42, b) } -------------------------------------------------------------------------------- /Chapter01/02_fib/03_channel.go: -------------------------------------------------------------------------------- 1 | package fib 2 | 3 | func Channel(ch chan int, counter int) { 4 | n1, n2 := 0, 1 5 | for i := 0; i < counter; i++ { 6 | ch <- n1 7 | n1, n2 = n2, n1 + n2 8 | } 9 | close(ch) 10 | } 11 | 12 | func FibChanneled(n int) int { 13 | n += 2 14 | ch := make(chan int) 15 | go Channel(ch, n) 16 | i := 0; var result int 17 | for num := range ch { 18 | result = num 19 | i++ 20 | } 21 | return result 22 | } 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Chapter01/02_fib/03_channel_test.go: -------------------------------------------------------------------------------- 1 | package fib 2 | 3 | import "testing" 4 | 5 | func TestChanneled(t *testing.T) { 6 | for _, ft := range fibTests { 7 | if v := FibChanneled(ft.a); v != ft.expected { 8 | t.Errorf("FibChanneled(%d) returned %d, expected %d", ft.a, v, ft.expected) 9 | } 10 | } 11 | } 12 | 13 | func BenchmarkFibChanneled(b *testing.B) { 14 | fn := FibChanneled 15 | for i := 0; i < b.N; i++ { 16 | _ = fn(8) 17 | } 18 | } 19 | 20 | func benchmarkFibChanneled(i int, b *testing.B) { 21 | for n := 0; n < b.N; n++ { 22 | FibChanneled(i) 23 | } 24 | } 25 | 26 | func BenchmarkFibChanneled1(b *testing.B) { benchmarkFibChanneled(1, b) } 27 | func BenchmarkFibChanneled2(b *testing.B) { benchmarkFibChanneled(2, b) } 28 | func BenchmarkFibChanneled3(b *testing.B) { benchmarkFibChanneled(3, b) } 29 | func BenchmarkFibChanneled10(b *testing.B) { benchmarkFibChanneled(4, b) } 30 | func BenchmarkFibChanneled20(b *testing.B) { benchmarkFibChanneled(20, b) } 31 | func BenchmarkFibChanneled40(b *testing.B) { benchmarkFibChanneled(42, b) } -------------------------------------------------------------------------------- /Chapter01/03_sum/01_loop.go: -------------------------------------------------------------------------------- 1 | package fib 2 | 3 | func SumLoop(nums []int) int { 4 | sum := 0 5 | for _, num := range nums { 6 | sum += num 7 | } 8 | return sum 9 | } 10 | -------------------------------------------------------------------------------- /Chapter01/03_sum/01_loop_test.go: -------------------------------------------------------------------------------- 1 | package fib 2 | 3 | import "testing" 4 | 5 | var sumTests = []struct { 6 | a []int 7 | expected int 8 | }{ 9 | {[]int{1}, 1}, 10 | {[]int{1, 2}, 3}, 11 | {[]int{1, 2, 3}, 6}, 12 | {[]int{1, 2, 3, 4}, 10}, 13 | } 14 | 15 | func TestSumLoop(t *testing.T) { 16 | for _, st := range sumTests { 17 | if v := SumLoop(st.a); v != st.expected { 18 | t.Errorf("SumLoop(%d) returned %d, expected %d", st.a, v, st.expected) 19 | } 20 | } 21 | } 22 | 23 | func BenchmarkSumLoop(b *testing.B) { 24 | fn := SumLoop 25 | for i := 0; i < b.N; i++ { 26 | _ = fn([]int{1, 2, 3}) 27 | } 28 | } 29 | 30 | func benchmarkSumLoop(s []int, b *testing.B) { 31 | for n := 0; n < b.N; n++ { 32 | SumLoop(s) 33 | } 34 | } 35 | 36 | func BenchmarkSumLoop1(b *testing.B) { benchmarkSumLoop([]int{1}, b) } 37 | func BenchmarkSumLoop2(b *testing.B) { benchmarkSumLoop([]int{1, 2}, b) } 38 | func BenchmarkSumLoop3(b *testing.B) { benchmarkSumLoop([]int{1, 2, 3}, b) } 39 | func BenchmarkSumLoop10(b *testing.B) { benchmarkSumLoop([]int{1, 2, 3, 4}, b) } 40 | func BenchmarkSumLoop20(b *testing.B) { benchmarkSumLoop([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, b) } 41 | func BenchmarkSumLoop40(b *testing.B) { benchmarkSumLoop([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40}, b) } -------------------------------------------------------------------------------- /Chapter01/03_sum/02_recursive.go: -------------------------------------------------------------------------------- 1 | package fib 2 | 3 | func SumRecursive(nums []int) int { 4 | if len(nums) == 0 { 5 | return 0 6 | } 7 | return nums[0] + SumRecursive(nums[1:]) 8 | } 9 | -------------------------------------------------------------------------------- /Chapter01/03_sum/02_recursive_test.go: -------------------------------------------------------------------------------- 1 | package fib 2 | 3 | import "testing" 4 | 5 | func TestSumRecursive(t *testing.T) { 6 | for _, st := range sumTests { 7 | if v := SumRecursive(st.a); v != st.expected { 8 | t.Errorf("SumRecursive(%d) returned %d, expected %d", st.a, v, st.expected) 9 | } 10 | } 11 | } 12 | 13 | func BenchmarkSumRecursive(b *testing.B) { 14 | fn := SumRecursive 15 | for i := 0; i < b.N; i++ { 16 | _ = fn([]int{1, 2, 3}) 17 | } 18 | } 19 | 20 | func benchmarkSumRecursive(s []int, b *testing.B) { 21 | for n := 0; n < b.N; n++ { 22 | SumRecursive(s) 23 | } 24 | } 25 | 26 | func BenchmarkSumRecursive1(b *testing.B) { benchmarkSumRecursive([]int{1}, b) } 27 | func BenchmarkSumRecursive2(b *testing.B) { benchmarkSumRecursive([]int{1, 2}, b) } 28 | func BenchmarkSumRecursive3(b *testing.B) { benchmarkSumRecursive([]int{1, 2, 3}, b) } 29 | func BenchmarkSumRecursive10(b *testing.B) { benchmarkSumRecursive([]int{1, 2, 3, 4}, b) } 30 | func BenchmarkSumRecursive20(b *testing.B) { benchmarkSumRecursive([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, b) } 31 | func BenchmarkSumRecursive40(b *testing.B) { benchmarkSumRecursive([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40}, b) } -------------------------------------------------------------------------------- /Chapter01/03_sum/03_tailcall.go: -------------------------------------------------------------------------------- 1 | package fib 2 | 3 | func SumTailCall(vs []int) int { 4 | if len(vs) == 0 { 5 | return 0 6 | } 7 | return vs[0] + SumTailCall(vs[1:]) 8 | } 9 | -------------------------------------------------------------------------------- /Chapter01/03_sum/03_tailcall_test.go: -------------------------------------------------------------------------------- 1 | package fib 2 | 3 | import "testing" 4 | 5 | func TestSumTailCall(t *testing.T) { 6 | for _, st := range sumTests { 7 | if v := SumTailCall(st.a); v != st.expected { 8 | t.Errorf("SumTailCall(%d) returned %d, expected %d", st.a, v, st.expected) 9 | } 10 | } 11 | } 12 | 13 | func BenchmarkSumTailCall(b *testing.B) { 14 | fn := SumTailCall 15 | for i := 0; i < b.N; i++ { 16 | _ = fn([]int{1, 2, 3}) 17 | } 18 | } 19 | 20 | func benchmarkSumTailCall(s []int, b *testing.B) { 21 | for n := 0; n < b.N; n++ { 22 | SumTailCall(s) 23 | } 24 | } 25 | 26 | func BenchmarkSumTailCall1(b *testing.B) { benchmarkSumTailCall([]int{1}, b) } 27 | func BenchmarkSumTailCall2(b *testing.B) { benchmarkSumTailCall([]int{1, 2}, b) } 28 | func BenchmarkSumTailCall3(b *testing.B) { benchmarkSumTailCall([]int{1, 2, 3}, b) } 29 | func BenchmarkSumTailCall10(b *testing.B) { benchmarkSumTailCall([]int{1, 2, 3, 4}, b) } 30 | func BenchmarkSumTailCall20(b *testing.B) { benchmarkSumTailCall([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, b) } 31 | func BenchmarkSumTailCall40(b *testing.B) { benchmarkSumTailCall([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40}, b) } -------------------------------------------------------------------------------- /Chapter01/chapter1.go: -------------------------------------------------------------------------------- 1 | package ch01_pure_fp 2 | 3 | import ( 4 | "fmt" 5 | . "bitbucket.org/lsheehan/fp-in-go/chapter1/01_fib" 6 | ) 7 | 8 | func main() { 9 | fibSimple := FibSimple 10 | fmt.Printf("FibSimple: %v\n", fibSimple(8)) 11 | 12 | fibMem := FibMemoized 13 | fmt.Printf("FibMemoized: %v\n", fibMem(8)) 14 | 15 | 16 | fibChan := FibChanneled 17 | fmt.Printf("FibChanneled: %v\n", fibChan(8)) 18 | 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /Chapter01/misc/func_literal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func curryAddTwo(n int) (ret int) { 4 | defer func(){ret = n + 2}() 5 | return n 6 | } 7 | 8 | func main() { 9 | println(curryAddTwo(1)) 10 | } -------------------------------------------------------------------------------- /Chapter02/01_iterator/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (if you set USES_TOML_CONFIG_YN=yes) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter02/01_iterator/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type IntIterator interface { 4 | Next() (value string, ok bool) 5 | } 6 | const INVALID_INT_VAL = -1 7 | const INVALID_STRING_VAL = "" 8 | 9 | type Collection struct { 10 | index int 11 | List []string 12 | } 13 | 14 | func (collection *Collection) Next() (value string, ok bool) { 15 | collection.index++ 16 | if collection.index >= len(collection.List) { 17 | return INVALID_STRING_VAL, false 18 | } 19 | return collection.List[collection.index], true 20 | } 21 | 22 | func newSlice(s []string) *Collection { 23 | return &Collection{INVALID_INT_VAL, s} 24 | } 25 | 26 | func main() { 27 | var intCollection IntIterator 28 | intCollection = newSlice([]string{"CRV", "IS250", "Blazer"}) 29 | value, ok := intCollection.Next() 30 | for ok { 31 | println(value) 32 | value, ok = intCollection.Next() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Chapter02/misc/ips.log: -------------------------------------------------------------------------------- 1 | Sun Feb 12 20:27:32 EST 2017 74.125.196.101 2 | Sun Feb 12 20:27:33 EST 2017 98.139.183.24 3 | Sun Feb 12 20:27:34 EST 2017 151.101.0.73 4 | Sun Feb 12 20:27:35 EST 2017 98.139.183.24 5 | Sun Feb 12 20:27:36 EST 2017 151.101.0.73 6 | Sun Feb 12 20:27:37 EST 2017 74.125.196.101 7 | Sun Feb 12 20:27:38 EST 2017 98.139.183.24 8 | Sun Feb 12 20:27:39 EST 2017 151.101.0.73 9 | Sun Feb 12 20:27:40 EST 2017 98.139.183.24 10 | Sun Feb 12 20:27:41 EST 2017 151.101.0.73 11 | Sun Feb 12 20:27:42 EST 2017 151.101.0.73 12 | Sun Feb 12 20:27:43 EST 2017 151.101.0.73 13 | -------------------------------------------------------------------------------- /Chapter03/hof/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: 3 | - package: github.com/julienschmidt/httprouter 4 | - package: github.com/l3x/fp-in-go 5 | subpackages: 6 | - chapter9/01_hof 7 | ignore: 8 | - ./hof 9 | -------------------------------------------------------------------------------- /Chapter03/hof/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (if you set USES_TOML_CONFIG_YN=yes) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter03/hof/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "hof" 5 | "log" 6 | "os" 7 | "github.com/julienschmidt/httprouter" 8 | "net/http" 9 | ) 10 | 11 | func init() { 12 | log.SetFlags(log.Lshortfile | log.Ldate) 13 | log.SetOutput(os.Stdout) 14 | } 15 | 16 | 17 | func main() { 18 | 19 | if os.Getenv("RUN_HTTP_SERVER") == "TRUE" { 20 | router := httprouter.New() 21 | router.GET("/cars", CarsIndexHandler) 22 | router.GET("/cars/:id", CarHandler) 23 | log.Println("Listening on port 8000") 24 | log.Fatal(http.ListenAndServe(":8000", router)) 25 | } else { 26 | 27 | cars := LoadCars() 28 | 29 | //PrintCars("ByMake - Honda", cars.Filter(ByMake("Honda"))) 30 | // 31 | //PrintCars("Numeric", cars.Filter(ByHasNumber())) 32 | // 33 | //PrintCars("Foreign, Numeric, Toyota", 34 | // cars.Filter(ByForeign()). 35 | // Filter(ByHasNumber()). 36 | // Filter(ByMake("Toyota"))) 37 | // 38 | //PrintCars("Domestic, Numeric, GM", 39 | // cars.Filter(ByDomestic()). 40 | // Filter(ByHasNumber()). 41 | // Filter(ByMake("GM"))) 42 | // 43 | //moreCars := LoadMoreCars() 44 | // 45 | //PrintCars("More Cars, Domestic, Numeric, GM", 46 | // cars.AddCars(moreCars). 47 | // Filter(ByDomestic()). 48 | // Filter(ByHasNumber()). 49 | // Filter(ByMake("GM"))) 50 | // 51 | //PrintCars("More Cars, Domestic, Numeric, Ford", 52 | // cars.AddCars(moreCars). 53 | // Filter(ByDomestic()). 54 | // Filter(ByHasNumber()). 55 | // Filter(ByMake("Ford"))) 56 | // 57 | // 58 | //PrintCars("Numeric, Foreign, Map Upgraded", 59 | // cars.Filter(ByHasNumber()). 60 | // Filter(ByForeign()). 61 | // Map(Upgrade())) 62 | // 63 | //PrintCars("Filter Honda, Reduce JSON", 64 | // cars.Filter(ByMake("Honda")). 65 | // Reduce(JsonReducer(cars), Collection{})) 66 | // 67 | //PrintCars("Reduce, Honda, JSON", 68 | // cars.Reduce(MakeReducer("Honda", cars), Collection{}). 69 | // Reduce(JsonReducer(cars), Collection{})) 70 | // 71 | //PrintCars2("Reduce - Lexus", 72 | // cars.Filter(ByMake("Lexus")). 73 | // Reduce2(CarTypeReducer(cars), []CarType{})) 74 | // 75 | //PrintCars("ByModel - Accord up/downgraded", 76 | // cars.Filter(ByModel("Accord")). 77 | // Map(Upgrade()). 78 | // Map(Downgrade())) 79 | 80 | PrintCars("GenerateCars(1, 3)", 81 | cars.GenerateCars(1, 3)) 82 | 83 | PrintCars("GenerateCars(1, 14), Domestic, Numeric, JSON", 84 | cars.GenerateCars(1, 14). 85 | Filter(ByDomestic()). 86 | Map(Upgrade()). 87 | Filter(ByHasNumber()). 88 | Reduce(JsonReducer(cars), Collection{})) 89 | 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /Chapter03/hof/src/hof/cars.csv: -------------------------------------------------------------------------------- 1 | "Honda Accord" 2 | "Honda Accord ES2" 3 | "Lexus IS250" 4 | "Honda CR-V" 5 | "Lexus SC 430" 6 | "Ford F-150" 7 | "Toyota 86" 8 | "Toyota Highlander" 9 | "Toyota RAV4" 10 | "GM Hummer H2" 11 | "GM Hummer H3" -------------------------------------------------------------------------------- /Chapter03/hof/src/hof/cars.go: -------------------------------------------------------------------------------- 1 | package hof 2 | 3 | import ( 4 | "fmt" 5 | s "strings" 6 | "regexp" 7 | "log" 8 | "encoding/json" 9 | ) 10 | 11 | var CarsDB = initCarsDB() 12 | 13 | func initCarsDB() []IndexedCar { 14 | var indexedCars []IndexedCar 15 | for i, car := range LoadCars() { 16 | indexedCars = append(indexedCars, IndexedCar{i, car}) 17 | } 18 | lenCars := len(indexedCars) 19 | for i, car := range LoadMoreCars() { 20 | indexedCars = append(indexedCars, IndexedCar{i + lenCars, car}) 21 | } 22 | return indexedCars 23 | } 24 | 25 | func LoadCars() Collection { 26 | return CsvToStruct("cars.csv") 27 | } 28 | 29 | func LoadMoreCars() Collection { 30 | return CsvToStruct("more_cars.csv") 31 | } 32 | 33 | 34 | func (cars Collection) AddCars(carsToAdd Collection) Collection { 35 | return append(cars, carsToAdd...) 36 | } 37 | 38 | 39 | // Reduce collection based on the function 40 | func (cars Collection) Reduce(fn ReducerFunc, accumulator Collection) Collection { 41 | var result = accumulator 42 | for _, car := range cars { 43 | result = append(fn(car, result)) 44 | } 45 | return result 46 | } 47 | func (cars Collection) Reduce2(fn ReducerFunc2, accumulator CarCollection) CarCollection { 48 | var result = accumulator 49 | for _, car := range cars { 50 | result = append(fn(car, result)) 51 | } 52 | return result 53 | } 54 | 55 | func JsonReducer(cars Collection) ReducerFunc { 56 | return func(car string, cars Collection) Collection { 57 | JSON := fmt.Sprintf("{\"car\": {\"make\": \"%s\", \"model\": \"%s\"}}", GetMake(car), GetModel(car)) 58 | cars = append(cars, JSON) 59 | return cars 60 | } 61 | } 62 | 63 | func CarTypeReducer(cars Collection) ReducerFunc2 { 64 | 65 | return func(car string, cars CarCollection) CarCollection { 66 | JSON := fmt.Sprintf("{\"make\": \"%s\", \"model\": \"%s\"}", GetMake(car), GetModel(car)) 67 | var c CarType 68 | err := json.Unmarshal([]byte(JSON), &c) 69 | if err != nil { 70 | log.Fatal("ERROR:", err) 71 | } 72 | cars = append(cars, c) 73 | return cars 74 | } 75 | } 76 | 77 | 78 | func MakeReducer(make string, cars Collection) ReducerFunc { 79 | return func(car string, cars Collection) Collection { 80 | if s.Contains(car, make) { 81 | cars = append(cars, car) 82 | } 83 | return cars 84 | } 85 | } 86 | 87 | 88 | func (cars Collection) Filter(fn FilterFunc) Collection { 89 | filteredCars := make(Collection, 0) 90 | for _, car := range cars { 91 | if fn(car) { 92 | filteredCars = append(filteredCars, car) 93 | } 94 | } 95 | return filteredCars 96 | } 97 | 98 | func ByMake(make string) FilterFunc { 99 | return func(car string) bool { 100 | return s.Contains(car, make) 101 | } 102 | } 103 | 104 | func ByForeign() FilterFunc { 105 | return func(car string) bool { 106 | return !isDomestic(car) 107 | } 108 | } 109 | 110 | func isDomestic(car string) bool { 111 | return s.Contains(car, "Ford") || s.Contains(car, "GM") || s.Contains(car, "Chrysler") 112 | } 113 | 114 | func ByDomestic() FilterFunc { 115 | return func(car string) bool { 116 | return isDomestic(car) 117 | } 118 | } 119 | 120 | func ByHasNumber() FilterFunc { 121 | return func(car string) bool { 122 | match, _ := regexp.MatchString(".+[0-9].*", car) 123 | return match 124 | } 125 | } 126 | 127 | func ByModel(model string) FilterFunc { 128 | return func(car string) bool { 129 | return s.Contains(car, model) 130 | } 131 | } 132 | 133 | func (cars Collection) Map(fn MapFunc) Collection { 134 | mappedCars := make(Collection, 0, len(cars)) 135 | for _, car := range cars { 136 | mappedCars = append(mappedCars, fn(car)) 137 | } 138 | return mappedCars 139 | } 140 | 141 | func Upgrade() MapFunc { 142 | return func(car string) string { 143 | return fmt.Sprintf("%s %s", car, UpgradeLabel(car)) 144 | } 145 | } 146 | 147 | func Downgrade() MapFunc { 148 | return func(car string) string { 149 | return car[:len(car)-2] 150 | } 151 | } 152 | 153 | 154 | func UpgradeLabel(car string) string { 155 | return map[string]string{ 156 | "Honda": "LX", 157 | "Lexus": "LS", 158 | "Toyota": "EV", 159 | "Ford": "XL", 160 | "GM": "X", 161 | }[GetMake(car)] 162 | } 163 | 164 | 165 | -------------------------------------------------------------------------------- /Chapter03/hof/src/hof/generator.go: -------------------------------------------------------------------------------- 1 | package hof 2 | 3 | import ( 4 | "sync" 5 | "log" 6 | ) 7 | 8 | 9 | func carGenerator(iterator func(int) int, lower int, upper int) func() (int, bool) { 10 | return func() (int, bool) { 11 | lower = iterator(lower) 12 | return lower, lower > upper 13 | } 14 | } 15 | 16 | func iterator(i int) int { 17 | i += 1 18 | return i 19 | } 20 | 21 | 22 | func (cars Collection) GenerateCars(start, limit int) Collection { 23 | 24 | // Create an unbuffered channel to receive match carChannel to display. 25 | carChannel := make(chan *IndexedCar) 26 | 27 | // Setup a wait group so we can process all the feeds. 28 | var waitGroup sync.WaitGroup 29 | 30 | numCarsToGenerate := start + limit - 1 31 | generatedCars := Collection{} 32 | 33 | // Set the number of goroutines we need to wait for while 34 | // they process the individual feeds. 35 | waitGroup.Add(numCarsToGenerate) 36 | 37 | next := carGenerator(iterator, start -1, numCarsToGenerate) 38 | 39 | carIndex, done := next() 40 | for !done { 41 | // Launch the goroutine to perform the search. 42 | go func(carIndex int) { 43 | thisCar, err := GetThisCar(carIndex) 44 | //log.Printf("GetThisCar(%v): %v\n", carIndex, thisCar) 45 | if err != nil { 46 | panic(err) 47 | } 48 | carChannel <- thisCar 49 | generatedCars = append(generatedCars, thisCar.Car) 50 | waitGroup.Done() 51 | }(carIndex) 52 | 53 | carIndex, done = next() 54 | } 55 | 56 | // Launch a goroutine to monitor when all the work is done. 57 | go func() { 58 | // Wait for everything to be processed. 59 | waitGroup.Wait() 60 | 61 | // Close the channel to signal to the getCars 62 | // function that we can exit the program. 63 | close(carChannel) 64 | }() 65 | 66 | 67 | // Start displaying carChannel as they are available and 68 | // return after the final result is displayed. 69 | printCars(carChannel, start, limit) 70 | return generatedCars 71 | } 72 | 73 | 74 | 75 | // getCars writes results to the console window as they 76 | // are received by the individual goroutines. 77 | func printCars(indexedCars chan *IndexedCar, start, limit int) { 78 | log.Printf("\nGenerated Cars (%d to %d)\n%s\n", start, start + limit, DASHES) 79 | // The channel blocks until a car is written to the channel. 80 | // Once the channel is closed the for loop terminates. 81 | var cars Collection 82 | for car := range indexedCars { 83 | log.Printf("car: %s\n", car.Car) 84 | cars = append(cars, car.Car) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Chapter03/hof/src/hof/more_cars.csv: -------------------------------------------------------------------------------- 1 | "Dodge Charger" 2 | "Dodge 330" 3 | "Chrysler Pacifica" 4 | "GM Oldsmobile Cutlass Supreme" 5 | "GM Oldsmobile Delta 88" 6 | "GM Oldsmobile 442" 7 | -------------------------------------------------------------------------------- /Chapter03/hof/src/hof/restful.go: -------------------------------------------------------------------------------- 1 | package hof 2 | 3 | import ( 4 | "fmt" 5 | "github.com/julienschmidt/httprouter" 6 | "net/http" 7 | "encoding/json" 8 | "log" 9 | "strconv" 10 | ) 11 | 12 | 13 | 14 | func CarsIndexHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 15 | response, err := getAllCarsJson(); 16 | if err != nil { 17 | panic(err) 18 | } 19 | w.Header().Set("Content-Type", "application/json") 20 | fmt.Fprintf(w , string(response)) 21 | } 22 | 23 | func CarHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 24 | carIndex, err := strconv.Atoi(p[0].Value) 25 | if err != nil { 26 | log.Fatal("CarHandler unable to find car (%v) by index\n", p[0].Value) 27 | } 28 | response, err := getThisCarJson(carIndex); 29 | if err != nil { 30 | panic(err) 31 | } 32 | w.Header().Set("Content-Type", "application/json") 33 | fmt.Fprintf(w , string(response)) 34 | } 35 | 36 | 37 | func getAllCarsJson() ([]byte, error) { 38 | return json.MarshalIndent(Payload{CarsDB}, "", " ") 39 | } 40 | 41 | func getThisCarJson(carIndex int) ([]byte, error) { 42 | return json.MarshalIndent(CarsDB[carIndex], "", " ") 43 | } 44 | 45 | // retrieve performs a HTTP Get request for the rss feed and decodes the results. 46 | func GetThisCar(carIndex int) (*IndexedCar, error) { 47 | 48 | thisCarJson, err := getThisCarJson(carIndex) 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | // Decode the json into our struct type. 54 | // We don't need to check for errors, the caller can do this. 55 | 56 | var thisIndexedCar IndexedCar 57 | if err := json.Unmarshal(thisCarJson, &thisIndexedCar); err != nil { 58 | panic(err) 59 | } 60 | //log.Printf("carIndex: %v\n", carIndex) 61 | //log.Printf("thisCarJson: %v\n", thisCarJson) 62 | //log.Printf("thisIndexedCar: %v\n", thisIndexedCar) 63 | return &thisIndexedCar, err 64 | } 65 | -------------------------------------------------------------------------------- /Chapter03/hof/src/hof/types.go: -------------------------------------------------------------------------------- 1 | package hof 2 | 3 | type ( 4 | FilterFunc func(string) bool 5 | MapFunc func(string) string 6 | Collection []string 7 | ReducerFunc func(string, Collection) Collection // 1st arg: list to construct, 2nd: thing we are iterating 8 | ReducerFunc2 func(string, CarCollection) CarCollection 9 | Car string 10 | CarType struct { 11 | Make string `json:"make"` 12 | Model string `json:"model"` 13 | } 14 | CarCollection []CarType 15 | IndexedCar struct { 16 | Index int `json:"index"` 17 | Car string` json:"car"` 18 | } 19 | Payload struct { 20 | IndexedCars []IndexedCar 21 | } 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /Chapter03/hof/src/hof/utils.go: -------------------------------------------------------------------------------- 1 | package hof 2 | 3 | import ( 4 | "path/filepath" 5 | "encoding/csv" 6 | "os" 7 | "log" 8 | "fmt" 9 | s "strings" 10 | ) 11 | 12 | const DASHES = "-----------------------" 13 | 14 | func PrintCars(title string, cars Collection) { 15 | log.Printf("\n%s\n%s\n", title, DASHES) 16 | for _, car := range cars { 17 | log.Printf("car: %v\n", car) 18 | } 19 | } 20 | 21 | func PrintCars2(title string, cars CarCollection) { 22 | log.Printf("\n%s\n%s\n", title, DASHES) 23 | for _, car := range cars { 24 | log.Printf("car: %v\n", car) 25 | } 26 | } 27 | 28 | func CsvToStruct(fileName string) Collection { 29 | pwd, err := filepath.Abs(filepath.Dir(os.Args[0])) 30 | pwd = "/Users/lex/clients/packt/dev/go/src/bitbucket.org/lsheehan/fp-in-go/chapter9/01_hof" 31 | if err != nil { 32 | log.Fatal(err) 33 | os.Exit(1) 34 | } 35 | csvfile, err := os.Open(fmt.Sprintf("%s/%s", pwd, fileName)) 36 | if err != nil { 37 | log.Fatal(err) 38 | os.Exit(1) 39 | } 40 | defer csvfile.Close() 41 | reader := csv.NewReader(csvfile) 42 | reader.FieldsPerRecord = -1 43 | rawCSVdata, err := reader.ReadAll() 44 | if err != nil { 45 | log.Println(err) 46 | os.Exit(1) 47 | } 48 | var cars Collection 49 | for _, car := range rawCSVdata { 50 | cars = append(cars, car[0]) 51 | } 52 | return cars 53 | } 54 | 55 | func GetMake(sentence string) string { 56 | ret := sentence 57 | posSpace := s.Index(sentence, " ") 58 | if posSpace >= 0 { 59 | ret = sentence[:(posSpace)] 60 | } 61 | return ret 62 | } 63 | 64 | func GetModel(sentence string) string { 65 | ret := sentence 66 | posSpace := s.Index(sentence, " ") 67 | if posSpace >= 0 { 68 | ret = sentence[posSpace:] 69 | } 70 | return ret 71 | } 72 | -------------------------------------------------------------------------------- /Chapter04/01_duck/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (if you set USES_TOML_CONFIG_YN=yes) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter04/01_duck/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "errors" 6 | "log" 7 | ) 8 | const DASHES = "----------------------" 9 | 10 | type Pond struct { 11 | BugSupply int 12 | StrokesRequired int 13 | } 14 | 15 | type StrokeBehavior interface { 16 | PaddleFoot(strokeSupply *int) 17 | } 18 | 19 | type EatBehavior interface { 20 | EatBug(strokeSupply *int) 21 | } 22 | 23 | type SurvivalBehaviors interface { 24 | StrokeBehavior 25 | EatBehavior 26 | } 27 | 28 | 29 | type Duck struct{} 30 | 31 | func (Duck) Stroke(s StrokeBehavior, strokeSupply *int, p Pond) (err error) { 32 | for i := 0; i < p.StrokesRequired; i++ { 33 | if *strokeSupply < p.StrokesRequired - i { 34 | err = errors.New("Our duck died!") 35 | } 36 | s.PaddleFoot(strokeSupply) 37 | } 38 | return err 39 | } 40 | 41 | func (Duck) Eat(e EatBehavior, strokeSupply *int, p Pond) { 42 | for i := 0; i < p.BugSupply; i++ { 43 | e.EatBug(strokeSupply) 44 | } 45 | } 46 | 47 | type Foot struct{} 48 | func (Foot) PaddleFoot(strokeSupply *int) { 49 | fmt.Println("- Foot, paddle!") 50 | *strokeSupply-- 51 | } 52 | 53 | type Bill struct{} 54 | func (Bill) EatBug(strokeSupply *int) { 55 | *strokeSupply++ 56 | fmt.Println("- Bill, eat a bug!") 57 | } 58 | 59 | func (d Duck) SwimAndEat(se SurvivalBehaviors, strokeSupply *int, ponds []Pond) { 60 | for i := range ponds { 61 | pond := &ponds[i] 62 | err := d.Stroke(se, strokeSupply, *pond) 63 | if err != nil { 64 | log.Fatal(err) // the duck died! 65 | } 66 | d.Eat(se, strokeSupply, *pond) 67 | } 68 | } 69 | 70 | type Capabilities struct { 71 | StrokeBehavior 72 | EatBehavior 73 | strokes int 74 | } 75 | 76 | func displayDuckStats(c Capabilities, ponds []Pond) { 77 | fmt.Printf("%s\n", DASHES) 78 | fmt.Printf("Ponds Processed:") 79 | for _, pond := range ponds { 80 | fmt.Printf("\n\t%+v", pond) 81 | } 82 | fmt.Printf("\nStrokes remaining: %+v\n", c.strokes) 83 | fmt.Printf("%s\n\n", DASHES) 84 | } 85 | 86 | 87 | func main() { 88 | var duck Duck 89 | capabilities := Capabilities{ 90 | StrokeBehavior: Foot{}, 91 | EatBehavior: Bill{}, 92 | strokes: 5, 93 | } 94 | 95 | ponds := []Pond{ 96 | {BugSupply: 1, StrokesRequired: 3}, 97 | {BugSupply: 1, StrokesRequired: 2}, 98 | } 99 | duck.SwimAndEat(&capabilities, &capabilities.strokes, ponds) 100 | displayDuckStats(capabilities, ponds) 101 | 102 | ponds = []Pond{ 103 | {BugSupply: 2, StrokesRequired: 3}, 104 | } 105 | duck.SwimAndEat(&capabilities, &capabilities.strokes, ponds) 106 | displayDuckStats(capabilities, ponds) 107 | } 108 | 109 | 110 | -------------------------------------------------------------------------------- /Chapter04/02_misc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | var languages map[string]string 6 | 7 | func init(){ 8 | languages= make(map[string]string) 9 | languages["js"] = "JavaScript" 10 | languages["rb"] = "Ruby" 11 | languages["go"] = "Golang" 12 | } 13 | func Get(key string) (string){ 14 | return languages[key] 15 | } 16 | func Add(key,value string){ 17 | languages[key]=value 18 | } 19 | func GetAll() (map[string]string){ 20 | return languages 21 | } 22 | 23 | func main() { 24 | fmt.Printf("languages: %v\n", languages) 25 | fmt.Printf("Get('Ruby'): %v\n", Get("rb")) 26 | fmt.Println("Add('Perl')") 27 | Add("pl", "Perl") 28 | fmt.Printf("GetAll(): %v\n", GetAll()) 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Chapter04/02_misc/misc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | 4 | 5 | //import "theirpkg" 6 | 7 | //func MyFunction(t *theirpkg.AType) 8 | // 9 | //func MyOtherFunction(i theirpkg.AnInterface) 10 | 11 | 12 | 13 | type errorBehavior interface { 14 | Retryable() bool 15 | } 16 | 17 | 18 | func IsRetryable(err error) bool { 19 | eb, ok := err.(errorBehavior) 20 | return ok && eb.Retryable() 21 | } 22 | 23 | 24 | //type writeFlusher interface { 25 | //     io.Writer 26 | //     http.Flusher 27 | //} 28 | 29 | 30 | 31 | 32 | 33 | func main() { 34 | 35 | } 36 | 37 | /* 38 | 39 | - Foot, paddle! 40 | - Foot, paddle! 41 | - Foot, paddle! 42 | - Bill, eat a bug! 43 | - Foot, paddle! 44 | - Foot, paddle! 45 | - Bill, eat a bug! 46 | ---------------------- 47 | Ponds Processed: 48 | {BugSupply:1 StrokesRequired:3} 49 | {BugSupply:1 StrokesRequired:2} 50 | Strokes remaining: 2 51 | ---------------------- 52 | 53 | 54 | 55 | - Foot, paddle! 56 | - Foot, paddle! 57 | - Foot, paddle! 58 | 59 | 60 | 2017/05/12 19:11:51 Our duck died! 61 | exit status 1 62 | 63 | 64 | 65 | 66 | */ -------------------------------------------------------------------------------- /Chapter05/01_reader_writer/glide.lock: -------------------------------------------------------------------------------- 1 | hash: 3441bf6ff6d443d7e65fcdfc4f1d1b67e506cee012ed3b4901bbe2aab9890aa9 2 | updated: 2017-10-04T13:52:32.765203122-04:00 3 | imports: [] 4 | testImports: [] 5 | -------------------------------------------------------------------------------- /Chapter05/01_reader_writer/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: [] 3 | ignore: 4 | 5 | -------------------------------------------------------------------------------- /Chapter05/01_reader_writer/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (if you set USES_TOML_CONFIG_YN=yes) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter05/01_reader_writer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | "os" 7 | ) 8 | 9 | type titlizeReader struct { 10 | src io.Reader 11 | } 12 | 13 | func NewTitlizeReader(source io.Reader) *titlizeReader { 14 | return &titlizeReader{source} 15 | } 16 | 17 | 18 | func (t *titlizeReader) Read(p []byte) (int, error) { 19 | count, err := t.src.Read(p) 20 | if err != nil { 21 | return count, err 22 | } 23 | for i := 0; i < len(p); i++ { 24 | if i == 0 { 25 | if (p[i] >= 't' && p[i] <= 'z') { 26 | p[i] = p[i] - 32 27 | } 28 | } else { 29 | if (p[i] >= 'A' && p[i] <= 'Z') { 30 | p[i] = p[i] + 32 31 | } 32 | } 33 | } 34 | return count, io.EOF 35 | } 36 | 37 | 38 | 39 | 40 | 41 | func main() { 42 | var r io.Reader 43 | r = strings.NewReader("this IS a tEsT") 44 | r = io.LimitReader(r, 12) 45 | r = NewTitlizeReader(r) 46 | 47 | var w io.Writer 48 | w = os.Stdout 49 | io.Copy(w, r) 50 | } 51 | -------------------------------------------------------------------------------- /Chapter05/02_decorator/glide.lock: -------------------------------------------------------------------------------- 1 | hash: 89b928605a61ad578b5d53db62cabc2c4abc2fcb2bf72bf8ef1e04e7073d19f2 2 | updated: 2017-10-04T13:53:43.593531394-04:00 3 | imports: 4 | - name: github.com/admobi/easy-metrics 5 | version: 5ed4e60cf060279052905ab7411b228ca17b0eaf 6 | testImports: [] 7 | -------------------------------------------------------------------------------- /Chapter05/02_decorator/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: 3 | - package: github.com/admobi/easy-metrics 4 | ignore: 5 | - ./decorator 6 | - ./easy_metrics 7 | -------------------------------------------------------------------------------- /Chapter05/02_decorator/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (if you set USES_TOML_CONFIG_YN=yes) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter05/02_decorator/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "os/signal" 13 | "time" 14 | "easy_metrics" 15 | . "decorator" 16 | ) 17 | 18 | const ( 19 | host = "127.0.0.1" 20 | protocol = "http://" 21 | ) 22 | var ( 23 | serverUrl string 24 | proxyUrl string 25 | ) 26 | 27 | func init() { 28 | serverPort := 3000 29 | proxyPort := 8080 30 | flag.IntVar(&serverPort, "serverPort", serverPort, "Server Port") 31 | flag.IntVar(&proxyPort, "proxyPort", proxyPort, "Server Port") 32 | flag.Parse() 33 | serverUrl = fmt.Sprintf("%s:%d", host, serverPort) 34 | proxyUrl = fmt.Sprintf("%s:%d", host, proxyPort) 35 | } 36 | 37 | func main() { 38 | InitLog("trace-log.txt", 39 | ioutil.Discard, os.Stdout, os.Stderr) 40 | Info.Printf("Metrics server listening on %s", serverUrl) 41 | go func() { 42 | log.Fatal(easy_metrics.Serve(serverUrl)) 43 | }() 44 | time.Sleep(1 * time.Second) 45 | 46 | req, err := http.NewRequest(http.MethodGet, protocol + serverUrl, nil) 47 | if err != nil { 48 | log.Fatalln(err) 49 | } 50 | 51 | Info.Printf("Proxy listening on %s", proxyUrl) 52 | proxyURL, _ := url.Parse(proxyUrl) 53 | tr := &http.Transport{ 54 | Proxy: http.ProxyURL(proxyURL), 55 | TLSClientConfig: &tls.Config{ 56 | InsecureSkipVerify: true, 57 | }, 58 | } 59 | tr.TLSNextProto = make(map[string]func(string, *tls.Conn) http.RoundTripper) 60 | proxyTimeoutClient := &http.Client{Transport: tr, Timeout: 1 * time.Second} 61 | 62 | client := Decorate(proxyTimeoutClient, 63 | Authorization("mysecretpassword"), 64 | LoadBalancing(RoundRobin(0, "web01:3000", "web02:3000", "web03:3000")), 65 | Logging(log.New(InfoHandler, "client: ", log.Ltime)), 66 | FaultTolerance(2, time.Second), 67 | ) 68 | 69 | job := &Job{ 70 | Client: client, 71 | Request: req, 72 | NumRequests: 10, 73 | IntervalSecs: 10, 74 | } 75 | 76 | start := time.Now() 77 | job.Run() 78 | Info.Printf("\n>> It took %s", time.Since(start)) 79 | 80 | Info.Printf("metrics") 81 | err = easy_metrics.DisplayResults(serverUrl) 82 | if err != nil { 83 | log.Fatalln(err) 84 | } 85 | 86 | Info.Printf("CTRL+C to exit") 87 | c := make(chan os.Signal, 1) 88 | signal.Notify(c, os.Interrupt) 89 | <-c 90 | } 91 | -------------------------------------------------------------------------------- /Chapter05/02_decorator/src/decorator/decorator.go: -------------------------------------------------------------------------------- 1 | package decorator 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "sync/atomic" 7 | "time" 8 | ) 9 | 10 | 11 | type Client interface { 12 | Do(*http.Request) (*http.Response, error) 13 | } 14 | 15 | type ClientFunc func(*http.Request) (*http.Response, error) 16 | 17 | func (f ClientFunc) Do(r *http.Request) (*http.Response, error) { 18 | return f(r) 19 | } 20 | 21 | var ratelimitDuration time.Duration 22 | 23 | func (f ClientFunc) SetRatelimit(duration time.Duration) (error) { 24 | ratelimitDuration = duration 25 | return nil 26 | } 27 | 28 | func (f ClientFunc) GetRatelimit() (time.Duration, error) { 29 | return ratelimitDuration, nil 30 | } 31 | 32 | type Decorator func(Client) Client 33 | 34 | func Decorate(c Client, ds ...Decorator) Client { 35 | decorated := c 36 | for _, decorate := range ds { 37 | decorated = decorate(decorated) 38 | } 39 | return decorated 40 | } 41 | 42 | 43 | func Authorization(token string) Decorator { 44 | return Header("Authorization", token) 45 | } 46 | 47 | func Header(name, value string) Decorator { 48 | return func(c Client) Client { 49 | return ClientFunc(func(r *http.Request)(*http.Response, error) { 50 | r.Header.Add(name, value) 51 | return c.Do(r) 52 | }) 53 | } 54 | } 55 | 56 | func Logging(l *log.Logger) Decorator { 57 | return func(c Client) Client { 58 | return ClientFunc(func(r *http.Request) (*http.Response, error ) { 59 | l.Printf("%s %s", r.Method, r.URL) 60 | return c.Do(r) 61 | }) 62 | } 63 | } 64 | 65 | type Director func(*http.Request) 66 | 67 | func LoadBalancing(dir Director) Decorator { 68 | return func(c Client) Client { 69 | return ClientFunc(func(r *http.Request)(*http.Response, error) { 70 | dir(r) 71 | return c.Do(r) 72 | }) 73 | } 74 | } 75 | 76 | func RoundRobin(robin int64, backends ...string) Director { 77 | return func(r *http.Request) { 78 | if len(backends) > 0 { 79 | r.URL.Host = backends[atomic.AddInt64(&robin, 1) % int64(len(backends))] 80 | } 81 | } 82 | } 83 | 84 | // FaultTolerance returns a Decorator that extends a Client with fault tolerance configured 85 | // with the given attempts and backoff duration 86 | func FaultTolerance(attempts int, backoff time.Duration) Decorator { 87 | return func(c Client) Client { 88 | return ClientFunc(func(r *http.Request) (*http.Response, error) { 89 | var res *http.Response 90 | var err error 91 | for i := 0; i <= attempts; i++ { 92 | if res, err = c.Do(r); err == nil { 93 | Info.Println("SUCCESS!") 94 | break 95 | } 96 | Debug.Println("backing off...") 97 | time.Sleep(backoff * time.Duration(i)) 98 | } 99 | if err != nil { Info.Println("FAILURE!") } 100 | return res, err 101 | }) 102 | } 103 | } 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /Chapter05/02_decorator/src/decorator/requestor.go: -------------------------------------------------------------------------------- 1 | package decorator 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "net/http" 7 | "os" 8 | "os/signal" 9 | "sync" 10 | "syscall" 11 | "time" 12 | ) 13 | 14 | type response struct { 15 | duration time.Duration 16 | err error 17 | } 18 | 19 | type Job struct { 20 | Client Client 21 | NumRequests int 22 | Request *http.Request 23 | IntervalSecs int 24 | responseChan chan *response 25 | } 26 | 27 | func (b *Job) displayProgress(stopChan chan struct{}) { 28 | var prevResponseCount int 29 | for { 30 | select { 31 | case <-time.Tick(time.Millisecond * 500): 32 | responseCount := len(b.responseChan) 33 | if prevResponseCount < responseCount { 34 | prevResponseCount = responseCount 35 | Debug.Printf("> %d requests done.", responseCount) 36 | } 37 | case <-stopChan: 38 | return 39 | } 40 | } 41 | } 42 | 43 | func (j *Job) Run() { 44 | j.responseChan = make(chan *response, j.NumRequests) 45 | stopChan := make(chan struct{}) 46 | go j.displayProgress(stopChan) 47 | 48 | interruptChan := make(chan os.Signal, 1) 49 | signal.Notify(interruptChan, os.Interrupt, syscall.SIGTERM) 50 | go func() { 51 | <-interruptChan 52 | stopChan <- struct{}{} 53 | close(j.responseChan) 54 | os.Exit(130) 55 | }() 56 | 57 | var wg sync.WaitGroup 58 | intervalSecs := time.Duration(j.IntervalSecs) 59 | requestsPerformed := 0 60 | for range time.Tick(intervalSecs * time.Second) { 61 | wg.Add(1) 62 | go func() { 63 | client := j.Client 64 | j.makeRequest(client) 65 | wg.Done() 66 | }() 67 | requestsPerformed++ 68 | if requestsPerformed >= j.NumRequests { 69 | break 70 | } 71 | } 72 | wg.Wait() 73 | stopChan <- struct{}{} 74 | Debug.Printf("All requests done.") 75 | close(j.responseChan) 76 | } 77 | 78 | func (j *Job) makeRequest(c Client) { 79 | Debug.Printf("makeRequest: ") 80 | start := time.Now() 81 | resp, err := c.Do(j.Request) 82 | if err == nil { 83 | io.Copy(ioutil.Discard, resp.Body) 84 | resp.Body.Close() 85 | } 86 | t := time.Now() 87 | finish := t.Sub(start) 88 | j.responseChan <- &response{ 89 | duration: finish, 90 | err: err, 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Chapter05/02_decorator/src/decorator/simple_log.go: -------------------------------------------------------------------------------- 1 | package decorator 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "os" 7 | ) 8 | 9 | var ( 10 | Debug *log.Logger 11 | Info *log.Logger 12 | Error *log.Logger 13 | InfoHandler io.Writer 14 | ) 15 | 16 | func InitLog( 17 | traceFileName string, 18 | debugHandler io.Writer, 19 | infoHandler io.Writer, 20 | errorHandler io.Writer, 21 | ) { 22 | if len(traceFileName) > 0 { 23 | _ = os.Remove(traceFileName) 24 | file, err := os.OpenFile(traceFileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) 25 | if err != nil { 26 | log.Fatalf("Failed to create log file: %s", traceFileName) 27 | } 28 | debugHandler = io.MultiWriter(file, debugHandler) 29 | infoHandler = io.MultiWriter(file, infoHandler) 30 | errorHandler = io.MultiWriter(file, errorHandler) 31 | } 32 | 33 | InfoHandler = infoHandler 34 | 35 | Debug = log.New(debugHandler, "DEBUG : ", 36 | log.Ldate|log.Ltime|log.Lshortfile) 37 | 38 | Info = log.New(infoHandler, "INFO : ", 39 | log.Ltime) 40 | 41 | Error = log.New(errorHandler, "ERROR : ", 42 | log.Ldate|log.Ltime|log.Lshortfile) 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Chapter05/02_decorator/src/easy_metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package easy_metrics 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "net/http" 7 | "time" 8 | "github.com/admobi/easy-metrics" 9 | "decorator" 10 | ) 11 | 12 | var ( 13 | avgResponseTime = metrics.NewGauge("avgResponseTime") 14 | requests = metrics.NewCounter("requests") 15 | responseTime = &timing{} 16 | ) 17 | 18 | func Serve(addr string) error { 19 | r, err := metrics.NewTrackRegistry("Stats", 100, time.Second, false) 20 | if err != nil { 21 | decorator.Error.Println(err) 22 | } 23 | 24 | err = r.AddMetrics(requests, avgResponseTime) 25 | if err != nil { 26 | decorator.Error.Println(err) 27 | } 28 | 29 | http.HandleFunc("/", handler) 30 | return http.ListenAndServe(addr, nil) 31 | } 32 | 33 | func handler(w http.ResponseWriter, r *http.Request) { 34 | begin := time.Now() 35 | 36 | work() 37 | 38 | responseTime.Observe(time.Since(begin)) 39 | requests.Inc() 40 | } 41 | 42 | func DisplayResults(addr string) error { 43 | decorator.Info.Printf("Go to http://%s/easy-metrics?show=Stats", addr) 44 | return nil 45 | } 46 | 47 | type timing struct { 48 | count int64 49 | sum time.Duration 50 | } 51 | 52 | func (t *timing) Observe(d time.Duration) { 53 | t.count++ 54 | t.sum += d 55 | avgResponseTime.Set(t.sum.Seconds() / float64(t.count)) 56 | } 57 | 58 | func (t timing) String() string { 59 | avg := time.Duration(t.sum.Nanoseconds() / t.count) 60 | return fmt.Sprintf("\"%v\"", avg) 61 | } 62 | 63 | 64 | func work() { 65 | randInt := rand.Intn(5000) 66 | decorator.Debug.Printf("- randInt: %v", randInt) 67 | 68 | workTime := time.Duration(randInt) * time.Millisecond 69 | time.Sleep(workTime) 70 | } 71 | -------------------------------------------------------------------------------- /Chapter05/misc/logs/console-log-book.txt: -------------------------------------------------------------------------------- 1 | ^C ~/clients/packt/dev/go/src/bitbucket.org/lsheehan/fp-in-go/chapter5 $ go run main.go 2 | INFO : 13:02:30 Metrics server listening on 127.0.0.1:3000 3 | INFO : 13:02:31 Proxy listening on 127.0.0.1:8080 4 | client: 13:02:33 GET http://127.0.0.1:3000 5 | client: 13:02:34 GET http://web02:3000 6 | client: 13:02:35 GET http://web03:3000 7 | client: 13:02:36 GET http://web01:3000 8 | client: 13:02:36 GET http://web02:3000 9 | INFO : 13:02:36 SUCCESS! 10 | ------- 11 | 12 | client: 13:02:37 GET http://web03:3000 13 | INFO : 13:02:38 SUCCESS! 14 | ------- 15 | 16 | client: 13:02:38 GET http://web01:3000 17 | client: 13:02:39 GET http://web02:3000 18 | client: 13:02:40 GET http://web03:3000 19 | client: 13:02:41 GET http://web01:3000 20 | INFO : 13:02:41 FAILURE! 21 | ------- 22 | 23 | INFO : 13:02:42 SUCCESS! 24 | ------- 25 | 26 | client: 13:02:42 GET http://web02:3000 27 | client: 13:02:43 GET http://web03:3000 28 | INFO : 13:02:44 SUCCESS! 29 | ------- 30 | 31 | client: 13:02:45 GET http://web01:3000 32 | INFO : 13:02:45 FAILURE! 33 | ------- 34 | 35 | INFO : 13:02:45 SUCCESS! 36 | ------- 37 | 38 | client: 13:02:47 GET http://web02:3000 39 | INFO : 13:02:47 SUCCESS! 40 | ------- 41 | 42 | client: 13:02:49 GET http://web03:3000 43 | client: 13:02:50 GET http://web01:3000 44 | client: 13:02:51 GET http://web02:3000 45 | client: 13:02:52 GET http://web03:3000 46 | client: 13:02:52 GET http://web01:3000 47 | client: 13:02:54 GET http://web02:3000 48 | INFO : 13:02:55 FAILURE! 49 | ------- 50 | 51 | INFO : 13:02:57 FAILURE! 52 | ------- 53 | 54 | INFO : 13:02:57 55 | >> It took 26.005951349s 56 | INFO : 13:02:57 metrics 57 | INFO : 13:02:57 Go to http://127.0.0.1:3000/easy-metrics?show=Stats 58 | INFO : 13:02:57 CTRL+C to exit -------------------------------------------------------------------------------- /Chapter05/misc/logs/results.log: -------------------------------------------------------------------------------- 1 | INFO : 17:03:13 Metrics server listening on%!(EXTRA string=127.0.0.1:3000) 2 | INFO : 17:03:14 Proxy listening on%!(EXTRA string=127.0.0.1:3000) 3 | DEBUG : 2017/05/16 17:03:16 requester.go:114: makeRequest: 4 | client: 17:03:16 GET http://127.0.0.1:3000 5 | client: 17:03:17 GET http://web02:3000 6 | DEBUG : 2017/05/16 17:03:18 requester.go:114: makeRequest: 7 | client: 17:03:18 GET http://web03:3000 8 | client: 17:03:19 GET http://web01:3000 9 | client: 17:03:19 GET http://web02:3000 10 | INFO : 17:03:19 SUCCESS! 11 | ------- 12 | 13 | DEBUG : 2017/05/16 17:03:20 requester.go:65: > 1 requests done. 14 | DEBUG : 2017/05/16 17:03:20 requester.go:114: makeRequest: 15 | client: 17:03:20 GET http://web03:3000 16 | INFO : 17:03:21 SUCCESS! 17 | ------- 18 | 19 | DEBUG : 2017/05/16 17:03:21 requester.go:65: > 2 requests done. 20 | client: 17:03:21 GET http://web01:3000 21 | DEBUG : 2017/05/16 17:03:22 requester.go:114: makeRequest: 22 | client: 17:03:22 GET http://web02:3000 23 | client: 17:03:23 GET http://web03:3000 24 | DEBUG : 2017/05/16 17:03:24 requester.go:114: makeRequest: 25 | client: 17:03:24 GET http://web01:3000 26 | INFO : 17:03:24 FAULURE! 27 | ------- 28 | 29 | DEBUG : 2017/05/16 17:03:24 requester.go:65: > 3 requests done. 30 | INFO : 17:03:25 SUCCESS! 31 | ------- 32 | 33 | DEBUG : 2017/05/16 17:03:25 requester.go:65: > 4 requests done. 34 | client: 17:03:25 GET http://web02:3000 35 | DEBUG : 2017/05/16 17:03:26 requester.go:114: makeRequest: 36 | client: 17:03:26 GET http://web03:3000 37 | INFO : 17:03:27 SUCCESS! 38 | ------- 39 | 40 | DEBUG : 2017/05/16 17:03:27 requester.go:65: > 5 requests done. 41 | DEBUG : 2017/05/16 17:03:28 requester.go:114: makeRequest: 42 | client: 17:03:28 GET http://web01:3000 43 | INFO : 17:03:28 FAULURE! 44 | ------- 45 | 46 | DEBUG : 2017/05/16 17:03:28 requester.go:65: > 6 requests done. 47 | INFO : 17:03:28 SUCCESS! 48 | ------- 49 | 50 | DEBUG : 2017/05/16 17:03:29 requester.go:65: > 7 requests done. 51 | DEBUG : 2017/05/16 17:03:30 requester.go:114: makeRequest: 52 | client: 17:03:30 GET http://web02:3000 53 | INFO : 17:03:30 SUCCESS! 54 | ------- 55 | 56 | DEBUG : 2017/05/16 17:03:31 requester.go:65: > 8 requests done. 57 | DEBUG : 2017/05/16 17:03:32 requester.go:114: makeRequest: 58 | client: 17:03:32 GET http://web03:3000 59 | client: 17:03:33 GET http://web01:3000 60 | DEBUG : 2017/05/16 17:03:34 requester.go:114: makeRequest: 61 | client: 17:03:34 GET http://web02:3000 62 | client: 17:03:35 GET http://web03:3000 63 | client: 17:03:35 GET http://web01:3000 64 | client: 17:03:37 GET http://web02:3000 65 | INFO : 17:03:38 FAULURE! 66 | ------- 67 | 68 | DEBUG : 2017/05/16 17:03:38 requester.go:65: > 9 requests done. 69 | INFO : 17:03:40 FAULURE! 70 | ------- 71 | 72 | DEBUG : 2017/05/16 17:03:40 requester.go:108: All requests done. 73 | INFO : 17:03:40 74 | >> It took 26.004486783s 75 | INFO : 17:03:40 metrics 76 | INFO : 17:03:40 Go to http://127.0.0.1:3000/easy-metrics?show=Stats 77 | INFO : 17:03:40 CTRL+C to exit 78 | -------------------------------------------------------------------------------- /Chapter05/misc/misc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "time" 7 | ) 8 | 9 | type Hash interface { 10 | io.Writer 11 | Sum(b []byte) []byte 12 | Reset() 13 | Size() int 14 | BlockSize() int 15 | } 16 | 17 | //func ReadAll(r io.Reader) ([]byte, error) 18 | // 19 | //func LoggingReader(r io.Reader) io.Reader 20 | //func LimitingReader(r io.Reader, n int64) io.Reader 21 | //func ErrorInjector(r io.Reader) io.Reader 22 | 23 | // The File interface is implemented by os.File. App specific 24 | // implementations may add concurrency, caching, stats, fuzzing, etc. 25 | type File interface { 26 | io.ReaderAt 27 | io.WriterAt 28 | io.Closer 29 | Stat() (os.FileInfo, error) 30 | Sync() error 31 | Truncate(size int64) error 32 | } 33 | 34 | type File interface { 35 | io.Reader 36 | io.ReaderAt 37 | io.Seeker 38 | io.Closer 39 | } 40 | 41 | type File interface { 42 | io.ReadSeeker 43 | io.Closer 44 | Name() string 45 | } 46 | // File is an interface to access the file part of a multipart message. 47 | // Its contents may be either stored in memory or on disk. 48 | // If stored on disk, the File's underlying concrete type will be an *os.File. 49 | type File interface { 50 | io.Reader 51 | io.ReaderAt 52 | io.Seeker 53 | io.Closer 54 | } 55 | 56 | 57 | type Reader interface { 58 | Read(p []byte) (n int, err error) 59 | } 60 | 61 | type Writer interface { 62 | Write(p []byte) (n int, err error) 63 | } 64 | 65 | /* 66 | Pick a Product Type: 67 | (1) Appliance 68 | (2) Book 69 | (3) Clothing 70 | 3 71 | Pick a Clothing Type: 72 | (1) Men 73 | (2) Women 74 | (3) Children 75 | 2 76 | 77 | 78 | 79 | $ go run main.go --help 80 | 81 | Usage of main: 82 | 83 | -proxyPort int 84 | 85 | Server Port (default 8080) 86 | 87 | -serverPort int 88 | 89 | Server Port (default 3000) 90 | 91 | 92 | 93 | INFO : 13:46:19 Metrics server listening on 127.0.0.1:3000 94 | INFO : 13:46:20 Proxy listening on 127.0.0.1:8080 95 | DEBUG : 2017/05/17 13:46:30 requester.go:114: makeRequest: 96 | client: 13:46:30 GET http://127.0.0.1:3000 97 | DEBUG : 2017/05/17 13:46:30 metrics.go:66: - randInt: 3081 98 | DEBUG : 2017/05/17 13:46:31 decorator.go:107: backing off... 99 | client: 13:46:31 GET http://web02:3000 100 | DEBUG : 2017/05/17 13:46:31 metrics.go:66: - randInt: 2887 101 | DEBUG : 2017/05/17 13:46:32 decorator.go:107: backing off... 102 | client: 13:46:33 GET http://web03:3000 103 | DEBUG : 2017/05/17 13:46:33 metrics.go:66: - randInt: 1847 104 | DEBUG : 2017/05/17 13:46:34 decorator.go:107: backing off... 105 | INFO : 13:46:36 FAILURE! 106 | 107 | 108 | DEBUG : 2017/05/17 13:47:30 requester.go:114: makeRequest: 109 | client: 13:47:30 GET http://web03:3000 110 | DEBUG : 2017/05/17 13:47:30 metrics.go:66: - randInt: 1445 111 | DEBUG : 2017/05/17 13:47:31 decorator.go:107: backing off... 112 | client: 13:47:31 GET http://web01:3000 113 | DEBUG : 2017/05/17 13:47:31 metrics.go:66: - randInt: 3237 114 | DEBUG : 2017/05/17 13:47:32 decorator.go:107: backing off... 115 | client: 13:47:33 GET http://web02:3000 116 | DEBUG : 2017/05/17 13:47:33 metrics.go:66: - randInt: 4106 117 | DEBUG : 2017/05/17 13:47:34 decorator.go:107: backing off... 118 | INFO : 13:47:36 FAILURE! 119 | DEBUG : 2017/05/17 13:47:36 requester.go:65: > 7 requests done. 120 | DEBUG : 2017/05/17 13:47:40 requester.go:114: makeRequest: 121 | client: 13:47:40 GET http://web03:3000 122 | DEBUG : 2017/05/17 13:47:40 metrics.go:66: - randInt: 495 123 | INFO : 13:47:41 SUCCESS! 124 | DEBUG : 2017/05/17 13:47:41 requester.go:65: > 8 requests done. 125 | 126 | 127 | */ 128 | 129 | func main() { 130 | 131 | } 132 | -------------------------------------------------------------------------------- /Chapter06/01_dependency-rule-good/glide.lock: -------------------------------------------------------------------------------- 1 | hash: 08a9e1dbeba0a2ab1a80f08116c1644d398a346e60386239774342f3f8f5ba12 2 | updated: 2017-10-04T12:35:37.473857969-04:00 3 | imports: [] 4 | testImports: [] 5 | -------------------------------------------------------------------------------- /Chapter06/01_dependency-rule-good/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: [] 3 | ignore: 4 | - ./packagea 5 | - ./packageb 6 | -------------------------------------------------------------------------------- /Chapter06/01_dependency-rule-good/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (if you set USES_TOML_CONFIG_YN=yes) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter06/01_dependency-rule-good/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | a "packagea" 5 | ) 6 | 7 | func main() { 8 | a.Atask() 9 | } 10 | 11 | -------------------------------------------------------------------------------- /Chapter06/01_dependency-rule-good/src/packagea/featurea.go: -------------------------------------------------------------------------------- 1 | package packagea 2 | 3 | import b "packageb" 4 | 5 | func Atask() { 6 | println("A") 7 | b.Btask() 8 | } 9 | 10 | -------------------------------------------------------------------------------- /Chapter06/01_dependency-rule-good/src/packageb/featureb.go: -------------------------------------------------------------------------------- 1 | package packageb 2 | 3 | func Btask() { 4 | println("B") 5 | } 6 | -------------------------------------------------------------------------------- /Chapter06/02_circulardep/glide.lock: -------------------------------------------------------------------------------- 1 | hash: 08a9e1dbeba0a2ab1a80f08116c1644d398a346e60386239774342f3f8f5ba12 2 | updated: 2017-10-04T12:40:56.537767121-04:00 3 | imports: [] 4 | testImports: [] 5 | -------------------------------------------------------------------------------- /Chapter06/02_circulardep/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: [] 3 | ignore: 4 | - ./packagea 5 | - ./packageb 6 | -------------------------------------------------------------------------------- /Chapter06/02_circulardep/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (it will create one if you don't have one) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter06/02_circulardep/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import a "packagea" 4 | 5 | func main() { 6 | a.Atask() 7 | } 8 | -------------------------------------------------------------------------------- /Chapter06/02_circulardep/src/packagea/featurea.go: -------------------------------------------------------------------------------- 1 | package packagea 2 | 3 | import b "packageb" 4 | 5 | func Atask() { 6 | println("A") 7 | b.Btask() 8 | } 9 | 10 | -------------------------------------------------------------------------------- /Chapter06/02_circulardep/src/packageb/featureb.go: -------------------------------------------------------------------------------- 1 | package packageb 2 | 3 | import a "packagea" 4 | 5 | func Btask() { 6 | println("B") 7 | a.Atask() 8 | } 9 | -------------------------------------------------------------------------------- /Chapter06/03_observer/glide.lock: -------------------------------------------------------------------------------- 1 | hash: cc05a88114b70fd295b8dfdfaf5fe66a22548aca09288010eeeb5f7544f3b781 2 | updated: 2017-10-04T12:42:18.328773426-04:00 3 | imports: [] 4 | testImports: [] 5 | -------------------------------------------------------------------------------- /Chapter06/03_observer/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: [] 3 | ignore: 4 | - ./observer 5 | -------------------------------------------------------------------------------- /Chapter06/03_observer/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (it will create one if you don't have one) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter06/03_observer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "observer" 5 | ) 6 | 7 | func main() { 8 | 9 | subject := Subject{} 10 | oa := Observable{Name: "A"} 11 | ob := Observable{Name: "B"} 12 | subject.AddObserver(&Observer{}) 13 | subject.NotifyObservers(oa, ob) 14 | 15 | oc := Observable{Name: "C"} 16 | subject.NotifyObservers(oa, ob, oc) 17 | 18 | subject.DeleteObserver(&Observer{}) 19 | subject.NotifyObservers(oa, ob, oc) 20 | 21 | od := Observable{Name: "D"} 22 | subject.NotifyObservers(oa, ob, oc, od) 23 | } -------------------------------------------------------------------------------- /Chapter06/03_observer/src/observer/observer.go: -------------------------------------------------------------------------------- 1 | package observer 2 | 3 | type Observable struct { 4 | Name string 5 | } 6 | 7 | type Observer struct { 8 | } 9 | 10 | func (ob *Observer) Notify(o *Observable) { 11 | println(o.Name) 12 | } 13 | 14 | type Callback interface { 15 | Notify(o *Observable) 16 | } 17 | 18 | -------------------------------------------------------------------------------- /Chapter06/03_observer/src/observer/subject.go: -------------------------------------------------------------------------------- 1 | package observer 2 | 3 | type Subject struct { 4 | callbacks []Callback 5 | } 6 | 7 | func (o *Subject) AddObserver(c Callback) { 8 | o.callbacks = append(o.callbacks, c) 9 | } 10 | func (o *Subject) DeleteObserver(c Callback) { 11 | o.callbacks = append(o.callbacks, c) 12 | 13 | newCallbacks := []Callback{} 14 | for _, cb := range o.callbacks { 15 | if cb != c { 16 | newCallbacks = append(newCallbacks, cb) 17 | } 18 | } 19 | o.callbacks = newCallbacks 20 | } 21 | 22 | func (o *Subject) NotifyObservers(oes ...Observable) { 23 | for _, oe := range oes { 24 | for _, c := range o.callbacks { 25 | c.Notify(&oe) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Chapter06/04_onion/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | pkg/ 3 | downloads/ 4 | onion 5 | keys/ 6 | trace*-log.txt 7 | src/lex/ 8 | vendors/ 9 | glide.* 10 | -------------------------------------------------------------------------------- /Chapter06/04_onion/activate: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Source me! 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | export GOPATH=$DIR:$GOPATH 5 | echo "GOPATH: $GOPATH" 6 | APP_NAME=$(basename $(pwd)) 7 | GOVERSION=$(go version) 8 | echo "go version: $GOVERSION" 9 | if [ -z $GOVERSION ] || [ "$GOVERSION" < "go1.8.3" ]; then 10 | echo "Go version 1.8.3 or above should be installed" 11 | exit 2 12 | fi 13 | if [ -e ./config.toml ]; then 14 | echo "You are missing the config.toml configuration file" 15 | exit 2 16 | fi 17 | 18 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 19 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 20 | alias glide-install='rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide' 21 | alias go-run="go install && $APP_NAME -config ./config.toml" 22 | 23 | echo You should only need to run this init script once. 24 | echo Run glide-install after updating dependencies, i.e., adding a new import statement. 25 | echo After running glide-install, run go-run to build and run your app. 26 | -------------------------------------------------------------------------------- /Chapter06/04_onion/config.toml: -------------------------------------------------------------------------------- 1 | #@IgnoreInspection BashAddShebang 2 | 3 | # Runtime environment. Determines whether to run semver scripts or expect env vars 4 | app_env = "development" 5 | 6 | # Project root directory (used for referencing scripts and API server) 7 | project_root_dir = "/Users/lex/clients/packt/dev/fp-go/2-design-patterns/ch06-onion-arch/04_onion" 8 | 9 | # Where files are downloaded 10 | download_dir = "/Users/lex/clients/packt/dev/fp-go/2-design-patterns/ch06-onion-arch/04_onion/downloads" 11 | 12 | # Parent folders for the log files on Google Cloud Storage 13 | gcp_source_dir = "source-events" 14 | 15 | # Source key file for Google Cloud Platform 16 | gcp_source_key_file = "/Users/lex/clients/packt/dev/fp-go/2-design-patterns/ch06-onion-arch/04_onion/keys/google-cloud-storage/source/onion-source-key.json" 17 | 18 | # Source GCP project id for 19 | gcp_source_project_id = "rdbx-168418" 20 | 21 | # Parent folders for the log files on Google Cloud Storage 22 | gcp_sink_dir = "sink-events" 23 | 24 | # Sink key file for Google Cloud Platform 25 | gcp_sink_key_file = "/Users/lex/clients/packt/dev/fp-go/2-design-patterns/ch06-onion-arch/04_onion/keys/google-cloud-storage/sink/onion-sink-key.json" 26 | 27 | # GCP sink project id for 28 | gcp_sink_project_id = "rdbx-168418" 29 | 30 | # Cloud bucket where the log files to be processed 31 | source_bucket_name = "lexttc3-my-backup-bucket" 32 | 33 | # Cloud bucket where they are to be copied after processing 34 | sink_bucket_name = "lexttc3-my-backup-bucket" 35 | 36 | # How often to pull logs from Google Cloud bucket 37 | fetch_interval = "60s" 38 | 39 | # The port the API will listen on 40 | api_port = "8080" 41 | 42 | # Enable or disable logging of utils/TimeTrack() (For benchmarking/debugging) 43 | log_timetrack = true 44 | 45 | # Whether to display full stack traces in log files 46 | log_full_stack_traces = false 47 | 48 | # Whether to log debug output to the log (set to true for debug purposes) 49 | log_debug_info = true 50 | 51 | # Whether to log debug output to the log when running tests (set to true for debug purposes) 52 | log_debug_info_for_tests = false 53 | 54 | # Run web service tests only to determine if the endpoints are active 55 | test_return_code_only = false 56 | -------------------------------------------------------------------------------- /Chapter06/04_onion/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (it will create one if you don't have one) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=yes 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter06/04_onion/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "utils" 5 | . "interfaces" 6 | "os" 7 | "io/ioutil" 8 | "infrastructure" 9 | "github.com/pkg/errors" 10 | "net/http" 11 | ) 12 | 13 | const defaultFileName = "eventset1.jsonl" 14 | var fileName string 15 | var wsh WebserviceHandler 16 | 17 | func init() { 18 | GetOptions() 19 | if Config.LogDebugInfo { 20 | InitLog("trace-debug-log.txt", os.Stdout, os.Stdout, os.Stderr) 21 | } else { 22 | InitLog("trace-log.txt", ioutil.Discard, os.Stdout, os.Stderr) 23 | } 24 | // use a filename in a downloads subdirectory 25 | fileName = os.Getenv("TEST_FILENAME") 26 | if len(fileName) == 0 { 27 | fileName = defaultFileName // CloudflareLogFilename(time.Now()) 28 | } 29 | Debug.Printf("ProjectRoot: %s", PadRight(Config.ProjectRoot, " ", 20)) 30 | Debug.Printf("AppEnv: %s", PadRight(Config.AppEnv, " ", 20)) 31 | Debug.Printf("GcpSourceKeyFile: %s", PadRight(Config.GcpSourceKeyFile, " ", 20)) 32 | Debug.Printf("GcpSinkKeyFile: %s", PadRight(Config.GcpSinkKeyFile, " ", 20)) 33 | Debug.Printf("LogDebugInfo: %v", Config.LogDebugInfo) 34 | HandlePanic(os.Chdir(Config.ProjectRoot)) 35 | } 36 | 37 | type endpoint struct { 38 | Api 39 | uriExample string 40 | } 41 | 42 | func printApiExample(url, uriExample string) { 43 | if len(uriExample) == 0 { 44 | Info.Printf("http://localhost:%s%s", Config.ApiPort, url) 45 | } else { 46 | Info.Printf("http://localhost:%s%s?%s", Config.ApiPort, url, uriExample) 47 | } 48 | } 49 | 50 | func main() { 51 | gcpi, err := infrastructure.GetGcpInteractor() 52 | HandlePanic(errors.Wrap(err, "unable to get gcp interactor")) 53 | li, err := infrastructure.GetLocalInteractor() 54 | HandlePanic(errors.Wrap(err, "unable to get local interactor")) 55 | 56 | wsh = WebserviceHandler{} 57 | wsh.GcpInteractor = gcpi 58 | wsh.LocalInteractor = li 59 | 60 | var endpoints = []endpoint{ 61 | {Api{wsh.Health, "/health"}, ""}, 62 | {Api{wsh.ListSourceBuckets, "/list-source-buckets"}, "projectId="+Config.GcpSourceProjectId}, 63 | {Api{wsh.ListSinkBuckets, "/list-sink-buckets"}, "projectId="+Config.GcpSinkProjectId}, 64 | {Api{wsh.SourceFileExists, "/source-file-exists"}, "fileName="+fileName}, 65 | {Api{wsh.DownloadFile, "/download-file"}, "fileName="+fileName}, 66 | {Api{wsh.UploadFile, "/upload-file"}, "fileName="+fileName}, 67 | {Api{wsh.LocalFileExists, "/local-file-exists"}, "fileName="+fileName}, 68 | } 69 | Info.Println("Example API endpoints:") 70 | { 71 | for _, ep := range endpoints { 72 | http.HandleFunc(ep.Api.Url, ep.Api.Handler) 73 | printApiExample(ep.Api.Url, ep.uriExample) 74 | } 75 | } 76 | http.ListenAndServe(":"+Config.ApiPort, nil) 77 | } 78 | -------------------------------------------------------------------------------- /Chapter06/04_onion/src/domain/api.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | type Existence struct { 4 | Exists bool `json:"exists"` 5 | } 6 | 7 | type Outcome struct { 8 | Success bool `json:"success"` 9 | } 10 | 11 | type OutcomeAndMsg struct { 12 | Success bool `json:"success"` 13 | Message string `json:"message"` 14 | } 15 | 16 | type MultiStatus struct { 17 | OutcomeAndMsgs []OutcomeAndMsg 18 | } 19 | -------------------------------------------------------------------------------- /Chapter06/04_onion/src/domain/domain.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | const ( 4 | GoogleCloudBucket HostProvider = iota 5 | SourceFlow FlowType = iota 6 | SinkFlow 7 | ) 8 | 9 | type ( 10 | HostProvider int 11 | FlowType int 12 | ) 13 | 14 | type CloudStorage struct { 15 | HostProvider HostProvider //Host location for log files, e.g., google cloud bucket 16 | ProjectId string //Project Id for this GCP storage account 17 | FlowType FlowType //source or sink 18 | } 19 | 20 | type LocalRepository interface { 21 | FileExists(fileName string) (fileExists bool, err error) 22 | } 23 | 24 | type BucketRepository interface { 25 | List(projectId string) (buckets []Bucket, err error) 26 | FileExists(fileName string) (fileExists bool, err error) 27 | DownloadFile(fileName string) (success bool, err error) 28 | UploadFile(fileName string) (success bool, err error) 29 | } 30 | 31 | type FileRepository interface { 32 | Store(file File) 33 | FindById(id int) File 34 | } 35 | 36 | type Bucket struct { 37 | Name string `json:"name"` 38 | } 39 | type Buckets struct { 40 | Buckets []Bucket `json:"buckets"` 41 | } 42 | -------------------------------------------------------------------------------- /Chapter06/04_onion/src/domain/log_file.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "os" 5 | "io" 6 | "encoding/json" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | type User struct { 11 | UserId int `json:"userId"` 12 | Country string `json:"country"` 13 | DeviceType string `json:"deviceType"` 14 | IP string `json:"ip"` 15 | SrcPort int `json:"srcPort"` 16 | } 17 | 18 | type LogFile struct { 19 | EventId int `json:"eventId"` 20 | Timestamp int64 `json:"timestamp"` 21 | Description string `json:"description"` 22 | User 23 | } 24 | 25 | func NewLogFile(logfileJson string) (logFile *LogFile, err error) { 26 | err = json.Unmarshal([]byte(logfileJson), &logFile) 27 | if err != nil { 28 | return nil, errors.Wrap(err, "unable to unmarshal json") 29 | } 30 | return 31 | } 32 | 33 | func (lf *LogFile) ToJson() (logFileJson string, err error) { 34 | logFileBytes, err := json.Marshal(lf) 35 | if err != nil { 36 | return "", errors.Wrap(err, "unable to marshal json") 37 | } 38 | logFileJson = string(logFileBytes) 39 | return 40 | } 41 | 42 | func (lf *LogFile) Write(logFilename, contents string) (err error) { 43 | overwrite := true 44 | flag := os.O_WRONLY | os.O_CREATE 45 | if overwrite { 46 | flag |= os.O_TRUNC 47 | } else { 48 | flag |= os.O_EXCL 49 | } 50 | osFile, err := os.OpenFile(logFilename, flag, 0666) 51 | if err != nil { 52 | return errors.Wrapf(err, "unable to open %s", logFilename) 53 | } 54 | bytes := []byte(contents) 55 | n, err := osFile.Write(bytes) 56 | if err == nil && n < len(bytes) { 57 | err = io.ErrShortWrite 58 | return errors.Wrapf(io.ErrShortWrite, "not all bytes written for %s", logFilename) 59 | } 60 | if err1 := osFile.Close(); err1 != nil { 61 | return errors.Wrapf(err, "unable to close %s", logFilename) 62 | } 63 | return 64 | } 65 | -------------------------------------------------------------------------------- /Chapter06/04_onion/src/infrastructure/infrastructure_test/localhandler_test.go: -------------------------------------------------------------------------------- 1 | package infrastructure_test 2 | 3 | import ( 4 | "testing" 5 | "infrastructure" 6 | ) 7 | 8 | func TestLocalInteractor(t *testing.T) { 9 | localInteractor, err := infrastructure.GetLocalInteractor() 10 | if err != nil { 11 | t.Error("GetLocalInteractor failed") 12 | } 13 | localInteractor.LocalRepository.FileExists() 14 | } 15 | 16 | func TestFileExists(t *testing.T) { 17 | t.Error("We haven't written our test yet") 18 | } 19 | -------------------------------------------------------------------------------- /Chapter06/04_onion/src/infrastructure/localhandler.go: -------------------------------------------------------------------------------- 1 | package infrastructure 2 | 3 | import ( 4 | "interfaces" 5 | "usecases" 6 | . "utils" 7 | "fmt" 8 | "os" 9 | ) 10 | 11 | type LocalHandler struct {} 12 | 13 | var LocalInteractor *usecases.LocalInteractor 14 | 15 | func NewLocalHandler() *LocalHandler { 16 | gcpHandler := new(LocalHandler) 17 | return gcpHandler 18 | } 19 | 20 | func (handler *LocalHandler) FileExists(fileName string) (fileExists bool, err error) { 21 | _, err = os.Stat(fmt.Sprintf("%s/%s", Config.DownloadDir, fileName)) 22 | if !os.IsNotExist(err) { 23 | fileExists = true 24 | } 25 | return 26 | } 27 | 28 | func GetLocalInteractor() (localInteractor *usecases.LocalInteractor, err error) { 29 | if LocalInteractor == nil { 30 | localHandler := NewLocalHandler() 31 | localHandlers := make(map[string] interfaces.LocalHandler) 32 | localHandlers["LocalFileSystemRepo"] = localHandler 33 | localInteractor = new(usecases.LocalInteractor) 34 | localInteractor.LocalRepository = interfaces.NewLocalRepo(localHandlers) 35 | LocalInteractor = localInteractor 36 | } 37 | return LocalInteractor, nil 38 | } -------------------------------------------------------------------------------- /Chapter06/04_onion/src/interfaces/gcpstorage.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "domain" 5 | ) 6 | 7 | type GcpHandler interface { 8 | ListBuckets(flowType domain.FlowType, projectId string) (buckets []domain.Bucket, err error) 9 | FileExists(fileName string) (fileExists bool, err error) 10 | DownloadFile(fileName string) (success bool, err error) 11 | UploadFile(fileName string) (success bool, err error) 12 | } 13 | 14 | type GcpRepo struct { 15 | gcpHandlers map[string]GcpHandler 16 | gcpHandler GcpHandler 17 | } 18 | 19 | type SourceBucketRepo GcpRepo 20 | type SinkBucketRepo GcpRepo 21 | 22 | 23 | func NewSourceBucketRepo(gcpHandlers map[string]GcpHandler) *SourceBucketRepo { 24 | bucketRepo := new(SourceBucketRepo) 25 | bucketRepo.gcpHandlers = gcpHandlers 26 | bucketRepo.gcpHandler = gcpHandlers["SourceBucketRepo"] 27 | return bucketRepo 28 | } 29 | 30 | func (repo *SourceBucketRepo) List(projectId string) (buckets []domain.Bucket, err error) { 31 | return repo.gcpHandler.ListBuckets(domain.SourceFlow, projectId) 32 | } 33 | 34 | func (repo *SourceBucketRepo) FileExists(fileName string) (fileExists bool, err error) { 35 | return repo.gcpHandler.FileExists(fileName) 36 | } 37 | 38 | func (repo *SourceBucketRepo) DownloadFile(fileName string) (success bool, err error) { 39 | return repo.gcpHandler.DownloadFile(fileName) 40 | } 41 | // UploadFile is not operational for a source bucket 42 | func (repo *SourceBucketRepo) UploadFile(fileName string) (success bool, err error) { 43 | return false, nil 44 | } 45 | 46 | 47 | func NewSinkBucketRepo(gcpHandlers map[string]GcpHandler) *SinkBucketRepo { 48 | bucketRepo := new(SinkBucketRepo) 49 | bucketRepo.gcpHandlers = gcpHandlers 50 | bucketRepo.gcpHandler = gcpHandlers["SinkBucketRepo"] 51 | return bucketRepo 52 | } 53 | 54 | func (repo *SinkBucketRepo) List(projectId string) (buckets []domain.Bucket, err error) { 55 | return repo.gcpHandler.ListBuckets(domain.SinkFlow, projectId) 56 | } 57 | 58 | func (repo *SinkBucketRepo) FileExists(fileName string) (fileExists bool, err error) { 59 | return repo.gcpHandler.FileExists(fileName) 60 | } 61 | // DownloadFile is not operational for a sink bucket 62 | func (repo *SinkBucketRepo) DownloadFile(fileName string) (success bool, err error) { 63 | return false, nil 64 | } 65 | 66 | func (repo *SinkBucketRepo) UploadFile(fileName string) (success bool, err error) { 67 | return repo.gcpHandler.UploadFile(fileName) 68 | } 69 | // ListFileNamesToFetch is not operational for a sink bucket 70 | func (repo *SinkBucketRepo) ListFileNamesToFetch(fileName string) (cloudFiles domain.CloudFiles, err error) { 71 | return cloudFiles, err 72 | } 73 | 74 | 75 | -------------------------------------------------------------------------------- /Chapter06/04_onion/src/interfaces/impl.go_STOP: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "domain" 5 | ) 6 | 7 | // Called by local-file-exists Api 8 | func GetFile(fileName string) (localFile *domain.File, err error) { 9 | // could return from a cache 10 | return domain.NewFile(fileName), nil 11 | } 12 | 13 | -------------------------------------------------------------------------------- /Chapter06/04_onion/src/interfaces/interfaces_test/webservice_test.go: -------------------------------------------------------------------------------- 1 | package interfaces_test 2 | 3 | import ( 4 | . "interfaces" 5 | . "utils" 6 | "infrastructure" 7 | "github.com/pkg/errors" 8 | "io/ioutil" 9 | "net/http" 10 | "net/http/httptest" 11 | "os" 12 | "strings" 13 | "testing" 14 | ) 15 | 16 | const failure = "\u2717" 17 | const defaultFileName = "eventset1.jsonl" 18 | 19 | var fileName string 20 | var wsh WebserviceHandler 21 | 22 | func init() { 23 | GetOptions() 24 | if Config.LogDebugInfoForTests { 25 | InitLog("trace-debug-log.txt", os.Stdout, os.Stdout, os.Stderr) 26 | } else { 27 | InitLog("trace-debug-log.txt", ioutil.Discard, os.Stdout, os.Stderr) 28 | } 29 | HandlePanic(os.Chdir(Config.ProjectRoot)) 30 | Debug.Printf("Config: %+v\n", Config) 31 | // use a filename in a downloads subdirectory 32 | fileName = os.Getenv("TEST_FILENAME") 33 | if len(fileName) == 0 { 34 | fileName = defaultFileName 35 | } 36 | // instantiate interactors 37 | gcpi, err := infrastructure.GetGcpInteractor() 38 | HandlePanic(errors.Wrap(err, "unable to get gcp interactor")) 39 | li, err := infrastructure.GetLocalInteractor() 40 | HandlePanic(errors.Wrap(err, "unable to get local interactor")) 41 | // wire up interactors to webservice handler 42 | wsh = WebserviceHandler{} 43 | wsh.GcpInteractor = gcpi 44 | wsh.LocalInteractor = li 45 | } 46 | 47 | type endpoint struct { 48 | Api 49 | expectedBody string 50 | } 51 | 52 | func TestEndpoints(t *testing.T) { 53 | Debug.Printf("fileName: %s", fileName) 54 | 55 | var endpoints = []endpoint{ 56 | {Api{wsh.Health, 57 | "/health"}, 58 | `{"alive": true}`}, 59 | {Api{wsh.ListSourceBuckets, 60 | "/list-source-buckets?projectId="+Config.GcpSourceProjectId}, 61 | `{"buckets":[{"name":"lexttc3-my-backup-bucket"},{"name":"lexttc3-my-source-bucket"}]}`}, 62 | {Api{wsh.ListSinkBuckets, 63 | "/list-sink-buckets?projectId="+Config.GcpSinkProjectId}, 64 | `{"buckets":[{"name":"lexttc3-my-backup-bucket"},{"name":"lexttc3-my-source-bucket"}]}`}, 65 | {Api{wsh.UploadFile, 66 | "/upload-file?fileName="+fileName}, 67 | `{"success":true}`}, 68 | //{Api{wsh.DownloadFile, 69 | // "/download-file?fileName="+fileName}, 70 | // `{"success":true}`}, 71 | {Api{wsh.SourceFileExists, 72 | "/source-file-exists?fileName="+fileName}, 73 | `{"exists":true}`}, 74 | {Api{wsh.LocalFileExists, 75 | "/local-file-exists?fileName="+fileName}, 76 | `{"exists":true}`}, 77 | } 78 | 79 | t.Log("Testing API endpoints...") 80 | { 81 | for _, ep := range endpoints { 82 | { 83 | req, err := http.NewRequest("GET", ep.Api.Url, nil) 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | // Create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response 88 | rr := httptest.NewRecorder() 89 | handler := http.HandlerFunc(ep.Api.Handler) 90 | // Our handlers implement http.Handler, so we can call their ServeHTTP method directly 91 | handler.ServeHTTP(rr, req) 92 | t.Logf("\tChecking \"%s\" for status code \"%d\"", 93 | ep.Api.Url, http.StatusOK) 94 | if status := rr.Code; status != http.StatusOK { 95 | t.Errorf("\t\t%v handler returned wrong status code: got %v want %v", 96 | failure, status, http.StatusOK) 97 | } 98 | t.Logf("\tChecking \"%s\" for expected body", ep.Api.Url) 99 | Debug.Println("rr.Body.String(): ", rr.Body.String()) 100 | if strings.TrimSpace(rr.Body.String()) != ep.expectedBody { 101 | t.Errorf("\t\t%v handler returned unexpected body: got %v want %v", 102 | failure, rr.Body.String(), ep.expectedBody) 103 | } 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Chapter06/04_onion/src/interfaces/localstorage.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | type LocalHandler interface { 4 | FileExists(fileName string) (fileExists bool, err error) 5 | } 6 | 7 | type LocalRepo struct { 8 | localHandlers map[string]LocalHandler 9 | localHandler LocalHandler 10 | } 11 | 12 | type LocalFileSystemRepo LocalRepo 13 | 14 | func NewLocalRepo(localHandlers map[string]LocalHandler) *LocalFileSystemRepo { 15 | localRepo := new(LocalFileSystemRepo) 16 | localRepo.localHandlers = localHandlers 17 | localRepo.localHandler = localHandlers["LocalFileSystemRepo"] 18 | return localRepo 19 | } 20 | 21 | func (repo *LocalFileSystemRepo) FileExists(fileName string) (fileExists bool, err error) { 22 | return repo.localHandler.FileExists(fileName) 23 | } 24 | -------------------------------------------------------------------------------- /Chapter06/04_onion/src/interfaces/webservice.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "domain" 7 | ) 8 | 9 | type Api struct { 10 | Handler func(res http.ResponseWriter, req *http.Request) 11 | Url string 12 | } 13 | 14 | type LocalInteractor interface { 15 | LocalFileExists(fileName string) (fileExists bool, err error) 16 | } 17 | 18 | type GcpInteractor interface { 19 | ListSourceBuckets(projectId string) (buckets []domain.Bucket, err error) 20 | ListSinkBuckets(projectId string) (buckets []domain.Bucket, err error) 21 | SourceFileExists(fileName string) (fileExists bool, err error) 22 | DownloadFile(fileName string) (success bool, err error) 23 | UploadFile(fileName string) (success bool, err error) 24 | } 25 | 26 | type WebserviceHandler struct { 27 | LocalInteractor LocalInteractor 28 | GcpInteractor GcpInteractor 29 | } 30 | 31 | func (handler WebserviceHandler) Health(res http.ResponseWriter, req *http.Request) { 32 | res.WriteHeader(http.StatusOK) 33 | res.Header().Set("Content-Type", "application/json") 34 | io.WriteString(res, `{"alive": true}`) 35 | } 36 | 37 | func (handler WebserviceHandler) LocalFileExists(res http.ResponseWriter, req *http.Request) { 38 | fileName := req.FormValue("fileName") 39 | exists, err := handler.LocalInteractor.LocalFileExists(fileName) 40 | handleExists(sf("Running LocalFileExists for fileName: %s...", fileName), "find file", req, res, err, exists) 41 | } 42 | 43 | 44 | func (handler WebserviceHandler) ListSourceBuckets(res http.ResponseWriter, req *http.Request) { 45 | projectId := req.FormValue("projectId") 46 | bucketNames, err := handler.GcpInteractor.ListSourceBuckets(projectId) 47 | handleBuckets(sf("Running ListSourceBuckets for projectId: %s...", projectId), "list source buckets", req, res, err, bucketNames) 48 | } 49 | 50 | func (handler WebserviceHandler) ListSinkBuckets(res http.ResponseWriter, req *http.Request) { 51 | projectId := req.FormValue("projectId") 52 | bucketNames, err := handler.GcpInteractor.ListSinkBuckets(projectId) 53 | handleBuckets(sf("Running ListSinkBuckets for projectId: %s...", projectId), "list sink buckets", req, res, err, bucketNames) 54 | } 55 | 56 | func (handler WebserviceHandler) SourceFileExists(res http.ResponseWriter, req *http.Request) { 57 | fileName := req.FormValue("fileName") 58 | exists, err := handler.GcpInteractor.SourceFileExists(fileName) 59 | handleExists(sf("Running SourceFileExists for fileName: %s...", fileName), "find file", req, res, err, exists) 60 | } 61 | 62 | func (handler WebserviceHandler) DownloadFile(res http.ResponseWriter, req *http.Request) { 63 | fileName := req.FormValue("fileName") 64 | success, err := handler.GcpInteractor.DownloadFile(fileName) 65 | handleSuccess(sf("Running DownloadFile for fileName: %s...", fileName), "upload file", req, res, err, success) 66 | } 67 | 68 | func (handler WebserviceHandler) UploadFile(res http.ResponseWriter, req *http.Request) { 69 | fileName := req.FormValue("fileName") 70 | success, err := handler.GcpInteractor.UploadFile(fileName) 71 | handleSuccess(sf("Running UploadFile for fileName: %s...", fileName), "upload file", req, res, err, success) 72 | } 73 | -------------------------------------------------------------------------------- /Chapter06/04_onion/src/interfaces/webservice_helpers.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/pkg/errors" 6 | "net/http" 7 | "fmt" 8 | "domain" 9 | . "utils" 10 | ) 11 | 12 | var ErrorResponse = []byte("Error") 13 | var sf = fmt.Sprintf 14 | 15 | func getFormat(r *http.Request) (format string) { 16 | //format = r.URL.Query()["format"][0] 17 | // Hard code json for now 18 | format = "json" 19 | return 20 | } 21 | 22 | func setFormat(format string, data interface{}) ([]byte, error) { 23 | var apiOutput []byte 24 | if format == "json" { 25 | output, err := json.Marshal(data) 26 | if err != nil { 27 | return nil, errors.Wrap(err, "unable to marshal data to json") 28 | } 29 | apiOutput = output 30 | } else { 31 | Error.Printf("invalid data format encountered") 32 | apiOutput = ErrorResponse 33 | } 34 | return apiOutput, nil 35 | } 36 | 37 | func handleSuccess(debugMsg, msg string, req *http.Request, res http.ResponseWriter, err error, success bool) { 38 | Debug.Printf(debugMsg) 39 | response := domain.Outcome{} 40 | response.Success = success 41 | if err != nil { 42 | Error.Printf("Failed to %s. %v", msg, err) 43 | } 44 | output, err := setFormat(getFormat(req), response) 45 | if err != nil { 46 | output = ErrorResponse 47 | Error.Printf("Failed to setFormat. %v", err) 48 | } 49 | Debug.Printf("string(output): %s", string(output)) 50 | fmt.Fprintln(res, string(output)) 51 | } 52 | 53 | 54 | func handleExists(debugMsg, msg string, req *http.Request, res http.ResponseWriter, err error, exists bool) { 55 | Debug.Printf(debugMsg) 56 | response := domain.Existence{} 57 | response.Exists = exists 58 | if err != nil { 59 | Error.Printf("Failed to %s. %v", msg, err) 60 | } 61 | output, err := setFormat(getFormat(req), response) 62 | if err != nil { 63 | output = ErrorResponse 64 | Error.Printf("Failed to setFormat. %v", err) 65 | } 66 | Debug.Printf("string(output): %s", string(output)) 67 | fmt.Fprintln(res, string(output)) 68 | } 69 | 70 | func handleBuckets(debugMsg, msg string, req *http.Request, res http.ResponseWriter, err error, bucketNames []domain.Bucket) { 71 | Debug.Printf(debugMsg) 72 | response := domain.Buckets{} 73 | for _, bucketName := range bucketNames { 74 | Debug.Printf("bucketName: %s", bucketName) 75 | response.Buckets = append(response.Buckets, bucketName) 76 | } 77 | if err != nil { 78 | Error.Printf("Failed to %s. %v", msg, err) 79 | } 80 | output, err := setFormat(getFormat(req), response) 81 | if err != nil { 82 | output = ErrorResponse 83 | Error.Printf("Failed to setFormat. %v", err) 84 | //return 85 | } 86 | Debug.Printf("string(output): %s", string(output)) 87 | fmt.Fprintln(res, string(output)) 88 | } 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /Chapter06/04_onion/src/usecases/usecases.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "domain" 5 | ) 6 | 7 | type LocalInteractor struct { 8 | LocalRepository domain.LocalRepository 9 | } 10 | 11 | func (interactor *LocalInteractor) LocalFileExists(fileName string) (fileExists bool, err error) { 12 | return interactor.LocalRepository.FileExists(fileName) 13 | } 14 | 15 | 16 | type GcpInteractor struct { 17 | SourceBucketRepository domain.BucketRepository 18 | SinkBucketRepository domain.BucketRepository 19 | } 20 | 21 | func (interactor *GcpInteractor) ListSourceBuckets(projectId string) (buckets []domain.Bucket, err error) { 22 | return interactor.SourceBucketRepository.List(projectId) 23 | } 24 | 25 | func (interactor *GcpInteractor) ListSinkBuckets(projectId string) (buckets []domain.Bucket, err error) { 26 | return interactor.SinkBucketRepository.List(projectId) 27 | } 28 | 29 | func (interactor *GcpInteractor) SourceFileExists(fileName string) (fileExists bool, err error) { 30 | return interactor.SourceBucketRepository.FileExists(fileName) 31 | } 32 | 33 | func (interactor *GcpInteractor) DownloadFile(fileName string) (success bool, err error) { 34 | return interactor.SourceBucketRepository.DownloadFile(fileName) 35 | } 36 | 37 | func (interactor *GcpInteractor) UploadFile(fileName string) (success bool, err error) { 38 | return interactor.SinkBucketRepository.UploadFile(fileName) 39 | } 40 | -------------------------------------------------------------------------------- /Chapter06/04_onion/src/utils/logger.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io" 5 | log "github.com/sirupsen/logrus" 6 | "os" 7 | "fmt" 8 | ) 9 | 10 | var ( 11 | Debug *log.Logger 12 | Info *log.Logger 13 | Error *log.Logger 14 | InfoHandler io.Writer 15 | ErrorHandler io.Writer 16 | ) 17 | 18 | const BasicTimeStampFormat = "2006-01-02 15:04:05" 19 | var LevelDescriptions = []string{"PANC", "FATL", "ERRO", "WARN", "INFO", "DEBG"} 20 | 21 | func InitLog ( 22 | traceFileName string, 23 | debugHandler io.Writer, 24 | infoHandler io.Writer, 25 | errorHandler io.Writer, 26 | ) { 27 | if len(traceFileName) > 0 { 28 | _ = os.Remove(traceFileName) 29 | file, err := os.OpenFile(traceFileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) 30 | if err != nil { 31 | log.Fatalf("Failed to create log file: %s", traceFileName) 32 | } 33 | debugHandler = io.MultiWriter(file, debugHandler) 34 | infoHandler = io.MultiWriter(file, infoHandler) 35 | errorHandler = io.MultiWriter(file, errorHandler) 36 | } 37 | 38 | InfoHandler = infoHandler 39 | ErrorHandler = errorHandler 40 | 41 | plainFormatter := new(PlainFormatter) 42 | 43 | basicFormatter := new(BasicFormatter) 44 | basicFormatter.TimestampFormat = BasicTimeStampFormat 45 | basicFormatter.LevelDesc = LevelDescriptions 46 | 47 | plusVFormatter := new(PlusVFormatter) 48 | plusVFormatter.TimestampFormat = BasicTimeStampFormat 49 | plusVFormatter.LevelDesc = LevelDescriptions 50 | plusVFormatter.FullTimestamp = true 51 | 52 | 53 | Debug = log.New() 54 | Debug.Out = debugHandler 55 | //Debug.Formatter = new(log.TextFormatter) //new(log.JSONFormatter) 56 | Debug.Formatter = basicFormatter 57 | //Debug.Formatter = plainFormatter 58 | Debug.Hooks= make(log.LevelHooks) 59 | Debug.Level = log.DebugLevel 60 | 61 | Info = log.New() 62 | Info.Out = infoHandler 63 | //Info.Formatter = customFormatter 64 | Info.Formatter = plainFormatter 65 | Info.Hooks= make(log.LevelHooks) 66 | Info.Level = log.InfoLevel 67 | 68 | Error = log.New() 69 | Error.Out = errorHandler 70 | //Error.Formatter = plusVFormatter 71 | Error.Formatter = plainFormatter 72 | Error.Hooks= make(log.LevelHooks) 73 | Error.Level = log.DebugLevel 74 | } 75 | 76 | 77 | type PlainFormatter struct {} 78 | func (f *PlainFormatter) Format(entry *log.Entry) ([]byte, error) { 79 | return []byte(fmt.Sprintf("%s\n", entry.Message)), nil 80 | } 81 | 82 | type BasicFormatter struct { 83 | TimestampFormat string 84 | LevelDesc []string 85 | } 86 | func (f *BasicFormatter) Format(entry *log.Entry) ([]byte, error) { 87 | timestamp := fmt.Sprintf(entry.Time.Format(f.TimestampFormat)) 88 | return []byte(fmt.Sprintf("%s %s %s\n", f.LevelDesc[entry.Level], timestamp, entry.Message)), nil 89 | } 90 | 91 | type PlusVFormatter struct { 92 | TimestampFormat string 93 | LevelDesc []string 94 | FullTimestamp bool 95 | } 96 | func (f *PlusVFormatter) Format(entry *log.Entry) ([]byte, error) { 97 | timestamp := fmt.Sprintf(entry.Time.Format(f.TimestampFormat)) 98 | //TODO: Find bug in logrus that prevents entry.Level from returning correct value 99 | return []byte(fmt.Sprintf("%s %s %s\n", f.LevelDesc[Error.Level], timestamp, entry.Message)), nil 100 | } -------------------------------------------------------------------------------- /Chapter06/04_onion/src/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "path" 10 | "runtime" 11 | "time" 12 | "strings" 13 | ) 14 | 15 | type APIError struct { 16 | ErrorMessage string `json:"error_message"` 17 | HTTPStatus int `json:"http_status"` 18 | } 19 | 20 | type HttpErrorHandler struct { 21 | Caller string 22 | Response http.ResponseWriter 23 | Request *http.Request 24 | } 25 | 26 | const ( 27 | ErrorActionErr = iota 28 | ErrorActionWarn 29 | ErrorActionDebug 30 | ErrorActionInfo 31 | ) 32 | 33 | func NewHttpErrorHandle(caller string, response http.ResponseWriter, request *http.Request) *HttpErrorHandler { 34 | return &HttpErrorHandler{caller, response, request} 35 | } 36 | 37 | // HandleError locally, according to the action passed to h.Handle, and then serialized 38 | // in json and sent to the remote address via http, then returns true. 39 | // Otherwise, if there is no error, h.Handle returns false 40 | func (h *HttpErrorHandler) Handle(err error, httpStatus int, action int) bool { 41 | if err != nil { 42 | _, filepath, line, _ := runtime.Caller(1) 43 | _, file := path.Split(filepath) 44 | Error.Printf("HttpErrorHandler()->[file:%s line:%d]: %s", file, line, err.Error()) 45 | apiErr := &APIError{ 46 | ErrorMessage: err.Error(), 47 | HTTPStatus: httpStatus, 48 | } 49 | serialErr, _ := json.Marshal(&apiErr) 50 | h.Response.Header().Set("Content-Type", "application/json") 51 | h.Response.WriteHeader(httpStatus) 52 | io.WriteString(h.Response, string(serialErr)) 53 | } 54 | return (err != nil) 55 | } 56 | 57 | // HandlePanic _Never_ returns on error, instead it panics 58 | func FromLineOfFile() string { 59 | _, filepath, line, _ := runtime.Caller(1) 60 | _, file := path.Split(filepath) 61 | return fmt.Sprintf("[file:%s line:%d]", file, line) 62 | } 63 | 64 | // HandlePanic _Never_ returns an error, instead it panics 65 | func HandlePanic(err error) { 66 | if err != nil { 67 | _, filePath, lineNo, _ := runtime.Caller(1) 68 | _, fileName := path.Split(filePath) 69 | msg := fmt.Sprintf("[file:%s line:%d]: %s", fileName, lineNo, err.Error()) 70 | panic(msg) 71 | } 72 | } 73 | 74 | func HandleError(err error, action int) bool { 75 | if err != nil { 76 | _, filepath, line, _ := runtime.Caller(1) 77 | _, file := path.Split(filepath) 78 | switch action { 79 | case ErrorActionErr: 80 | Error.Printf("[file:%s line:%d]: %s", file, line, err.Error()) 81 | break 82 | case ErrorActionWarn: 83 | Error.Printf("[file:%s line:%d]: %s", file, line, err.Error()) 84 | break 85 | case ErrorActionDebug: 86 | Error.Printf("[file:%s line:%d]: %s", file, line, err.Error()) 87 | break 88 | case ErrorActionInfo: 89 | Error.Printf("[file:%s line:%d]: %s", file, line, err.Error()) 90 | break 91 | } 92 | } 93 | return (err != nil) 94 | } 95 | 96 | func WriteFile(filename string, source io.Reader) error { 97 | writer, err := os.Create(filename) 98 | if err != nil { 99 | return err 100 | } 101 | defer writer.Close() 102 | io.Copy(writer, source) 103 | return nil 104 | } 105 | 106 | // This is neat: https://coderwall.com/p/cp5fya/measuring-execution-time-in-go 107 | func TimeTrack(start time.Time, name string) { 108 | if Config.LogTimeTrack == true { 109 | elapsed := time.Since(start) 110 | Info.Printf("%s took %s", name, elapsed) 111 | } 112 | } 113 | 114 | // pad str with padWith count times to right 115 | func PadRight(str string, padWith string, length int) string { 116 | count := length - len(str) 117 | if count < 0 { 118 | count = 0 119 | } 120 | return str + strings.Repeat(padWith, count) 121 | } 122 | 123 | func InSlice(slice []string, searchFor string) (found bool) { 124 | for _, v := range slice { 125 | if searchFor == v { 126 | found = true 127 | } 128 | } 129 | return found 130 | } -------------------------------------------------------------------------------- /Chapter06/04_onion/src/utils/utils_test/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "testing" 5 | "utils" 6 | "github.com/pkg/errors" 7 | "os" 8 | ) 9 | 10 | func TestPanic(t *testing.T) { 11 | defer func() { 12 | if r := recover(); r == nil { 13 | t.Errorf("Expected to see a panic") 14 | } 15 | }() 16 | filePanicFcn() 17 | } 18 | 19 | func TestPanic2(t *testing.T) { 20 | assertPanic(t, filePanicFcn) 21 | } 22 | 23 | func TestPanic3(t *testing.T) { 24 | assertPanic(t, zeroPanicFcn) 25 | } 26 | 27 | 28 | // ------------- 29 | // Helpers 30 | // ------------- 31 | 32 | func assertPanic(t *testing.T, f func()) { 33 | defer func() { 34 | if r := recover(); r == nil { 35 | t.Errorf("Expected to see a panic") 36 | } 37 | }() 38 | f() 39 | } 40 | 41 | func filePanicFcn() { 42 | _, err := os.Open("doesnot-exist.txt") 43 | if err != nil { 44 | utils.HandlePanic(errors.Wrap(err, "unable to read file")) 45 | } 46 | } 47 | 48 | func divFcn(d int) error { 49 | if d == 0 { 50 | return errors.New("divide by 0 attempted") 51 | } 52 | return nil 53 | } 54 | 55 | func zeroPanicFcn() { 56 | err := divFcn(0) 57 | if err != nil { 58 | utils.HandlePanic(err) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Chapter07/func-param/config.toml: -------------------------------------------------------------------------------- 1 | #@IgnoreInspection BashAddShebang 2 | 3 | # Port that the API listens on 4 | port = "8080" 5 | 6 | # Whether to log debug output to the log (set to true for debug purposes) 7 | log_debug_info = true 8 | 9 | # Buffer size (not currently used) 10 | initial_buffer_size = 20 11 | 12 | # Maximum number of concurrent connections (not currently used) 13 | max_concurrent_connections = 4 14 | 15 | # Maximum number that user is allowed to enter 16 | max_number = 256 17 | 18 | # Use number handler (to display number, optionally with FormatNumber applied) else display files in project root 19 | use_number_handler = true 20 | -------------------------------------------------------------------------------- /Chapter07/func-param/glide.lock: -------------------------------------------------------------------------------- 1 | hash: bdf7fc2261afbf117e830e20345af7a9bf8a08311cf40a31d1ada449dff79c17 2 | updated: 2017-10-04T14:33:24.354540529-04:00 3 | imports: 4 | - name: github.com/BurntSushi/toml 5 | version: a368813c5e648fee92e5f6c30e3944ff9d5e8895 6 | - name: github.com/pkg/errors 7 | version: 2b3a18b5f0fb6b4f9190549597d3f962c02bc5eb 8 | - name: github.com/sirupsen/logrus 9 | version: 89742aefa4b206dcf400792f3bd35b542998eb3b 10 | - name: golang.org/x/crypto 11 | version: 9419663f5a44be8b34ca85f08abc5fe1be11f8a3 12 | subpackages: 13 | - ssh/terminal 14 | - name: golang.org/x/sys 15 | version: 314a259e304ff91bd6985da2a7149bbf91237993 16 | subpackages: 17 | - unix 18 | - windows 19 | testImports: [] 20 | -------------------------------------------------------------------------------- /Chapter07/func-param/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: 3 | - package: github.com/BurntSushi/toml 4 | - package: github.com/pkg/errors 5 | - package: github.com/sirupsen/logrus 6 | ignore: 7 | - ./server 8 | - ./utils 9 | -------------------------------------------------------------------------------- /Chapter07/func-param/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (it will create one if you don't have one) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=yes 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter07/func-param/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "server" 5 | . "utils" 6 | "context" 7 | "io/ioutil" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "time" 12 | "fmt" 13 | ) 14 | 15 | func init() { 16 | GetOptions() 17 | InitLog("trace-log.txt", ioutil.Discard, os.Stdout, os.Stderr) 18 | } 19 | 20 | func main() { 21 | quit := make(chan os.Signal, 1) 22 | signal.Notify(quit, os.Interrupt) 23 | Info.Printf("Config %+v", Config) 24 | newServer, err := server.New( 25 | server.MaxConcurrentConnections(4), 26 | server.MaxNumber(256), // Config.MaxNumber 27 | server.UseNumberHandler(true), 28 | server.FormatNumber(func(x int) (string, error) { return fmt.Sprintf("%x", x), nil }), // anonymous fcn 29 | //server.FormatNumber(func(x int) (string, error) { return "", errors.New("FormatNumber error") }), // anonymous fcn 30 | ) 31 | if err != nil { 32 | Error.Printf("unable to initialize server: %v", err) 33 | os.Exit(1) 34 | } 35 | srv := &http.Server{ 36 | Addr: ":"+Config.Port, 37 | Handler: newServer, 38 | } 39 | 40 | go func() { 41 | <-quit 42 | ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2 * time.Second)) 43 | defer cancel() 44 | Info.Println("shutting down server...") 45 | if err := srv.Shutdown( ctx ); err != nil { 46 | Error.Printf("unable to shutdown server: %v", err) 47 | } 48 | }() 49 | Error.Println("server started at localhost:"+Config.Port) 50 | err = srv.ListenAndServe() 51 | if err != nil && err != http.ErrServerClosed { 52 | Error.Printf("ListenAndServe error: %v", err) 53 | } 54 | Info.Println("server shutdown gracefully") 55 | } -------------------------------------------------------------------------------- /Chapter07/func-param/src/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/pkg/errors" 7 | "log" 8 | "net/http" 9 | "os" 10 | "strconv" 11 | ) 12 | 13 | const ( 14 | defaultServerMaxMessageSize = 1024 * 1024 * 4 15 | defaultMaxNumber = 30 16 | defaultMaxConcurrentConnections = 2 17 | ) 18 | 19 | var defaultServerOptions = options { 20 | maxMessageSize: defaultServerMaxMessageSize, 21 | maxNumber: defaultMaxNumber, 22 | maxConcurrentConnections: defaultMaxConcurrentConnections, 23 | } 24 | 25 | type Server struct { 26 | logger Logger 27 | opts options 28 | handler http.Handler 29 | } 30 | 31 | type options struct { 32 | maxMessageSize int 33 | maxNumber int 34 | maxConcurrentConnections int 35 | convertFn convert 36 | useNumberHandler bool 37 | } 38 | 39 | type Logger interface { 40 | Printf(format string, v ...interface{}) 41 | } 42 | 43 | func New(opt ...ServerOption) (*Server, error) { 44 | opts := defaultServerOptions 45 | for _, f := range opt { 46 | err := f(&opts) 47 | if err != nil { 48 | return nil, errors.Wrap(err, "error setting option") 49 | } 50 | } 51 | s := &Server{ 52 | opts: opts, 53 | logger: log.New(os.Stdout, "", 0), 54 | } 55 | s.register() 56 | return s, nil 57 | } 58 | 59 | func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 60 | s.handler.ServeHTTP(w, r) 61 | } 62 | 63 | func (s *Server) register() { 64 | mux := http.NewServeMux() 65 | if s.opts.useNumberHandler { 66 | mux.Handle("/", http.HandlerFunc(s.displayNumber)) 67 | } else { 68 | mux.Handle("/", http.FileServer(http.Dir("./"))) 69 | } 70 | s.handler = mux 71 | } 72 | 73 | func (s *Server) displayNumber(w http.ResponseWriter, r *http.Request) { 74 | s.logger.Printf("displayNumber called with number=%s\n", r.URL.Query().Get("number")) 75 | if numberParam := r.URL.Query().Get("number"); numberParam != "" { 76 | number, err := strconv.Atoi(numberParam) 77 | if err != nil { 78 | writeJSON(w, map[string]interface{}{ 79 | "error": fmt.Sprintf("invalid number (%v)", numberParam), 80 | }, http.StatusBadRequest) 81 | } 82 | if number > s.opts.maxNumber { 83 | writeJSON(w, map[string]interface{}{ 84 | "error": fmt.Sprintf("number (%d) too big. Max number: %d", number, s.opts.maxNumber), 85 | }, http.StatusBadRequest) 86 | } else { 87 | var displayNumber string 88 | if s.opts.convertFn == nil { 89 | displayNumber = numberParam 90 | } else { 91 | displayNumber, err = s.opts.convertFn(number) 92 | } 93 | if err != nil { 94 | writeJSON(w, map[string]interface{}{ 95 | "error": "error running convertFn number", 96 | }, http.StatusBadRequest) 97 | } else { 98 | writeJSON(w, map[string]interface{}{ 99 | "displayNumber": displayNumber, 100 | }) 101 | } 102 | } 103 | } else { 104 | writeJSON(w, map[string]interface{}{ 105 | "error": "missing number", 106 | }, http.StatusBadRequest) 107 | } 108 | } 109 | 110 | func writeJSON(w http.ResponseWriter, v interface{}, statuses ...int) { 111 | w.Header().Set("Content-Type", "application/json;charset=utf-8") 112 | if len(statuses) > 0 { 113 | w.WriteHeader(statuses[0]) 114 | } 115 | json.NewEncoder(w).Encode(v) 116 | } 117 | -------------------------------------------------------------------------------- /Chapter07/func-param/src/server/server_options.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | . "utils" 5 | "errors" 6 | ) 7 | 8 | type ServerOption func(*options) error 9 | 10 | func MaxNumber(n int) ServerOption { 11 | return func(o *options) error { 12 | o.maxNumber = n 13 | return nil 14 | } 15 | } 16 | 17 | func MaxConcurrentConnections(n int) ServerOption { 18 | return func(o *options) error { 19 | if n > Config.MaxConcurrentConnections { 20 | return errors.New("error setting MaxConcurrentConnections") 21 | } 22 | o.maxConcurrentConnections = n 23 | return nil 24 | } 25 | } 26 | 27 | type convert func(int) (string, error) 28 | 29 | func FormatNumber(fn convert) ServerOption { 30 | return func(o *options) (err error) { 31 | o.convertFn = fn 32 | return 33 | } 34 | } 35 | 36 | func UseNumberHandler(b bool) ServerOption { 37 | return func(o *options) error { 38 | o.useNumberHandler = b 39 | return nil 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /Chapter07/func-param/src/utils/logger.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io" 5 | log "github.com/sirupsen/logrus" 6 | "os" 7 | "fmt" 8 | ) 9 | 10 | var ( 11 | Debug *log.Logger 12 | Info *log.Logger 13 | Error *log.Logger 14 | InfoHandler io.Writer 15 | ErrorHandler io.Writer 16 | ) 17 | 18 | const BasicTimeStampFormat = "2006-01-02 15:04:05" 19 | var LevelDescriptions = []string{"PANC", "FATL", "ERRO", "WARN", "INFO", "DEBG"} 20 | 21 | func InitLog ( 22 | traceFileName string, 23 | debugHandler io.Writer, 24 | infoHandler io.Writer, 25 | errorHandler io.Writer, 26 | ) { 27 | if len(traceFileName) > 0 { 28 | _ = os.Remove(traceFileName) 29 | file, err := os.OpenFile(traceFileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) 30 | if err != nil { 31 | log.Fatalf("Failed to create log file: %s", traceFileName) 32 | } 33 | debugHandler = io.MultiWriter(file, debugHandler) 34 | infoHandler = io.MultiWriter(file, infoHandler) 35 | errorHandler = io.MultiWriter(file, errorHandler) 36 | } 37 | 38 | InfoHandler = infoHandler 39 | ErrorHandler = errorHandler 40 | 41 | plainFormatter := new(PlainFormatter) 42 | 43 | basicFormatter := new(BasicFormatter) 44 | basicFormatter.TimestampFormat = BasicTimeStampFormat 45 | basicFormatter.LevelDesc = LevelDescriptions 46 | 47 | plusVFormatter := new(PlusVFormatter) 48 | plusVFormatter.TimestampFormat = BasicTimeStampFormat 49 | plusVFormatter.LevelDesc = LevelDescriptions 50 | plusVFormatter.FullTimestamp = true 51 | 52 | 53 | Debug = log.New() 54 | Debug.Out = debugHandler 55 | //Debug.Formatter = new(log.TextFormatter) //new(log.JSONFormatter) 56 | Debug.Formatter = basicFormatter 57 | //Debug.Formatter = plainFormatter 58 | Debug.Hooks= make(log.LevelHooks) 59 | Debug.Level = log.DebugLevel 60 | 61 | Info = log.New() 62 | Info.Out = infoHandler 63 | //Info.Formatter = customFormatter 64 | Info.Formatter = plainFormatter 65 | Info.Hooks= make(log.LevelHooks) 66 | Info.Level = log.InfoLevel 67 | 68 | Error = log.New() 69 | Error.Out = errorHandler 70 | //Error.Formatter = plusVFormatter 71 | Error.Formatter = plainFormatter 72 | Error.Hooks= make(log.LevelHooks) 73 | Error.Level = log.DebugLevel 74 | } 75 | 76 | 77 | type PlainFormatter struct {} 78 | func (f *PlainFormatter) Format(entry *log.Entry) ([]byte, error) { 79 | return []byte(fmt.Sprintf("%s\n", entry.Message)), nil 80 | } 81 | 82 | type BasicFormatter struct { 83 | TimestampFormat string 84 | LevelDesc []string 85 | } 86 | func (f *BasicFormatter) Format(entry *log.Entry) ([]byte, error) { 87 | timestamp := fmt.Sprintf(entry.Time.Format(f.TimestampFormat)) 88 | return []byte(fmt.Sprintf("%s %s %s\n", f.LevelDesc[entry.Level], timestamp, entry.Message)), nil 89 | } 90 | 91 | type PlusVFormatter struct { 92 | TimestampFormat string 93 | LevelDesc []string 94 | FullTimestamp bool 95 | } 96 | func (f *PlusVFormatter) Format(entry *log.Entry) ([]byte, error) { 97 | timestamp := fmt.Sprintf(entry.Time.Format(f.TimestampFormat)) 98 | //TODO: Find bug in logrus that prevents entry.Level from returning correct value 99 | return []byte(fmt.Sprintf("%s %s %s\n", f.LevelDesc[Error.Level], timestamp, entry.Message)), nil 100 | } -------------------------------------------------------------------------------- /Chapter07/func-param/src/utils/options.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/BurntSushi/toml" 7 | "reflect" 8 | "strconv" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | type Conf struct { 13 | Port string `toml:"port"` 14 | LogDebugInfo bool `toml:"log_debug_info"` 15 | MaxConcurrentConnections int `toml:"max_concurrent_connections"` 16 | MaxNumber int `toml:"max_number"` 17 | UseNumberHandler bool `toml:"use_number_handler"` 18 | } 19 | 20 | var Config Conf 21 | 22 | func GetOptions() bool { 23 | var configFile string 24 | 25 | flag.StringVar(&configFile, "config", "", "Configuration file") 26 | flag.StringVar(&Config.Port, "port", "8080", "Port that the API listens on") 27 | flag.BoolVar(&Config.LogDebugInfo, "log-debug-info", false, "Whether to log debug output to the log (set to true for debug purposes)") 28 | flag.IntVar(&Config.MaxConcurrentConnections, "max-concurrent-connections", 6, "Maximum number of concurrent connections (not currently used)") 29 | flag.IntVar(&Config.MaxNumber, "max_number", 10, "Maximum number that user is allowed to enter") 30 | flag.BoolVar(&Config.UseNumberHandler, "use-number-handler", true, "Use number handler (to display number, optionally with FormatNumber applied) else display files in project root") 31 | 32 | flag.Parse() 33 | 34 | if configFile != "" { 35 | if _, err := toml.DecodeFile(configFile, &Config); err != nil { 36 | HandlePanic(errors.Wrap(err, "unable to read config file")) 37 | } 38 | } 39 | return true 40 | } 41 | 42 | type Datastore interface {} 43 | 44 | // UpdateConfigVal returns string representation of old config value 45 | func UpdateConfigVal(d Datastore, key, val string) (oldValue string) { 46 | Debug.Printf("key (%s), val (%v)\n", key, val) 47 | value := reflect.ValueOf(d) 48 | if value.Kind() != reflect.Ptr { 49 | panic("not a pointer") 50 | } 51 | valElem := value.Elem() 52 | //this loops through the fields 53 | for i := 0; i < valElem.NumField(); i++ { // iterates through every struct type field 54 | tag := valElem.Type().Field(i).Tag // returns the tag string 55 | field := valElem.Field(i) // returns the content of the struct type field 56 | switch tag.Get("toml") { 57 | case key: 58 | if fmt.Sprintf("%v", field.Kind()) == "int" { 59 | oldValue = strconv.FormatInt(field.Int(), 10) 60 | intVal, err := strconv.Atoi(val) 61 | if err != nil { 62 | fmt.Printf("could not parse int, key(%s) val(%s)", key, val) 63 | } else { 64 | field.SetInt(int64(intVal)) 65 | } 66 | } else if fmt.Sprintf("%v", field.Kind()) == "bool" { 67 | oldValue = strconv.FormatBool(field.Bool()) 68 | b, err := strconv.ParseBool(val) 69 | if err != nil { 70 | fmt.Printf("could not parse bool, key(%s) val(%s)", key, val) 71 | } else { 72 | field.SetBool(b) 73 | } 74 | } else { 75 | // Currently only supports bool, int and string 76 | oldValue = field.String() 77 | field.SetString(val) 78 | } 79 | } 80 | } 81 | return 82 | } 83 | 84 | -------------------------------------------------------------------------------- /Chapter07/func-param/src/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "path" 10 | "runtime" 11 | "strings" 12 | ) 13 | 14 | type APIError struct { 15 | ErrorMessage string `json:"error_message"` 16 | HTTPStatus int `json:"http_status"` 17 | } 18 | 19 | type HttpErrorHandler struct { 20 | Caller string 21 | Response http.ResponseWriter 22 | Request *http.Request 23 | } 24 | 25 | const ( 26 | ErrorActionErr = iota 27 | ErrorActionWarn 28 | ErrorActionDebug 29 | ErrorActionInfo 30 | ) 31 | 32 | func NewHttpErrorHandle(caller string, response http.ResponseWriter, request *http.Request) *HttpErrorHandler { 33 | return &HttpErrorHandler{caller, response, request} 34 | } 35 | 36 | // HandleError locally, according to the action passed to h.Handle, and then serialized 37 | // in json and sent to the remote address via http, then returns true. 38 | // Otherwise, if there is no error, h.Handle returns false 39 | func (h *HttpErrorHandler) Handle(err error, httpStatus int, action int) bool { 40 | if err != nil { 41 | _, filepath, line, _ := runtime.Caller(1) 42 | _, file := path.Split(filepath) 43 | Error.Printf("HttpErrorHandler()->[file:%s line:%d]: %s", file, line, err.Error()) 44 | apiErr := &APIError{ 45 | ErrorMessage: err.Error(), 46 | HTTPStatus: httpStatus, 47 | } 48 | serialErr, _ := json.Marshal(&apiErr) 49 | h.Response.Header().Set("Content-Type", "application/json") 50 | h.Response.WriteHeader(httpStatus) 51 | io.WriteString(h.Response, string(serialErr)) 52 | } 53 | return (err != nil) 54 | } 55 | 56 | // HandlePanic _Never_ returns on error, instead it panics 57 | func FromLineOfFile() string { 58 | _, filepath, line, _ := runtime.Caller(1) 59 | _, file := path.Split(filepath) 60 | return fmt.Sprintf("[file:%s line:%d]", file, line) 61 | } 62 | 63 | // HandlePanic _Never_ returns an error, instead it panics 64 | func HandlePanic(err error) { 65 | if err != nil { 66 | _, filePath, lineNo, _ := runtime.Caller(1) 67 | _, fileName := path.Split(filePath) 68 | msg := fmt.Sprintf("[file:%s line:%d]: %s", fileName, lineNo, err.Error()) 69 | panic(msg) 70 | } 71 | } 72 | 73 | func HandleError(err error, action int) bool { 74 | if err != nil { 75 | _, filepath, line, _ := runtime.Caller(1) 76 | _, file := path.Split(filepath) 77 | switch action { 78 | case ErrorActionErr: 79 | Error.Printf("[file:%s line:%d]: %s", file, line, err.Error()) 80 | break 81 | case ErrorActionWarn: 82 | Error.Printf("[file:%s line:%d]: %s", file, line, err.Error()) 83 | break 84 | case ErrorActionDebug: 85 | Error.Printf("[file:%s line:%d]: %s", file, line, err.Error()) 86 | break 87 | case ErrorActionInfo: 88 | Error.Printf("[file:%s line:%d]: %s", file, line, err.Error()) 89 | break 90 | } 91 | } 92 | return (err != nil) 93 | } 94 | 95 | func WriteFile(filename string, source io.Reader) error { 96 | writer, err := os.Create(filename) 97 | if err != nil { 98 | return err 99 | } 100 | defer writer.Close() 101 | io.Copy(writer, source) 102 | return nil 103 | } 104 | 105 | 106 | // pad str with padWith count times to right 107 | func PadRight(str string, padWith string, length int) string { 108 | count := length - len(str) 109 | if count < 0 { 110 | count = 0 111 | } 112 | return str + strings.Repeat(padWith, count) 113 | } 114 | 115 | func InSlice(slice []string, searchFor string) (found bool) { 116 | for _, v := range slice { 117 | if searchFor == v { 118 | found = true 119 | } 120 | } 121 | return found 122 | } -------------------------------------------------------------------------------- /Chapter07/func-param/src/utils/utils_test/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "testing" 5 | "utils" 6 | "github.com/pkg/errors" 7 | "os" 8 | ) 9 | 10 | func TestPanic(t *testing.T) { 11 | defer func() { 12 | if r := recover(); r == nil { 13 | t.Errorf("Expected to see a panic") 14 | } 15 | }() 16 | filePanicFcn() 17 | } 18 | 19 | func TestPanic2(t *testing.T) { 20 | assertPanic(t, filePanicFcn) 21 | } 22 | 23 | func TestPanic3(t *testing.T) { 24 | assertPanic(t, zeroPanicFcn) 25 | } 26 | 27 | 28 | // ------------- 29 | // Helpers 30 | // ------------- 31 | 32 | func assertPanic(t *testing.T, f func()) { 33 | defer func() { 34 | if r := recover(); r == nil { 35 | t.Errorf("Expected to see a panic") 36 | } 37 | }() 38 | f() 39 | } 40 | 41 | func filePanicFcn() { 42 | _, err := os.Open("doesnot-exist.txt") 43 | if err != nil { 44 | utils.HandlePanic(errors.Wrap(err, "unable to read file")) 45 | } 46 | } 47 | 48 | func divFcn(d int) error { 49 | if d == 0 { 50 | return errors.New("divide by 0 attempted") 51 | } 52 | return nil 53 | } 54 | 55 | func zeroPanicFcn() { 56 | err := divFcn(0) 57 | if err != nil { 58 | utils.HandlePanic(err) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Chapter07/func-param/trace-debug-log.txt: -------------------------------------------------------------------------------- 1 | server started at localhost:8080 2 | shutting down server... 3 | server shutdown gracefully 4 | -------------------------------------------------------------------------------- /Chapter07/func-param/trace-log.txt: -------------------------------------------------------------------------------- 1 | Config {Port:8080 LogDebugInfo:false MaxConcurrentConnections:6 MaxNumber:10 UseNumberHandler:true} 2 | server started at localhost:8080 3 | shutting down server... 4 | server shutdown gracefully 5 | -------------------------------------------------------------------------------- /Chapter08/01_imperative/ex1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | gc "github.com/go-goodies/go_currency" 6 | ) 7 | 8 | func main() { 9 | orders := GetOrders() 10 | for _, order := range orders { 11 | fmt.Printf("Processed order: %v\n", Pipeline(*order)) 12 | } 13 | } 14 | 15 | func Pipeline(o Order) Order { 16 | o = Authenticate(o) 17 | o = Decrypt(o) 18 | o = Charge(o) 19 | return o 20 | } 21 | 22 | 23 | func Authenticate(o Order) Order { 24 | fmt.Printf("Order %d is Authenticated\n", o.OrderNumber) 25 | return o 26 | } 27 | 28 | func Decrypt(o Order) Order { 29 | fmt.Printf("Order %d is Decrypted\n", o.OrderNumber) 30 | return o 31 | } 32 | 33 | func Charge(o Order) Order { 34 | fmt.Printf("Order %d is Charged\n", o.OrderNumber) 35 | return o 36 | } 37 | 38 | 39 | type Order struct { 40 | OrderNumber int 41 | IsValid bool 42 | Credentials string 43 | CCardNumber string 44 | CCardExpDate string 45 | LineItems []LineItem 46 | } 47 | 48 | type LineItem struct { 49 | Description string 50 | Count int 51 | PriceUSD gc.USD 52 | } 53 | 54 | func GetOrders() []*Order { 55 | 56 | order1 := &Order{ 57 | 10001, 58 | true, 59 | "alice,secret", 60 | "7b/HWvtIB9a16AYk+Yv6WWwer3GFbxpjoR+GO9iHIYY=", 61 | "0922", 62 | []LineItem{ 63 | LineItem{"Apples", 1, gc.USD{4, 50}}, 64 | LineItem{"Oranges", 4, gc.USD{12, 00}}, 65 | }, 66 | } 67 | 68 | order2 := &Order{ 69 | 10002, 70 | true, 71 | "bob,secret", 72 | "EOc3kF/OmxY+dRCaYRrey8h24QoGzVU0/T2QKVCHb1Q=", 73 | "0123", 74 | []LineItem{ 75 | LineItem{"Milk", 2, gc.USD{8, 00}}, 76 | LineItem{"Sugar", 1, gc.USD{2, 25}}, 77 | LineItem{"Salt", 3, gc.USD{3, 75}}, 78 | }, 79 | } 80 | orders := []*Order{order1, order2} 81 | return orders 82 | } 83 | -------------------------------------------------------------------------------- /Chapter08/01_imperative/glide.lock: -------------------------------------------------------------------------------- 1 | hash: f6b3a2dd121f9c6e5740b697ca5895a456ef44a9019cb5061f0afa13fc1501d5 2 | updated: 2017-10-04T18:56:23.905532083-04:00 3 | imports: 4 | - name: github.com/go-goodies/go_currency 5 | version: bf5e29957fc495f772d938b17185d50de8f36fd0 6 | testImports: [] 7 | -------------------------------------------------------------------------------- /Chapter08/01_imperative/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: 3 | - package: github.com/go-goodies/go_currency 4 | ignore: 5 | 6 | -------------------------------------------------------------------------------- /Chapter08/01_imperative/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (it will create one if you don't have one) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter08/02_concurrent/ex1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | gc "github.com/go-goodies/go_currency" 6 | ) 7 | 8 | func main() { 9 | input := make(chan Order) 10 | output := make(chan Order) 11 | 12 | go func() { 13 | for order := range input { 14 | output <- Pipeline(order) 15 | } 16 | }() 17 | 18 | orders := GetOrders() 19 | for _, order := range orders { 20 | fmt.Printf("Processed order: %v\n", Pipeline(*order)) 21 | } 22 | close(input) 23 | } 24 | 25 | // close the input channel so start() will exit and can clean up after 26 | // itself if it so wishes. 27 | 28 | func Pipeline(o Order) Order { 29 | o = Authenticate(o) 30 | o = Decrypt(o) 31 | o = Charge(o) 32 | return o 33 | } 34 | 35 | 36 | func Authenticate(o Order) Order { 37 | fmt.Printf("Order %d is Authenticated\n", o.OrderNumber) 38 | return o 39 | } 40 | 41 | func Decrypt(o Order) Order { 42 | fmt.Printf("Order %d is Decrypted\n", o.OrderNumber) 43 | return o 44 | } 45 | 46 | func Charge(o Order) Order { 47 | fmt.Printf("Order %d is Charged\n", o.OrderNumber) 48 | return o 49 | } 50 | 51 | 52 | 53 | type Order struct { 54 | OrderNumber int 55 | IsValid bool 56 | Credentials string 57 | CCardNumber string 58 | CCardExpDate string 59 | LineItems []LineItem 60 | } 61 | 62 | type LineItem struct { 63 | Description string 64 | Count int 65 | PriceUSD gc.USD 66 | } 67 | func GetOrders() []*Order { 68 | 69 | order1 := &Order{ 70 | 10001, 71 | true, 72 | "alice,secret", 73 | "7b/HWvtIB9a16AYk+Yv6WWwer3GFbxpjoR+GO9iHIYY=", 74 | "0922", 75 | []LineItem{ 76 | LineItem{"Apples", 1, gc.USD{4, 50}}, 77 | LineItem{"Oranges", 4, gc.USD{12, 00}}, 78 | }, 79 | } 80 | 81 | order2 := &Order{ 82 | 10002, 83 | true, 84 | "bob,secret", 85 | "EOc3kF/OmxY+dRCaYRrey8h24QoGzVU0/T2QKVCHb1Q=", 86 | "0123", 87 | []LineItem{ 88 | LineItem{"Milk", 2, gc.USD{8, 00}}, 89 | LineItem{"Sugar", 1, gc.USD{2, 25}}, 90 | LineItem{"Salt", 3, gc.USD{3, 75}}, 91 | }, 92 | } 93 | orders := []*Order{order1, order2} 94 | return orders 95 | } 96 | -------------------------------------------------------------------------------- /Chapter08/02_concurrent/glide.lock: -------------------------------------------------------------------------------- 1 | hash: f6b3a2dd121f9c6e5740b697ca5895a456ef44a9019cb5061f0afa13fc1501d5 2 | updated: 2017-10-04T18:57:29.056223767-04:00 3 | imports: 4 | - name: github.com/go-goodies/go_currency 5 | version: bf5e29957fc495f772d938b17185d50de8f36fd0 6 | testImports: [] 7 | -------------------------------------------------------------------------------- /Chapter08/02_concurrent/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: 3 | - package: github.com/go-goodies/go_currency 4 | ignore: 5 | 6 | -------------------------------------------------------------------------------- /Chapter08/02_concurrent/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (it will create one if you don't have one) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter08/03_buffered/ex1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | gc "github.com/go-goodies/go_currency" 6 | ) 7 | 8 | func main() { 9 | orders := GetOrders() 10 | numberOfOrders := len(orders) 11 | input := make(chan Order, numberOfOrders) 12 | output := make(chan Order, numberOfOrders) 13 | for i := 0; i < numberOfOrders; i++ { 14 | go func() { 15 | for order := range input { 16 | output <- Pipeline(order) 17 | } 18 | }() 19 | } 20 | for _, order := range orders { 21 | input <- *order 22 | } 23 | close(input) 24 | for i := 0; i < numberOfOrders; i++ { 25 | fmt.Println("The result is:", <-output) 26 | } 27 | } 28 | 29 | 30 | // close the input channel so start() will exit and can clean up after 31 | // itself if it so wishes. 32 | 33 | func Pipeline(o Order) Order { 34 | o = Authenticate(o) 35 | o = Decrypt(o) 36 | o = Charge(o) 37 | return o 38 | } 39 | 40 | 41 | func Authenticate(o Order) Order { 42 | fmt.Printf("Order %d is Authenticated\n", o.OrderNumber) 43 | return o 44 | } 45 | 46 | func Decrypt(o Order) Order { 47 | fmt.Printf("Order %d is Decrypted\n", o.OrderNumber) 48 | return o 49 | } 50 | 51 | func Charge(o Order) Order { 52 | fmt.Printf("Order %d is Charged\n", o.OrderNumber) 53 | return o 54 | } 55 | 56 | 57 | 58 | type Order struct { 59 | OrderNumber int 60 | IsValid bool 61 | Credentials string 62 | CCardNumber string 63 | CCardExpDate string 64 | LineItems []LineItem 65 | } 66 | 67 | type LineItem struct { 68 | Description string 69 | Count int 70 | PriceUSD gc.USD 71 | } 72 | func GetOrders() []*Order { 73 | 74 | order1 := &Order{ 75 | 10001, 76 | true, 77 | "alice,secret", 78 | "7b/HWvtIB9a16AYk+Yv6WWwer3GFbxpjoR+GO9iHIYY=", 79 | "0922", 80 | []LineItem{ 81 | LineItem{"Apples", 1, gc.USD{4, 50}}, 82 | LineItem{"Oranges", 4, gc.USD{12, 00}}, 83 | }, 84 | } 85 | 86 | order2 := &Order{ 87 | 10002, 88 | true, 89 | "bob,secret", 90 | "EOc3kF/OmxY+dRCaYRrey8h24QoGzVU0/T2QKVCHb1Q=", 91 | "0123", 92 | []LineItem{ 93 | LineItem{"Milk", 2, gc.USD{8, 00}}, 94 | LineItem{"Sugar", 1, gc.USD{2, 25}}, 95 | LineItem{"Salt", 3, gc.USD{3, 75}}, 96 | }, 97 | } 98 | orders := []*Order{order1, order2} 99 | return orders 100 | } 101 | -------------------------------------------------------------------------------- /Chapter08/03_buffered/glide.lock: -------------------------------------------------------------------------------- 1 | hash: f6b3a2dd121f9c6e5740b697ca5895a456ef44a9019cb5061f0afa13fc1501d5 2 | updated: 2017-10-04T18:58:08.466947799-04:00 3 | imports: 4 | - name: github.com/go-goodies/go_currency 5 | version: bf5e29957fc495f772d938b17185d50de8f36fd0 6 | testImports: [] 7 | -------------------------------------------------------------------------------- /Chapter08/03_buffered/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: 3 | - package: github.com/go-goodies/go_currency 4 | ignore: 5 | 6 | -------------------------------------------------------------------------------- /Chapter08/03_buffered/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (it will create one if you don't have one) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter08/04_buffered_cpus/ex1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | gc "github.com/go-goodies/go_currency" 6 | "runtime" 7 | ) 8 | 9 | func main() { 10 | orders := GetOrders() 11 | numberOfOrders := len(orders) 12 | cpus := runtime.NumCPU() 13 | runtime.GOMAXPROCS(cpus) 14 | input := make(chan Order, cpus) 15 | output := make(chan Order, cpus) 16 | for i := 0; i < numberOfOrders; i++ { 17 | go func() { 18 | for order := range input { 19 | output <- Pipeline(order) 20 | } 21 | }() 22 | } 23 | for _, order := range orders { 24 | input <- *order 25 | } 26 | close(input) 27 | for i := 0; i < numberOfOrders; i++ { 28 | fmt.Println("The result is:", <-output) 29 | } 30 | } 31 | 32 | 33 | // close the input channel so start() will exit and can clean up after 34 | // itself if it so wishes. 35 | 36 | func Pipeline(o Order) Order { 37 | o = Authenticate(o) 38 | o = Decrypt(o) 39 | o = Charge(o) 40 | return o 41 | } 42 | 43 | 44 | func Authenticate(o Order) Order { 45 | fmt.Printf("Order %d is Authenticated\n", o.OrderNumber) 46 | return o 47 | } 48 | 49 | func Decrypt(o Order) Order { 50 | fmt.Printf("Order %d is Decrypted\n", o.OrderNumber) 51 | return o 52 | } 53 | 54 | func Charge(o Order) Order { 55 | fmt.Printf("Order %d is Charged\n", o.OrderNumber) 56 | return o 57 | } 58 | 59 | 60 | 61 | type Order struct { 62 | OrderNumber int 63 | IsValid bool 64 | Credentials string 65 | CCardNumber string 66 | CCardExpDate string 67 | LineItems []LineItem 68 | } 69 | 70 | type LineItem struct { 71 | Description string 72 | Count int 73 | PriceUSD gc.USD 74 | } 75 | func GetOrders() []*Order { 76 | 77 | order1 := &Order{ 78 | 10001, 79 | true, 80 | "alice,secret", 81 | "7b/HWvtIB9a16AYk+Yv6WWwer3GFbxpjoR+GO9iHIYY=", 82 | "0922", 83 | []LineItem{ 84 | LineItem{"Apples", 1, gc.USD{4, 50}}, 85 | LineItem{"Oranges", 4, gc.USD{12, 00}}, 86 | }, 87 | } 88 | 89 | order2 := &Order{ 90 | 10002, 91 | true, 92 | "bob,secret", 93 | "EOc3kF/OmxY+dRCaYRrey8h24QoGzVU0/T2QKVCHb1Q=", 94 | "0123", 95 | []LineItem{ 96 | LineItem{"Milk", 2, gc.USD{8, 00}}, 97 | LineItem{"Sugar", 1, gc.USD{2, 25}}, 98 | LineItem{"Salt", 3, gc.USD{3, 75}}, 99 | }, 100 | } 101 | orders := []*Order{order1, order2} 102 | return orders 103 | } 104 | -------------------------------------------------------------------------------- /Chapter08/04_buffered_cpus/glide.lock: -------------------------------------------------------------------------------- 1 | hash: f6b3a2dd121f9c6e5740b697ca5895a456ef44a9019cb5061f0afa13fc1501d5 2 | updated: 2017-10-04T18:58:42.38661086-04:00 3 | imports: 4 | - name: github.com/go-goodies/go_currency 5 | version: bf5e29957fc495f772d938b17185d50de8f36fd0 6 | testImports: [] 7 | -------------------------------------------------------------------------------- /Chapter08/04_buffered_cpus/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: 3 | - package: github.com/go-goodies/go_currency 4 | ignore: 5 | 6 | -------------------------------------------------------------------------------- /Chapter08/04_buffered_cpus/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (it will create one if you don't have one) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter09/misc/ex1/anonymous.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func addTwo(x int) int { 4 | return x + 2 5 | } 6 | 7 | func main() { 8 | println(addTwo(5)) // named function 9 | 10 | println(func(x int) int {return x + 2}(5)) // anonymous function 11 | 12 | val := func(x int) int {return x + 2}(5) // function expression 13 | println(val) 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Chapter09/misc/ex2/lambda.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type lambda func(int) int 4 | 5 | func add(a int) lambda { 6 | return func(free int) int { 7 | return func(b int) int { 8 | return a + b 9 | }(free) 10 | } 11 | } 12 | 13 | func main() { 14 | add2 := add(2) 15 | three := add2(1) 16 | println("Pass 1 to to add2 expression to get:", three) 17 | four := add2(2) 18 | println("Pass 2 to to add2 expression to get:", four) 19 | } 20 | 21 | 22 | /* 23 | 24 | var n = 10 25 | fmt.Println(n == func(z int) int { return n }(123)) 26 | fmt.Println(add(1)(10)) 27 | 28 | 29 | */ 30 | -------------------------------------------------------------------------------- /Chapter09/misc/ex2/lambda.js: -------------------------------------------------------------------------------- 1 | var add = function (a) { 2 | return function (b) { 3 | return a + b; 4 | }; 5 | }; 6 | add2 = add(2); 7 | 8 | add2(1) == 6 9 | 10 | 11 | const add = a => b => a + b; 12 | add2 = add(2); 13 | -------------------------------------------------------------------------------- /Chapter09/misc/ex3/lambda.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // Inline definition 6 | func main() { 7 | // No inline definition 8 | fmt.Println(compose(mul3, add1)(10)) 9 | 10 | // fmt.Println(compose(mul3, func(n int) int { return n + 1 })(10)) 11 | // fmt.Println(compose(func(n int) int { return n * 3 }, func(n int) int { return n + 1 })(10)) 12 | 13 | // Replace add1 by its definition 14 | fmt.Println(compose(mul3, func(x int) fii { return func(v int) int { return func(n int) int { return n + x }(v) } }(1))(10)) 15 | 16 | // Replace mul3 by its definition 17 | fmt.Println(compose(func(n int) int { return func(z int) int { return n * 3 }(1234567) }, func(x int) fii { return func(v int) int { return func(n int) int { return n + x }(v) } }(1))(10)) 18 | 19 | // Replace compose by its definition 20 | fmt.Println(func(f, g fii) fii { return func(n int) int { return f(g(n)) } }(func(n int) int { return func(z int) int { return n * 3 }(1234567) }, func(x int) fii { return func(v int) int { return func(n int) int { return n + x }(v) } }(1))(10)) 21 | 22 | // Pure lambda expression 23 | // - no assignments 24 | } 25 | 26 | type fii func(int) int 27 | 28 | func compose(f, g fii) fii { 29 | return func(n int) int { return f(g(n)) } 30 | } 31 | 32 | var add1 = makeAdder(1) 33 | 34 | func mul3(n int) int { 35 | return func(z int) int { return n * 3 }(1234567) 36 | } 37 | 38 | func makeAdder(x int) fii { 39 | return func(v int) int { return func(n int) int { return n + x }(v) } 40 | } -------------------------------------------------------------------------------- /Chapter09/misc/ex4/typeinfer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | func main() { 8 | //a := 1.8 9 | //b := math.Floor(a + 1) 10 | //fmt.Println("b:", reflect.TypeOf(b)) 11 | 12 | a := 1 13 | b := math.Floor(a + 1.8) 14 | println(b) 15 | 16 | 17 | //a := 1.8 18 | //println(a + 1) 19 | // 20 | //a := 1 21 | //b := a + 1.8 22 | } 23 | -------------------------------------------------------------------------------- /Chapter09/wip/fpingo/config.toml: -------------------------------------------------------------------------------- 1 | #@IgnoreInspection BashAddShebang 2 | 3 | # Port that the API listens on 4 | port = "8080" 5 | 6 | # Whether to log debug output to the log (set to true for debug purposes) 7 | log_debug_info = true 8 | 9 | # Buffer size (not currently used) 10 | initial_buffer_size = 20 11 | 12 | # Maximum number of concurrent connections (not currently used) 13 | max_concurrent_connections = 4 14 | 15 | # Maximum number that user is allowed to enter 16 | max_number = 256 17 | 18 | # Use number handler (to display number, optionally with FormatNumber applied) else display files in project root 19 | use_number_handler = true 20 | -------------------------------------------------------------------------------- /Chapter09/wip/fpingo/glide.lock: -------------------------------------------------------------------------------- 1 | hash: 63ffd922d495a52ce8d0b01a9a6016e2a235e54c9d1a638c9ff0822810395969 2 | updated: 2017-08-24T14:52:13.387967712-04:00 3 | imports: 4 | - name: github.com/go-functional/core 5 | version: 2963c8ec15f6816f877d6f78bce2429bfce1c28d 6 | subpackages: 7 | - functor 8 | - typeclass 9 | - util 10 | testImports: [] 11 | -------------------------------------------------------------------------------- /Chapter09/wip/fpingo/glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: 3 | - package: github.com/go-functional/core 4 | subpackages: 5 | - functor 6 | ignore: 7 | 8 | -------------------------------------------------------------------------------- /Chapter09/wip/fpingo/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Author : Lex Sheehan 3 | # Purpose: This script initializes a go project with glide dependency management 4 | # 5 | # This script will: 6 | # 1) create a link to this project root directory in your MY_DEV_DIR directory 7 | # 2) verify that you are running the correct version of go 8 | # 3) verify that you have a toml config file (it will create one if you don't have one) 9 | # 4) verify that you have glide installed 10 | # 5) verify that you have a src directory (it will create one if you don't have one) 11 | # 6) create aliases for your convenience 12 | # 13 | # Notes: 14 | # 1) Source this file. Run it like this: source init 15 | # 2) Required dependency: https://github.com/Masterminds/glide 16 | # 3) Optional dependency: https://github.com/l3x/goenv 17 | # 4) Glide commands must be run real directory (not a linked one) 18 | # 19 | # License: MIT, 2017 Lex Sheehan LLC 20 | MY_DEV_DIR=~/dev 21 | CURRENT_GO_VERSION=1.9 22 | USES_TOML_CONFIG_YN=no 23 | # --------------------------------------------------------------- 24 | # Verify variables above are correct. Do not modify lines below. 25 | if [ -L "$(pwd)" ]; then 26 | echo "You must be in the real project directory to run this init script. You are currently in a linked directory" 27 | echo "Running: ln -l \"$(pwd)\"" 28 | ls -l "$(pwd)" 29 | return 30 | fi 31 | CURRENT_GOVERSION="go$CURRENT_GO_VERSION" 32 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 33 | DEV_DIR="$MY_DEV_DIR/$(basename $DIR)" 34 | set -x 35 | PROJECT_DIR_LINK="$MY_DEV_DIR/$(basename $DIR)" 36 | { set +x; } 2>/dev/null 37 | if [ -L "$PROJECT_DIR_LINK" ]; then 38 | rm "$PROJECT_DIR_LINK" 39 | fi 40 | if [ ! -d "$MY_DEV_DIR" ]; then 41 | mkdir "$MY_DEV_DIR" 42 | fi 43 | # Create link to project directory in MY_DEV_DIR 44 | set -x 45 | ln -s "$DIR" "$PROJECT_DIR_LINK" 46 | { set +x; } 2>/dev/null 47 | cd "$PROJECT_DIR_LINK" 48 | export GOPATH=$DIR 49 | APP_NAME=$(basename $(pwd)) 50 | GOVERSION=$(go version) 51 | echo "Installed Go version: $GOVERSION" 52 | if [[ $(type goenv) ]]; then 53 | # Attempt to automatically set desired/current go version. This requires goenv. 54 | . goenv "$CURRENT_GO_VERSION" 55 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 56 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 57 | return 58 | fi 59 | else 60 | if [ -z "$GOVERSION" ] || [[ "$(echo $GOVERSION | awk '{print $3}')" != "$CURRENT_GOVERSION" ]]; then 61 | echo "Expected Go version $CURRENT_GOVERSION to be installed" 62 | return 63 | fi 64 | fi 65 | command -v glide >/dev/null 2>&1 || { echo >&2 "Missing glide. For details, see: https://github.com/Masterminds/glide"; return; } 66 | if [ ! -e ./src ]; then 67 | mkdir src 68 | fi 69 | if [ "${PATH/$GOBIN}" == "$PATH" ] ; then 70 | export PATH=$PATH:$GOBIN 71 | fi 72 | if [[ "$USES_TOML_CONFIG_YN" =~ ^(yes|y)$ ]] && [ ! -e ./config.toml ]; then 73 | echo You were missing the config.toml configuration file... Creating bare config.toml file ... 74 | echo -e "# Runtime environment\napp_env = \"development\"" > config.toml 75 | ls -l config.toml 76 | alias go-run="go install && $APP_NAME -config ./config.toml" 77 | else 78 | alias go-run="go install && $APP_NAME" 79 | fi 80 | alias glide-ignore-project-dirs="printf \"ignore:\n$(find ./src -maxdepth 1 -type d | tail -n +2 | sed 's|./src\/||' | sed -e 's/^/- \.\//')\n\"" 81 | alias mvglide='mkdir -p vendors && mv vendor/ vendors/src/ && export GOPATH=$(pwd):$(pwd)/vendors;echo "vendor packages have been moved to $(pwd)/vendors and your GOPATH: $GOPATH"' 82 | alias glide-update='if [ ! -z $(readlink `pwd`) ]; then export LINKED=true && pushd "$(readlink `pwd`)"; fi;rm -rf {vendor,vendors};rm glide.*;export GOPATH=$(pwd):$(pwd)/vendors && export GOBIN=$(pwd)/bin && glide init --non-interactive && glide-ignore-project-dirs >> glide.yaml && glide up && mvglide && if [ $LINKED==true ]; then popd;fi' 83 | alias prune-project='(rm -rf bin pkg vendors;rm glide.lock) 2>/dev/null' 84 | alias tree2='tree -C -d -L 2' 85 | alias find-imports='find . -type f -name "*.go" -exec grep -A3 "import" {} \; -exec echo {} \; -exec echo --- \;' 86 | echo You should only need to run this init script once. 87 | echo Add Go source code files under the src directory. 88 | echo After updating dependencies, i.e., adding a new import statement, run: glide-update 89 | echo To build and run your app, run: go-run 90 | -------------------------------------------------------------------------------- /Chapter09/wip/fpingo/lambda.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import fmt "fmt" 4 | 5 | type Stringy func() string 6 | 7 | func foo() string{ 8 | return "Stringy function" 9 | } 10 | 11 | func addTwo(x int) int { 12 | return x + 2 13 | } 14 | 15 | func takesAFunction(foo Stringy){ 16 | fmt.Printf("takesAFunction: %v\n", foo()) 17 | } 18 | 19 | func returnsAFunction()Stringy{ 20 | return func()string{ 21 | fmt.Printf("Inner stringy function\n"); 22 | return "bar" // have to return a string to be stringy 23 | } 24 | } 25 | 26 | func main(){ 27 | takesAFunction(foo); 28 | var f Stringy = returnsAFunction(); 29 | f(); 30 | var baz Stringy = func()string{ 31 | return "anonymous stringy\n" 32 | }; 33 | fmt.Printf(baz()); 34 | 35 | // f(x) = y 36 | // 37 | // https://imgur.com/r/linguistics/XBHub 38 | // λx.x 39 | // λx.x+2 40 | // λx.x+2(5) 41 | println(addTwo(5)) 42 | println(func(x int) int { return x + 2 }(5)) 43 | } -------------------------------------------------------------------------------- /Chapter09/wip/fpingo/simple-functor.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/go-functional/core/functor" 7 | "fmt" 8 | ) 9 | 10 | func main() { 11 | intSlice := []int{1, 2, 3, 4} 12 | log.Printf("lifing this int slice into a functor: %#v", intSlice) 13 | // here's where we turn an int slice into a functor. in FP terms, this is called "lifting" 14 | functor := functor.LiftIntSlice(intSlice) 15 | log.Printf("original functor: %s", functor) 16 | 17 | // now we map over the functor to get a new functor with a new int slice in it. 18 | 19 | // this is the mapper function that we will pass to the Map method 20 | mapperFunc := func(i int) int { 21 | // note that this func needs to be pure! 22 | return i + 10 23 | } 24 | 25 | // The Map func isn't a Go map! Map is a method on all functors that executes mapperFunc 26 | // on every item in the slice it contains 27 | mapped := functor.Map(mapperFunc) 28 | log.Printf("mapped functor: %s", mapped) 29 | 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Learning Functional Programming in Go 5 | This is the code repository for [Learning Functional Programming in Go](https://www.packtpub.com/application-development/learning-functional-programming-go?utm_source=github&utm_medium=repository&utm_campaign=9781787281394), published by [Packt](https://www.packtpub.com/?utm_source=github). It contains all the supporting project files necessary to work through the book from start to finish. 6 | ## About the Book 7 | This book bridges the language gap for Golang developers by showing you how to create and consume functional constructs in Golang. 8 | ## Instructions and Navigation 9 | All of the code is organized into folders. Each folder starts with a number followed by the application name. For example, Chapter02. 10 | 11 | When I think of other things to add, I'll put that information here: https:/ 12 | / lexsheehan. blogspot. com/ 2017/ 11/ what- you- need- for- this- book. 13 | html. 14 | 15 | The code will look like the following: 16 | ``` 17 | func newSlice(s []string) *Collection { 18 | return &Collection{INVALID_INT_VAL, s} 19 | } 20 | ``` 21 | 22 | If you want to run the Go projects discussed in each chapter, you need to install Go. If 23 | you're on a Mac, visit here. Next, you need to get your Go development environment 24 | running and start writing code. 25 | Read the TL;DR subsection of the How to build and run Go projects section of the Appendix. 26 | Go to Chapter 1, Pure Functional Programming in Go in the book and start reading 27 | the Getting the source code section. Continue reading on how to set up and run your first 28 | project. 29 | Other Go resources include: 30 | Tour of Go (https:/ / tour. golang. org/ welcome/ 1) 31 | Go by Example (https:/ / gobyexample. com/ ) 32 | Learning Go book (https:/ / www. miek. nl/ go/ ) 33 | Go language specification (https:/ / golang. org/ ref/ spec) 34 | 35 | ## Related Products 36 | * [The Complete Google Go Programming Course For Beginners [Video]](https://www.packtpub.com/application-development/complete-google-go-programming-course-beginners-video?utm_source=github&utm_medium=repository&utm_campaign=9781788626972) 37 | 38 | * [Machine Learning With Go](https://www.packtpub.com/big-data-and-business-intelligence/machine-learning-go?utm_source=github&utm_medium=repository&utm_campaign=9781785882104) 39 | 40 | * [Go Systems Programming](https://www.packtpub.com/networking-and-servers/go-systems-programming?utm_source=github&utm_medium=repository&utm_campaign=9781787125643) 41 | 42 | ### Download a free PDF 43 | 44 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
45 |

https://packt.link/free-ebook/9781787281394

-------------------------------------------------------------------------------- /fp-go-master.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learning-Functional-Programming-in-Go/8b849f6d58c4e7b6285516db8018d5c2f49c7ee8/fp-go-master.zip --------------------------------------------------------------------------------