├── .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 |