Initial Commit

This commit is contained in:
Mischa Taylor 2013-06-08 15:17:03 -07:00
commit bbb948328e
120 changed files with 14088 additions and 0 deletions
LICENSE-2.0.txtREADME.txtbuild.shgopath.sh
src
americanise
apachereport1
apachereport2
apachereport3
apachereport4
apachereport5
archive_file_list
archive_file_list_ans
bigdigits
bigdigits_ans
cgrep1
cgrep2
cgrep3
chap4_ans
common_prefix
contains
filter
findduplicates
font
fuzzy
fuzzy_immutable
fuzzy_mutable
fuzzy_value
guess_separator
hello
imagetag1
imagetag2
indent_sort
invoicedata
invoicedata_ans
linkcheck
m3u2pls
memoize
ordered_map
oslice
pack
palindrome
palindrome_ans
pi_by_digits
playlist
polar2cartesian
qtrac.eu/omap
quadratic_ans1
quadratic_ans2
safemap
safeslice
shaper1
shaper2
shaper3
shaper_ans1
shaper_ans2

202
LICENSE-2.0.txt Normal file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

132
README.txt Normal file

@ -0,0 +1,132 @@
Programming in Go by Mark Summerfield
ISBN: 0321774639
Copyright © 2011-12 Qtrac Ltd.
All the programs, packages, and associated files in this archive are
licensed under the Apache License, Version 2.0 (the "License"); you may
not use these files except in compliance with the License. You can get a
copy of the License at: http://www.apache.org/licenses/LICENSE-2.0. (The
License is also included in this archive in file LICENSE-2.0.txt.)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
All the book's examples are designed to be educational, and many are
also designed to be useful. I hope that you find them helpful, and are
perhaps able to use some of them as starting points for your own
projects.
On Unix-like systems (e.g., Linux, FreeBSD, Mac OS X), once you have
installed Go, you can build all the examples in one go by executing:
$ cd $HOME/goeg
$ ./build.sh
On Windows it works similarly:
C:\>cd goeg
C:\goeg>build.bat
The build.sh (Unix) or build.bat (Windows) script sets GOPATH
temporarily just for the build and uses the go command (go build); both
assume that the go command (i.e., Go's bin directory) is in the PATH
which it will be if you installed a binary version.
If you want to build the examples individually and build your own Go
programs you will need to set GOPATH. This can be done temporarily by
running the accompanying gopath.sh (Unix) or gopath.bat (Windows) script
(after editing to change any paths to match your setup), or permanently
by adding the export lines from gopath.sh to your .bashrc file or on
Windows by creating a Go-specific console shortcut: see
gopath.sh or gopath.bat for more information.
Here is the list of programs and packages referred to in the book
grouped by chapter:
Chapter 1: An Overview in Five Examples
hello
bigdigits
stack
americanize
polar2cartesian
bigdigits_ans
Chapter 2: Identifiers, Booleans, and Numbers
pi_by_digits
statistics
statistics_ans
quadratic_ans1
quadratic_ans2
Chapter 3: Strings
m3u2pls
playlist
soundex
Chapter 4: Collection Types
guess_separator
wordfrequency
chap4_ans
Chapter 5: Procedural Programming
archive_file_list
archive_file_list_ans
statistics_nonstop
statistics_nonstop2
contains
palindrome
palindrome_ans
memoize
indent_sort
common_prefix
Chapter 6: Object-Oriented Programming
fuzzy
fuzzy_immutable
fuzzy_mutable
fuzzy_value
shaper1
shaper2
shaper3
ordered_map
qtrac.eu/omap
font
shaper_ans1
shaper_ans2
shaper_ans3
Chapter 7: Concurrent Programming
filter
cgrep1
cgrep2
cgrep3
safemap
apachereport1
apachereport2
apachereport3
findduplicates
safeslice
apachereport4
[apachereport5 added to examples after publication; see errata]
imagetag1
imagetag2
sizeimages1
sizeimages2
Chapter 8: File Handling
invoicedata
pack
unpack
unpack_ans
utf16-to-utf8
invoicedata_ans
Chapter 9: Packages
qtrac.eu/omap
cgrep3
linkcheck

74
build.sh Executable file

@ -0,0 +1,74 @@
#!/bin/sh
export GOPATH=`pwd`
cd src
DIRS="americanise
apachereport1
apachereport2
apachereport3
apachereport4
apachereport5
archive_file_list
archive_file_list_ans
bigdigits
bigdigits_ans
cgrep1
cgrep2
cgrep3
chap4_ans
common_prefix
contains
filter
findduplicates
font
fuzzy
fuzzy_immutable
fuzzy_mutable
fuzzy_value
guess_separator
hello
imagetag1
imagetag2
indent_sort
invoicedata
invoicedata_ans
linkcheck
m3u2pls
memoize
ordered_map
pack
palindrome
palindrome_ans
pi_by_digits
playlist
polar2cartesian
quadratic_ans1
quadratic_ans2
safemap
safeslice
shaper1
shaper2
shaper3
shaper_ans1
shaper_ans2
shaper_ans3
sizeimages1
sizeimages2
soundex
stacker
statistics
statistics_ans
statistics_nonstop
statistics_nonstop2
wordfrequency
unpack
unpack_ans
utf16-to-utf8"
for dir in $DIRS
do
cd $dir
echo building $dir...
rm -f $dir
go build
cd ..
done

20
gopath.sh Executable file

@ -0,0 +1,20 @@
#!/bin/sh
# Programming in Go by Mark Summerfield ISBN: 0321774639
# Execute this shell script in a console before using the Go tools.
# Or, if you want to be able to use the tools at anytime, copy the
# uncommented export lines into your .bashrc file.
# Uncomment the following two exports if you installed and built from
# source instead of installing a binary package, and change the path if
# necessary.
#export GOROOT=/usr/local/go
#export PATH=$PATH:$GOROOT/bin
# Tell your shell where to find the Go book's examples ($HOME/goeg) and
# your own code ($HOME/app/go); change the paths if necessary. If you
# don't need the book's examples anymore just change it to have a single
# path. Important: wherever you put your own Go programs must have a src
# directory, e.g., $HOME/app/go/src, with your programs and packages
# inside it, e.g., $HOME/app/go/src/myapp.
export GOPATH=$HOME/app/go:$HOME/goeg

@ -0,0 +1,133 @@
// Copyright © 2010-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strings"
)
var britishAmerican = "british-american.txt"
func init() {
dir, _ := filepath.Split(os.Args[0])
britishAmerican = filepath.Join(dir, britishAmerican)
}
func main() {
inFilename, outFilename, err := filenamesFromCommandLine()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
inFile, outFile := os.Stdin, os.Stdout
if inFilename != "" {
if inFile, err = os.Open(inFilename); err != nil {
log.Fatal(err)
}
defer inFile.Close()
}
if outFilename != "" {
if outFile, err = os.Create(outFilename); err != nil {
log.Fatal(err)
}
defer outFile.Close()
}
if err = americanise(inFile, outFile); err != nil {
log.Fatal(err)
}
}
func filenamesFromCommandLine() (inFilename, outFilename string,
err error) {
if len(os.Args) > 1 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
err = fmt.Errorf("usage: %s [<]infile.txt [>]outfile.txt",
filepath.Base(os.Args[0]))
return "", "", err
}
if len(os.Args) > 1 {
inFilename = os.Args[1]
if len(os.Args) > 2 {
outFilename = os.Args[2]
}
}
if inFilename != "" && inFilename == outFilename {
log.Fatal("won't overwrite the infile")
}
return inFilename, outFilename, nil
}
func americanise(inFile io.Reader, outFile io.Writer) (err error) {
reader := bufio.NewReader(inFile)
writer := bufio.NewWriter(outFile)
defer func() {
if err == nil {
err = writer.Flush()
}
}()
var replacer func(string) string
if replacer, err = makeReplacerFunction(britishAmerican); err != nil {
return err
}
wordRx := regexp.MustCompile("[A-Za-z]+")
eof := false
for !eof {
var line string
line, err = reader.ReadString('\n')
if err == io.EOF {
err = nil // io.EOF isn't really an error
eof = true // this will end the loop at the next iteration
} else if err != nil {
return err // finish immediately for real errors
}
line = wordRx.ReplaceAllStringFunc(line, replacer)
if _, err = writer.WriteString(line); err != nil {
return err
}
}
return nil
}
func makeReplacerFunction(file string) (func(string) string, error) {
rawBytes, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
text := string(rawBytes)
usForBritish := make(map[string]string)
lines := strings.Split(text, "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) == 2 {
usForBritish[fields[0]] = fields[1]
}
}
return func(word string) string {
if usWord, found := usForBritish[word]; found {
return usWord
}
return word
}, nil
}

@ -0,0 +1,70 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bytes"
"io/ioutil"
"log"
"os"
"path/filepath"
"testing"
)
const (
inFilename = "input.txt"
expectedFilename = "expected.txt"
actualFilename = "actual.txt"
)
func TestAmericanize(t *testing.T) {
log.SetFlags(0)
log.Println("TEST americanize")
path, _ := filepath.Split(os.Args[0])
var inFile, outFile *os.File
var err error
inFilename := filepath.Join(path, inFilename)
if inFile, err = os.Open(inFilename); err != nil {
t.Fatal(err)
}
defer inFile.Close()
outFilename := filepath.Join(path, actualFilename)
if outFile, err = os.Create(outFilename); err != nil {
t.Fatal(err)
}
defer outFile.Close()
defer os.Remove(outFilename)
if err := americanise(inFile, outFile); err != nil {
t.Fatal(err)
}
compare(outFilename, filepath.Join(path, expectedFilename), t)
}
func compare(actual, expected string, t *testing.T) {
if actualBytes, err := ioutil.ReadFile(actual); err != nil {
t.Fatal(err)
} else if expectedBytes, err := ioutil.ReadFile(expected); err != nil {
t.Fatal(err)
} else {
if bytes.Compare(actualBytes, expectedBytes) != 0 {
t.Fatal("actual != expected")
}
}
}

@ -0,0 +1,62 @@
behaviour behavior
colour color
favourite favorite
humour humor
labour labor
neighbour neighbor
centre center
fibre fiber
theatre theater
litre liter
metre meter
analogue analog
catalogue catalog
dialogue dialog
anaemia anemia
anaesthesia anesthesia
diarrhoea diarrhea
foetus fetus
haemorrhage hemorrhage
defence defense
offence offense
pretence pretense
aluminium aluminum
cheque check
jewellery jewelry
kerb curb
manoeuvre maneuver
mould mold
plough plow
tyre tire
sulphur sulfur
Behaviour Behavior
Colour Color
Favourite Favorite
Humour Humor
Labour Labor
Neighbour Neighbor
Centre Center
Fibre Fiber
Theatre Theater
Litre Liter
Metre Meter
Analogue Analog
Catalogue Catalog
Dialogue Dialog
Anaemia Anemia
Anaesthesia Anesthesia
Diarrhoea Diarrhea
Foetus Fetus
Haemorrhage Hemorrhage
Defence Defense
Offence Offense
Pretence Pretense
Aluminium Aluminum
Cheque Check
Jewellery Jewelry
Kerb Curb
Manoeuvre Maneuver
Mould Mold
Plough Plow
Tyre Tire
Sulphur Sulfur

@ -0,0 +1,14 @@
This is just some test text. It contains some of the British words that
have different spellings in American English, such as behavior, color,
favorite, labor, and neighbor.
In fact the differences between British and American spellings aren't
that great. Some British words that end with "our" get "or" endings in
American English, and similarly for "re" to "er" (for example "Center"
becomes "Center"), and "ogue" becomes "og" as with dialog and
catalog.
There are also a few more obscure changes, such as "ence" to "ense", for
example "defense" becomes "defense". And there are a few miscellaneous
differences, such as for anemia, hemorrhage, aluminum, check, curb,
tire, sulfur, and maneuver.

14
src/americanise/input.txt Normal file

@ -0,0 +1,14 @@
This is just some test text. It contains some of the British words that
have different spellings in American English, such as behaviour, colour,
favourite, labour, and neighbour.
In fact the differences between British and American spellings aren't
that great. Some British words that end with "our" get "or" endings in
American English, and similarly for "re" to "er" (for example "Centre"
becomes "Center"), and "ogue" becomes "og" as with dialogue and
catalogue.
There are also a few more obscure changes, such as "ence" to "ense", for
example "defence" becomes "defense". And there are a few miscellaneous
differences, such as for anaemia, haemorrhage, aluminium, cheque, kerb,
tyre, sulphur, and manoeuvre.

@ -0,0 +1,100 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"safemap"
)
var workers = runtime.NumCPU()
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // Use all the machine's cores
if len(os.Args) != 2 || os.Args[1] == "-h" || os.Args[1] == "--help" {
fmt.Printf("usage: %s <file.log>\n", filepath.Base(os.Args[0]))
os.Exit(1)
}
lines := make(chan string, workers*4)
done := make(chan struct{}, workers)
pageMap := safemap.New()
go readLines(os.Args[1], lines)
processLines(done, pageMap, lines)
waitUntil(done)
showResults(pageMap)
}
func readLines(filename string, lines chan<- string) {
file, err := os.Open(filename)
if err != nil {
log.Fatal("failed to open the file:", err)
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if line != "" {
lines <- line
}
if err != nil {
if err != io.EOF {
log.Println("failed to finish reading the file:", err)
}
break
}
}
close(lines)
}
func processLines(done chan<- struct{}, pageMap safemap.SafeMap,
lines <-chan string) {
getRx := regexp.MustCompile(`GET[ \t]+([^ \t\n]+[.]html?)`)
incrementer := func(value interface{}, found bool) interface{} {
if found {
return value.(int) + 1
}
return 1
}
for i := 0; i < workers; i++ {
go func() {
for line := range lines {
if matches := getRx.FindStringSubmatch(line);
matches != nil {
pageMap.Update(matches[1], incrementer)
}
}
done <- struct{}{}
}()
}
}
func waitUntil(done <-chan struct{}) {
for i := 0; i < workers; i++ {
<-done
}
}
func showResults(pageMap safemap.SafeMap) {
pages := pageMap.Close()
for page, count := range pages {
fmt.Printf("%8d %s\n", count, page)
}
}

@ -0,0 +1,112 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"sync"
)
var workers = runtime.NumCPU()
type pageMap struct {
countForPage map[string]int
mutex *sync.RWMutex
}
func NewPageMap() *pageMap {
return &pageMap{make(map[string]int), new(sync.RWMutex)}
}
func (pm *pageMap) Increment(page string) {
pm.mutex.Lock()
defer pm.mutex.Unlock()
pm.countForPage[page]++
}
func (pm *pageMap) Len() int {
pm.mutex.RLock()
defer pm.mutex.RUnlock()
return len(pm.countForPage)
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // Use all the machine's cores
if len(os.Args) != 2 || os.Args[1] == "-h" || os.Args[1] == "--help" {
fmt.Printf("usage: %s <file.log>\n", filepath.Base(os.Args[0]))
os.Exit(1)
}
lines := make(chan string, workers*4)
done := make(chan struct{}, workers)
pageMap := NewPageMap()
go readLines(os.Args[1], lines)
getRx := regexp.MustCompile(`GET[ \t]+([^ \t\n]+[.]html?)`)
for i := 0; i < workers; i++ {
go processLines(done, getRx, pageMap, lines)
}
waitUntil(done)
showResults(pageMap)
}
func readLines(filename string, lines chan<- string) {
file, err := os.Open(filename)
if err != nil {
log.Fatal("failed to open the file:", err)
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if line != "" {
lines <- line
}
if err != nil {
if err != io.EOF {
log.Println("failed to finish reading the file:", err)
}
break
}
}
close(lines)
}
func processLines(done chan<- struct{}, getRx *regexp.Regexp,
pageMap *pageMap, lines <-chan string) {
for line := range lines {
if matches := getRx.FindStringSubmatch(line); matches != nil {
pageMap.Increment(matches[1])
}
}
done <- struct{}{}
}
func waitUntil(done <-chan struct{}) {
for i := 0; i < workers; i++ {
<-done
}
}
func showResults(pageMap *pageMap) {
// No lock, accesses in only one goroutine
for page, count := range pageMap.countForPage {
fmt.Printf("%8d %s\n", count, page)
}
}

@ -0,0 +1,93 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
)
var workers = runtime.NumCPU()
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // Use all the machine's cores
if len(os.Args) != 2 || os.Args[1] == "-h" || os.Args[1] == "--help" {
fmt.Printf("usage: %s <file.log>\n", filepath.Base(os.Args[0]))
os.Exit(1)
}
lines := make(chan string, workers*4)
results := make(chan map[string]int, workers)
go readLines(os.Args[1], lines)
getRx := regexp.MustCompile(`GET[ \t]+([^ \t\n]+[.]html?)`)
for i := 0; i < workers; i++ {
go processLines(results, getRx, lines)
}
totalForPage := make(map[string]int)
merge(results, totalForPage)
showResults(totalForPage)
}
func readLines(filename string, lines chan<- string) {
file, err := os.Open(filename)
if err != nil {
log.Fatal("failed to open the file:", err)
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if line != "" {
lines <- line
}
if err != nil {
if err != io.EOF {
log.Println("failed to finish reading the file:", err)
}
break
}
}
close(lines)
}
func processLines(results chan<- map[string]int, getRx *regexp.Regexp,
lines <-chan string) {
countForPage := make(map[string]int)
for line := range lines {
if matches := getRx.FindStringSubmatch(line); matches != nil {
countForPage[matches[1]]++
}
}
results <- countForPage
}
func merge(results <-chan map[string]int, totalForPage map[string]int) {
for i := 0; i < workers; i++ {
countForPage := <-results
for page, count := range countForPage {
totalForPage[page] += count
}
}
}
func showResults(totalForPage map[string]int) {
for page, count := range totalForPage {
fmt.Printf("%8d %s\n", count, page)
}
}

@ -0,0 +1,96 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"safeslice"
)
var workers = runtime.NumCPU()
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // Use all the machine's cores
if len(os.Args) != 2 || os.Args[1] == "-h" || os.Args[1] == "--help" {
fmt.Printf("usage: %s <file.log>\n", filepath.Base(os.Args[0]))
os.Exit(1)
}
lines := make(chan string)
done := make(chan struct{}, workers)
pageList := safeslice.New()
go readLines(os.Args[1], lines)
getRx := regexp.MustCompile(`GET[ \t]+([^ \t\n]+[.]html?)`)
for i := 0; i < workers; i++ {
go processLines(done, getRx, pageList, lines)
}
waitUntil(done)
showResults(pageList)
}
func readLines(filename string, lines chan<- string) {
var file *os.File
var err error
if file, err = os.Open(filename); err != nil {
log.Fatal("failed to open the file:", err)
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if line != "" {
lines <- line
}
if err != nil {
if err != io.EOF {
log.Println("failed to finish reading the file:", err)
}
break
}
}
close(lines)
}
func processLines(done chan<- struct{}, getRx *regexp.Regexp,
pageList safeslice.SafeSlice, lines <-chan string) {
for line := range lines {
if matches := getRx.FindStringSubmatch(line); matches != nil {
pageList.Append(matches[1])
}
}
done <- struct{}{}
}
func waitUntil(done <-chan struct{}) {
for i := 0; i < workers; i++ {
<-done
}
}
func showResults(pageList safeslice.SafeSlice) {
list := pageList.Close()
counts := make(map[string]int)
for _, page := range list { // uniquify
counts[page.(string)] += 1
}
for page, count := range counts {
fmt.Printf("%8d %s\n", count, page)
}
}

@ -0,0 +1,104 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
)
var workers = runtime.NumCPU()
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // Use all the machine's cores
if len(os.Args) != 2 || os.Args[1] == "-h" || os.Args[1] == "--help" {
fmt.Printf("usage: %s <file.log>\n", filepath.Base(os.Args[0]))
os.Exit(1)
}
filename := os.Args[1]
info, err := os.Stat(filename)
if err != nil {
log.Fatal("failed to open the file:", err)
}
chunkSize := info.Size() / int64(workers)
results := make(chan map[string]int, workers)
getRx := regexp.MustCompile(`GET[ \t]+([^ \t\n]+[.]html?)`)
for i := 0; i < workers; i++ {
offset := int64(i) * chunkSize
if i + 1 == workers {
chunkSize *= 2 // Make sure we read all of the last chunk
}
go processLines(results, getRx, filename, offset, chunkSize)
}
totalForPage := make(map[string]int)
merge(results, totalForPage)
showResults(totalForPage)
}
func processLines(results chan<- map[string]int, getRx *regexp.Regexp,
filename string, offset, chunkSize int64) {
file, err := os.Open(filename)
if err != nil {
log.Fatal("failed to open the file:", err)
}
defer file.Close()
file.Seek(offset, 0)
var bytesRead int64
reader := bufio.NewReader(file)
if (offset > 0) { // Find first whole line
line, err := reader.ReadString('\n')
if err != nil {
log.Fatal("failed to read the file:", err)
}
bytesRead = int64(len(line))
}
countForPage := make(map[string]int)
for bytesRead < chunkSize {
line, err := reader.ReadString('\n')
if line != "" {
bytesRead += int64(len(line))
if matches := getRx.FindStringSubmatch(line); matches != nil {
countForPage[matches[1]]++
}
}
if err != nil {
if err != io.EOF {
log.Println("failed to finish reading the file:", err)
}
break
}
}
results <- countForPage
}
func merge(results <-chan map[string]int, totalForPage map[string]int) {
for i := 0; i < workers; i++ {
countForPage := <-results
for page, count := range countForPage {
totalForPage[page] += count
}
}
}
func showResults(totalForPage map[string]int) {
for page, count := range totalForPage {
fmt.Printf("%8d %s\n", count, page)
}
}

@ -0,0 +1,219 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"
)
var FunctionForSuffix = map[string]func(string) ([]string, error){
".gz": GzipFileList, ".tar": TarFileList, ".tar.gz": TarFileList,
".tgz": TarFileList, ".zip": ZipFileList}
func main() {
if len(os.Args) == 1 || os.Args[1] == "-h" || os.Args[1] == "--help" {
fmt.Printf("usage: %s archive1 [archive2 [... archiveN]]\n",
filepath.Base(os.Args[0]))
os.Exit(1)
}
args := commandLineFiles(os.Args[1:])
archiveFileList := ArchiveFileList
if len(args[0]) == 1 && strings.IndexAny(args[0], "12345") != -1 {
which := args[0][0]
args = args[1:]
switch which {
case '2':
archiveFileList = ArchiveFileList2
case '3':
archiveFileList = ArchiveFileList3
case '4':
archiveFileList = ArchiveFileList4
case '5':
archiveFileList = ArchiveFileListMap
}
}
for _, filename := range args {
fmt.Print(filename)
lines, err := archiveFileList(filename)
if err != nil {
fmt.Println(" ERROR:", err)
} else {
fmt.Println()
for _, line := range lines {
fmt.Println(" ", line)
}
}
}
}
func commandLineFiles(files []string) []string {
if runtime.GOOS == "windows" {
args := make([]string, 0, len(files))
for _, name := range files {
if matches, err := filepath.Glob(name); err != nil {
args = append(args, name) // Invalid pattern
} else if matches != nil { // At least one match
args = append(args, matches...)
}
}
return args
}
return files
}
func ArchiveFileList(file string) ([]string, error) {
if suffix := Suffix(file); suffix == ".gz" {
return GzipFileList(file)
} else if suffix == ".tar" || suffix == ".tar.gz" || suffix == ".tgz" {
return TarFileList(file)
} else if suffix == ".zip" {
return ZipFileList(file)
}
return nil, errors.New("unrecognized archive")
}
func ArchiveFileList2(file string) ([]string, error) {
switch suffix := Suffix(file); suffix { // Naïve and noncanonical!
case ".gz":
return GzipFileList(file)
case ".tar":
fallthrough
case ".tar.gz":
fallthrough
case ".tgz":
return TarFileList(file)
case ".zip":
return ZipFileList(file)
}
return nil, errors.New("unrecognized archive")
}
func ArchiveFileList3(file string) ([]string, error) {
switch Suffix(file) {
case ".gz":
return GzipFileList(file)
case ".tar":
fallthrough
case ".tar.gz":
fallthrough
case ".tgz":
return TarFileList(file)
case ".zip":
return ZipFileList(file)
}
return nil, errors.New("unrecognized archive")
}
func ArchiveFileList4(file string) ([]string, error) {
switch Suffix(file) { // Canonical
case ".gz":
return GzipFileList(file)
case ".tar", ".tar.gz", ".tgz":
return TarFileList(file)
case ".zip":
return ZipFileList(file)
}
return nil, errors.New("unrecognized archive")
}
func ArchiveFileListMap(file string) ([]string, error) {
if function, ok := FunctionForSuffix[Suffix(file)]; ok {
return function(file)
}
return nil, errors.New("unrecognized archive")
}
func Suffix(file string) string {
file = strings.ToLower(filepath.Base(file))
if i := strings.LastIndex(file, "."); i > -1 {
if file[i:] == ".bz2" || file[i:] == ".gz" || file[i:] == ".xz" {
if j := strings.LastIndex(file[:i], ".");
j > -1 && strings.HasPrefix(file[j:], ".tar") {
return file[j:]
}
}
return file[i:]
}
return file
}
func ZipFileList(filename string) ([]string, error) {
zipReader, err := zip.OpenReader(filename)
if err != nil {
return nil, err
}
defer zipReader.Close()
var files []string
for _, file := range zipReader.File {
files = append(files, file.Name)
}
return files, nil
}
func GzipFileList(filename string) ([]string, error) {
reader, err := os.Open(filename)
if err != nil {
return nil, err
}
defer reader.Close()
gzipReader, err := gzip.NewReader(reader)
if err != nil {
return nil, err
}
return []string{gzipReader.Header.Name}, nil
}
func TarFileList(filename string) ([]string, error) {
reader, err := os.Open(filename)
if err != nil {
return nil, err
}
defer reader.Close()
var tarReader *tar.Reader
if strings.HasSuffix(filename, ".gz") ||
strings.HasSuffix(filename, ".tgz") {
gzipReader, err := gzip.NewReader(reader)
if err != nil {
return nil, err
}
tarReader = tar.NewReader(gzipReader)
} else {
tarReader = tar.NewReader(reader)
}
var files []string
for {
header, err := tarReader.Next()
if err != nil {
if err == io.EOF {
break
}
return files, err
}
if header == nil {
break
}
files = append(files, header.Name)
}
return files, nil
}

@ -0,0 +1,153 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"archive/tar"
"archive/zip"
"compress/bzip2"
"compress/gzip"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"
)
var FunctionForSuffix = map[string]func(string) ([]string, error){
".gz": GzipFileList, ".tar": TarFileList, ".tar.gz": TarFileList,
".tar.bz2": TarFileList, ".tgz": TarFileList, ".zip": ZipFileList}
func main() {
if len(os.Args) == 1 || os.Args[1] == "-h" || os.Args[1] == "--help" {
fmt.Printf("usage: %s archive1 [archive2 [... archiveN]]\n",
filepath.Base(os.Args[0]))
os.Exit(1)
}
args := commandLineFiles(os.Args[1:])
for _, filename := range args {
fmt.Print(filename)
lines, err := ArchiveFileList(filename)
if err != nil {
fmt.Println(" ERROR:", err)
} else {
fmt.Println()
for _, line := range lines {
fmt.Println(" ", line)
}
}
}
}
func commandLineFiles(files []string) []string {
if runtime.GOOS == "windows" {
args := make([]string, 0, len(files))
for _, name := range files {
if matches, err := filepath.Glob(name); err != nil {
args = append(args, name) // Invalid pattern
} else if matches != nil { // At least one match
args = append(args, matches...)
}
}
return args
}
return files
}
func ArchiveFileList(file string) ([]string, error) {
if function, ok := FunctionForSuffix[Suffix(file)]; ok {
return function(file)
}
return nil, errors.New("unrecognized archive")
}
func Suffix(file string) string {
file = strings.ToLower(filepath.Base(file))
if i := strings.LastIndex(file, "."); i > -1 {
if file[i:] == ".bz2" || file[i:] == ".gz" || file[i:] == ".xz" {
if j := strings.LastIndex(file[:i], ".");
j > -1 && strings.HasPrefix(file[j:], ".tar") {
return file[j:]
}
}
return file[i:]
}
return file
}
func ZipFileList(filename string) ([]string, error) {
zipReader, err := zip.OpenReader(filename)
if err != nil {
return nil, err
}
defer zipReader.Close()
var files []string
for _, file := range zipReader.File {
files = append(files, file.Name)
}
return files, nil
}
func GzipFileList(filename string) ([]string, error) {
reader, err := os.Open(filename)
if err != nil {
return nil, err
}
defer reader.Close()
gzipReader, err := gzip.NewReader(reader)
if err != nil {
return nil, err
}
return []string{gzipReader.Header.Name}, nil
}
func TarFileList(filename string) ([]string, error) {
reader, err := os.Open(filename)
if err != nil {
return nil, err
}
defer reader.Close()
var tarReader *tar.Reader
if strings.HasSuffix(filename, ".gz") ||
strings.HasSuffix(filename, ".tgz") {
gzipReader, err := gzip.NewReader(reader)
if err != nil {
return nil, err
}
tarReader = tar.NewReader(gzipReader)
} else if strings.HasSuffix(filename, ".bz2") {
bz2Reader := bzip2.NewReader(reader)
tarReader = tar.NewReader(bz2Reader)
} else {
tarReader = tar.NewReader(reader)
}
var files []string
for {
header, err := tarReader.Next()
if err != nil {
if err == io.EOF {
break
}
return files, err
}
if header == nil {
break
}
files = append(files, header.Name)
}
return files, nil
}

@ -0,0 +1,7 @@
000 1 222 333 4 55555 666 77777 888 9999
0 0 11 2 2 3 3 44 5 6 7 8 8 9 9
0 0 1 2 3 4 4 5 6 7 8 8 9 9
0 0 1 2 33 4 4 555 6666 7 888 9999
0 0 1 2 3 444444 5 6 6 7 8 8 9
0 0 1 2 3 3 4 5 5 6 6 7 8 8 9
000 111 22222 333 4 555 666 7 888 9

@ -0,0 +1,62 @@
// Copyright © 2010-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
)
func main() {
if len(os.Args) == 1 {
fmt.Printf("usage: %s <whole-number>\n", filepath.Base(os.Args[0]))
os.Exit(1)
}
stringOfDigits := os.Args[1]
for row := range bigDigits[0] {
line := ""
for column := range stringOfDigits {
digit := stringOfDigits[column] - '0'
if 0 <= digit && digit <= 9 {
line += bigDigits[digit][row] + " "
} else {
log.Fatal("invalid whole number")
}
}
fmt.Println(line)
}
}
var bigDigits = [][]string{
{" 000 ",
" 0 0 ",
"0 0",
"0 0",
"0 0",
" 0 0 ",
" 000 "},
{" 1 ", "11 ", " 1 ", " 1 ", " 1 ", " 1 ", "111"},
{" 222 ", "2 2", " 2 ", " 2 ", " 2 ", "2 ", "22222"},
{" 333 ", "3 3", " 3", " 33 ", " 3", "3 3", " 333 "},
{" 4 ", " 44 ", " 4 4 ", "4 4 ", "444444", " 4 ",
" 4 "},
{"55555", "5 ", "5 ", " 555 ", " 5", "5 5", " 555 "},
{" 666 ", "6 ", "6 ", "6666 ", "6 6", "6 6", " 666 "},
{"77777", " 7", " 7 ", " 7 ", " 7 ", "7 ", "7 "},
{" 888 ", "8 8", "8 8", " 888 ", "8 8", "8 8", " 888 "},
{" 9999", "9 9", "9 9", " 9999", " 9", " 9", " 9"},
}

@ -0,0 +1,55 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bytes"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"testing"
)
func TestBigDigits(t *testing.T) {
log.SetFlags(0)
log.Println("TEST bigdigits")
path, _ := os.Getwd()
expected, err := ioutil.ReadFile(filepath.Join(path, "0123456789.txt"))
if err != nil {
t.Fatal(err)
}
executable := filepath.Join(path, "bigdigits")
reader, writer, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
command := exec.Command(executable, "0123456789")
command.Stdout = writer
err = command.Run()
if err != nil {
t.Fatal(err)
}
writer.Close()
actual, err := ioutil.ReadAll(reader)
if err != nil {
t.Fatal(err)
}
reader.Close()
if bytes.Compare(actual, expected) != 0 {
t.Fatal("actual != expected")
}
}

@ -0,0 +1,9 @@
*********************************************************************
000 1 222 333 4 55555 666 77777 888 9999
0 0 11 2 2 3 3 44 5 6 7 8 8 9 9
0 0 1 2 3 4 4 5 6 7 8 8 9 9
0 0 1 2 33 4 4 555 6666 7 888 9999
0 0 1 2 3 444444 5 6 6 7 8 8 9
0 0 1 2 3 3 4 5 5 6 6 7 8 8 9
000 111 22222 333 4 555 666 7 888 9
*********************************************************************

@ -0,0 +1,79 @@
// Copyright © 2010-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
)
func main() {
if len(os.Args) == 1 || os.Args[1] == "-h" ||
os.Args[1] == "--help" ||
(len(os.Args) == 2 && (os.Args[1] == "-b" ||
os.Args[1] == "--bar")) {
fmt.Printf("usage: %s [-b|--bar] <whole-number>\n"+
"-b --bar draw an underbar and an overbar\n",
filepath.Base(os.Args[0]))
os.Exit(1)
}
bar := false
var stringOfDigits string
if os.Args[1] == "-b" || os.Args[1] == "--bar" {
bar = true
stringOfDigits = os.Args[2]
} else {
stringOfDigits = os.Args[1]
}
for row := range bigDigits[0] {
line := ""
for column := range stringOfDigits {
digit := stringOfDigits[column] - '0'
if 0 <= digit && digit <= 9 {
line += bigDigits[digit][row]
if column+1 < len(stringOfDigits) {
line += " "
}
} else {
log.Fatal("invalid whole number")
}
}
if bar && row == 0 {
fmt.Println(strings.Repeat("*", len(line)))
}
fmt.Println(line)
if bar && row+1 == len(bigDigits[0]) {
fmt.Println(strings.Repeat("*", len(line)))
}
}
}
var bigDigits = [][]string{
{" 000 ", " 0 0 ", "0 0", "0 0", "0 0", " 0 0 ",
" 000 "},
{" 1 ", "11 ", " 1 ", " 1 ", " 1 ", " 1 ", "111"},
{" 222 ", "2 2", " 2 ", " 2 ", " 2 ", "2 ", "22222"},
{" 333 ", "3 3", " 3", " 33 ", " 3", "3 3", " 333 "},
{" 4 ", " 44 ", " 4 4 ", "4 4 ", "444444", " 4 ",
" 4 "},
{"55555", "5 ", "5 ", " 555 ", " 5", "5 5", " 555 "},
{" 666 ", "6 ", "6 ", "6666 ", "6 6", "6 6", " 666 "},
{"77777", " 7", " 7 ", " 7 ", " 7 ", "7 ", "7 "},
{" 888 ", "8 8", "8 8", " 888 ", "8 8", "8 8", " 888 "},
{" 9999", "9 9", "9 9", " 9999", " 9", " 9", " 9"},
}

@ -0,0 +1,55 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bytes"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"testing"
)
func TestBigDigits(t *testing.T) {
log.SetFlags(0)
log.Println("TEST bigdigits_ans")
path, _ := os.Getwd()
expected, err := ioutil.ReadFile(filepath.Join(path, "0123456789.txt"))
if err != nil {
t.Fatal(err)
}
executable := filepath.Join(path, "bigdigits_ans")
reader, writer, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
command := exec.Command(executable, "-b", "0123456789")
command.Stdout = writer
err = command.Run()
if err != nil {
t.Fatal(err)
}
writer.Close()
actual, err := ioutil.ReadAll(reader)
if err != nil {
t.Fatal(err)
}
reader.Close()
if bytes.Compare(actual, expected) != 0 {
t.Fatal("actual != expected")
}
}

143
src/cgrep1/cgrep.go Normal file

@ -0,0 +1,143 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The approach taken here was inspired by an example on the gonuts mailing
// list by Roger Peppe.
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
)
var workers = runtime.NumCPU()
type Result struct {
filename string
lino int
line string
}
type Job struct {
filename string
results chan<- Result
}
func (job Job) Do(lineRx *regexp.Regexp) {
file, err := os.Open(job.filename)
if err != nil {
log.Printf("error: %s\n", err)
return
}
defer file.Close()
reader := bufio.NewReader(file)
for lino := 1; ; lino++ {
line, err := reader.ReadBytes('\n')
line = bytes.TrimRight(line, "\n\r")
if lineRx.Match(line) {
job.results <- Result{job.filename, lino, string(line)}
}
if err != nil {
if err != io.EOF {
log.Printf("error:%d: %s\n", lino, err)
}
break
}
}
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // Use all the machine's cores
if len(os.Args) < 3 || os.Args[1] == "-h" || os.Args[1] == "--help" {
fmt.Printf("usage: %s <regexp> <files>\n",
filepath.Base(os.Args[0]))
os.Exit(1)
}
if lineRx, err := regexp.Compile(os.Args[1]); err != nil {
log.Fatalf("invalid regexp: %s\n", err)
} else {
grep(lineRx, commandLineFiles(os.Args[2:]))
}
}
func commandLineFiles(files []string) []string {
if runtime.GOOS == "windows" {
args := make([]string, 0, len(files))
for _, name := range files {
if matches, err := filepath.Glob(name); err != nil {
args = append(args, name) // Invalid pattern
} else if matches != nil { // At least one match
args = append(args, matches...)
}
}
return args
}
return files
}
func grep(lineRx *regexp.Regexp, filenames []string) {
jobs := make(chan Job, workers)
results := make(chan Result, minimum(1000, len(filenames)))
done := make(chan struct{}, workers)
go addJobs(jobs, filenames, results) // Executes in its own goroutine
for i := 0; i < workers; i++ {
go doJobs(done, lineRx, jobs) // Each executes in its own goroutine
}
go awaitCompletion(done, results) // Executes in its own goroutine
processResults(results) // Blocks until the work is done
}
func addJobs(jobs chan<- Job, filenames []string, results chan<- Result) {
for _, filename := range filenames {
jobs <- Job{filename, results}
}
close(jobs)
}
func doJobs(done chan<- struct{}, lineRx *regexp.Regexp, jobs <-chan Job) {
for job := range jobs {
job.Do(lineRx)
}
done <- struct{}{}
}
func awaitCompletion(done <-chan struct{}, results chan Result) {
for i := 0; i < workers; i++ {
<-done
}
close(results)
}
func processResults(results <-chan Result) {
for result := range results {
fmt.Printf("%s:%d:%s\n", result.filename, result.lino, result.line)
}
}
func minimum(x int, ys ...int) int {
for _, y := range ys {
if y < x {
x = y
}
}
return x
}

151
src/cgrep2/cgrep.go Normal file

@ -0,0 +1,151 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The approach taken here was inspired by an example on the gonuts mailing
// list by Roger Peppe.
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
)
var workers = runtime.NumCPU()
type Result struct {
filename string
lino int
line string
}
type Job struct {
filename string
results chan<- Result
}
func (job Job) Do(lineRx *regexp.Regexp) {
file, err := os.Open(job.filename)
if err != nil {
log.Printf("error: %s\n", err)
return
}
defer file.Close()
reader := bufio.NewReader(file)
for lino := 1; ; lino++ {
line, err := reader.ReadBytes('\n')
line = bytes.TrimRight(line, "\n\r")
if lineRx.Match(line) {
job.results <- Result{job.filename, lino, string(line)}
}
if err != nil {
if err != io.EOF {
log.Printf("error:%d: %s\n", lino, err)
}
break
}
}
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // Use all the machine's cores
if len(os.Args) < 3 || os.Args[1] == "-h" || os.Args[1] == "--help" {
fmt.Printf("usage: %s <regexp> <files>\n",
filepath.Base(os.Args[0]))
os.Exit(1)
}
if lineRx, err := regexp.Compile(os.Args[1]); err != nil {
log.Fatalf("invalid regexp: %s\n", err)
} else {
grep(lineRx, commandLineFiles(os.Args[2:]))
}
}
func commandLineFiles(files []string) []string {
if runtime.GOOS == "windows" {
args := make([]string, 0, len(files))
for _, name := range files {
if matches, err := filepath.Glob(name); err != nil {
args = append(args, name) // Invalid pattern
} else if matches != nil { // At least one match
args = append(args, matches...)
}
}
return args
}
return files
}
func grep(lineRx *regexp.Regexp, filenames []string) {
jobs := make(chan Job, workers)
results := make(chan Result, minimum(1000, len(filenames)))
done := make(chan struct{}, workers)
go addJobs(jobs, filenames, results)
for i := 0; i < workers; i++ {
go doJobs(done, lineRx, jobs)
}
waitAndProcessResults(done, results)
}
func addJobs(jobs chan<- Job, filenames []string, results chan<- Result) {
for _, filename := range filenames {
jobs <- Job{filename, results}
}
close(jobs)
}
func doJobs(done chan<- struct{}, lineRx *regexp.Regexp, jobs <-chan Job) {
for job := range jobs {
job.Do(lineRx)
}
done <- struct{}{}
}
func waitAndProcessResults(done <-chan struct{}, results <-chan Result) {
for working := workers; working > 0; {
select { // Blocking
case result := <-results:
fmt.Printf("%s:%d:%s\n", result.filename, result.lino,
result.line)
case <-done:
working--
}
}
DONE:
for {
select { // Nonblocking
case result := <-results:
fmt.Printf("%s:%d:%s\n", result.filename, result.lino,
result.line)
default:
break DONE
}
}
}
func minimum(x int, ys ...int) int {
for _, y := range ys {
if y < x {
x = y
}
}
return x
}

159
src/cgrep3/cgrep.go Normal file

@ -0,0 +1,159 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The approach taken here was inspired by an example on the gonuts mailing
// list by Roger Peppe.
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"io"
"log"
"os"
"regexp"
"runtime"
"time"
)
var workers = runtime.NumCPU()
type Result struct {
filename string
lino int
line string
}
type Job struct {
filename string
results chan<- Result
}
func (job Job) Do(lineRx *regexp.Regexp) {
file, err := os.Open(job.filename)
if err != nil {
log.Printf("error: %s\n", err)
return
}
defer file.Close()
reader := bufio.NewReader(file)
for lino := 1; ; lino++ {
line, err := reader.ReadBytes('\n')
line = bytes.TrimRight(line, "\n\r")
if lineRx.Match(line) {
job.results <- Result{job.filename, lino, string(line)}
}
if err != nil {
if err != io.EOF {
log.Printf("error:%d: %s\n", lino, err)
}
break
}
}
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // Use all the machine's cores
log.SetFlags(0)
var timeoutOpt *int64 = flag.Int64("timeout", 0,
"seconds (0 means no timeout)")
flag.Parse()
if *timeoutOpt < 0 || *timeoutOpt > 240 {
log.Fatalln("timeout must be in the range [0,240] seconds")
}
args := flag.Args()
if len(args) < 1 {
log.Fatalln("a regexp to match must be specified")
}
pattern := args[0]
files := args[1:]
if len(files) < 1 {
log.Fatalln("must provide at least one filename")
}
if lineRx, err := regexp.Compile(pattern); err != nil {
log.Fatalf("invalid regexp: %s\n", err)
} else {
var timeout int64 = 1e9 * 60 * 10 // 10 minutes!
if *timeoutOpt != 0 {
timeout = *timeoutOpt * 1e9
}
grep(timeout, lineRx, commandLineFiles(files))
}
}
func grep(timeout int64, lineRx *regexp.Regexp, filenames []string) {
jobs := make(chan Job, workers)
results := make(chan Result, minimum(1000, len(filenames)))
done := make(chan struct{}, workers)
go addJobs(jobs, filenames, results)
for i := 0; i < workers; i++ {
go doJobs(done, lineRx, jobs)
}
waitAndProcessResults(timeout, done, results)
}
func addJobs(jobs chan<- Job, filenames []string, results chan<- Result) {
for _, filename := range filenames {
jobs <- Job{filename, results}
}
close(jobs)
}
func doJobs(done chan<- struct{}, lineRx *regexp.Regexp, jobs <-chan Job) {
for job := range jobs {
job.Do(lineRx)
}
done <- struct{}{}
}
func waitAndProcessResults(timeout int64, done <-chan struct{},
results <-chan Result) {
finish := time.After(time.Duration(timeout))
for working := workers; working > 0; {
select { // Blocking
case result := <-results:
fmt.Printf("%s:%d:%s\n", result.filename, result.lino,
result.line)
case <-finish:
fmt.Println("timed out")
return // Time's up so finish with what results there were
case <-done:
working--
}
}
for {
select { // Nonblocking
case result := <-results:
fmt.Printf("%s:%d:%s\n", result.filename, result.lino,
result.line)
case <-finish:
fmt.Println("timed out")
return // Time's up so finish with what results there were
default:
return
}
}
}
func minimum(x int, ys ...int) int {
for _, y := range ys {
if y < x {
x = y
}
}
return x
}

16
src/cgrep3/util_darwin.go Normal file

@ -0,0 +1,16 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
func commandLineFiles(files []string) []string { return files }

@ -0,0 +1,16 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
func commandLineFiles(files []string) []string { return files }

16
src/cgrep3/util_linux.go Normal file

@ -0,0 +1,16 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
func commandLineFiles(files []string) []string { return files }

@ -0,0 +1,28 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import "path/filepath"
func commandLineFiles(files []string) []string {
args := make([]string, 0, len(files))
for _, name := range files {
if matches, err := filepath.Glob(name); err != nil {
args = append(args, name) // Invalid pattern
} else if matches != nil { // At least one match
args = append(args, matches...)
}
}
return args
}

151
src/chap4_ans/chap4_ans.go Normal file

@ -0,0 +1,151 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"log"
"sort"
"strings"
)
func main() {
irregularMatrix := [][]int{{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11},
{12, 13, 14, 15},
{16, 17, 18, 19, 20}}
fmt.Println("irregular:", irregularMatrix)
slice := Flatten(irregularMatrix)
fmt.Printf("1x%d: %v\n", len(slice), slice)
fmt.Printf(" 3x%d: %v\n", neededRows(slice, 3), Make2D(slice, 3))
fmt.Printf(" 4x%d: %v\n", neededRows(slice, 4), Make2D(slice, 4))
fmt.Printf(" 5x%d: %v\n", neededRows(slice, 5), Make2D(slice, 5))
fmt.Printf(" 6x%d: %v\n", neededRows(slice, 6), Make2D(slice, 6))
slice = []int{9, 1, 9, 5, 4, 4, 2, 1, 5, 4, 8, 8, 4, 3, 6, 9, 5, 7, 5}
fmt.Println("Original:", slice)
slice = UniqueInts(slice)
fmt.Println("Unique: ", slice)
iniData := []string{
"; Cut down copy of Mozilla application.ini file",
"",
"[App]",
"Vendor=Mozilla",
"Name=Iceweasel",
"Profile=mozilla/firefox",
"Version=3.5.16",
"[Gecko]",
"MinVersion=1.9.1",
"MaxVersion=1.9.1.*",
"[XRE]",
"EnableProfileMigrator=0",
"EnableExtensionManager=1",
}
ini := ParseIni(iniData)
PrintIni(ini)
}
// Minimum result length is len(matrix) + len(matrix[0]); by using append()
// we can cope with irregular matrices whose inner slices are of different
// lengths.
func Flatten(matrix [][]int) []int {
slice := make([]int, 0, len(matrix)+len(matrix[0]))
for _, innerSlice := range matrix {
for _, x := range innerSlice {
slice = append(slice, x)
}
}
return slice
}
func Make2D(slice []int, columns int) [][]int {
matrix := make([][]int, neededRows(slice, columns))
for i, x := range slice {
row := i / columns
column := i % columns
if matrix[row] == nil {
matrix[row] = make([]int, columns)
}
matrix[row][column] = x
}
return matrix
}
func neededRows(slice []int, columns int) int {
rows := len(slice) / columns
if len(slice)%columns != 0 {
rows++
}
return rows
}
func UniqueInts(slice []int) []int {
seen := map[int]bool{} // == make(map[int]bool)
unique := []int{} // == make([]int, 0)
for _, x := range slice {
if _, found := seen[x]; !found {
unique = append(unique, x)
seen[x] = true
}
}
return unique
}
func ParseIni(lines []string) map[string]map[string]string {
const separator = "="
ini := make(map[string]map[string]string)
group := "General"
for _, line := range lines {
line := strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, ";") {
continue
}
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
group = line[1 : len(line)-1]
} else if i := strings.Index(line, separator); i > -1 {
key := line[:i]
value := line[i+len(separator):]
if _, found := ini[group]; !found {
ini[group] = make(map[string]string)
}
ini[group][key] = value
} else {
log.Print("failed to parse line:", line)
}
}
return ini
}
func PrintIni(ini map[string]map[string]string) {
groups := make([]string, 0, len(ini))
for group := range ini {
groups = append(groups, group)
}
sort.Strings(groups)
for i, group := range groups {
fmt.Printf("[%s]\n", group)
keys := make([]string, 0, len(ini[group]))
for key := range ini[group] {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
fmt.Printf("%s=%s\n", key, ini[group][key])
}
if i+1 < len(groups) {
fmt.Println()
}
}
}

@ -0,0 +1,107 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bytes"
"fmt"
"path/filepath"
"strings"
)
func main() {
testData := [][]string{
{"/home/user/goeg", "/home/user/goeg/prefix",
"/home/user/goeg/prefix/extra"},
{"/home/user/goeg", "/home/user/goeg/prefix",
"/home/user/prefix/extra"},
{"/pecan/π/goeg", "/pecan/π/goeg/prefix",
"/pecan/π/prefix/extra"},
{"/pecan/π/circle", "/pecan/π/circle/prefix",
"/pecan/π/circle/prefix/extra"},
{"/home/user/goeg", "/home/users/goeg",
"/home/userspace/goeg"},
{"/home/user/goeg", "/tmp/user", "/var/log"},
{"/home/mark/goeg", "/home/user/goeg"},
{"home/user/goeg", "/tmp/user", "/var/log"},
}
for _, data := range testData {
fmt.Printf("[")
gap := ""
for _, datum := range data {
fmt.Printf("%s\"%s\"", gap, datum)
gap = " "
}
fmt.Println("]")
cp := CommonPrefix(data)
cpp := CommonPathPrefix(data)
equal := "=="
if cpp != cp {
equal = "!="
}
fmt.Printf("char ⨉ path prefix: \"%s\" %s \"%s\"\n\n",
cp, equal, cpp)
}
}
func CommonPrefix(texts []string) string {
components := make([][]rune, len(texts))
for i, text := range texts {
components[i] = []rune(text)
}
if len(components) == 0 || len(components[0]) == 0 {
return ""
}
var common bytes.Buffer
FINISH:
for column := 0; column < len(components[0]); column++ {
char := components[0][column]
for row := 1; row < len(components); row++ {
if column >= len(components[row]) ||
components[row][column] != char {
break FINISH
}
}
common.WriteRune(char)
}
return common.String()
}
func CommonPathPrefix(paths []string) string {
const separator = string(filepath.Separator)
components := make([][]string, len(paths))
for i, path := range paths {
components[i] = strings.Split(path, separator)
if strings.HasPrefix(path, separator) {
components[i] = append([]string{separator}, components[i]...)
}
}
if len(components) == 0 || len(components[0]) == 0 {
return ""
}
var common []string
FINISH:
for column := range components[0] {
part := components[0][column]
for row := 1; row < len(components); row++ {
if len(components[row]) == 0 ||
column >= len(components[row]) ||
components[row][column] != part {
break FINISH
}
}
common = append(common, part)
}
return filepath.Join(common...)
}

191
src/contains/contains.go Normal file

@ -0,0 +1,191 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"reflect"
)
type Slicer interface {
EqualTo(i int, x interface{}) bool
Len() int
}
type IntSlice []int
func (slice IntSlice) EqualTo(i int, x interface{}) bool {
return slice[i] == x.(int)
}
func (slice IntSlice) Len() int { return len(slice) }
func IntIndexSlicer(ints []int, x int) int {
return IndexSlicer(IntSlice(ints), x)
}
type FloatSlice []float64
func (slice FloatSlice) EqualTo(i int, x interface{}) bool {
return slice[i] == x.(float64)
}
func (slice FloatSlice) Len() int { return len(slice) }
func FloatIndexSlicer(floats []float64, x float64) int {
return IndexSlicer(FloatSlice(floats), x)
}
type StringSlice []string
func (slice StringSlice) EqualTo(i int, x interface{}) bool {
return slice[i] == x.(string)
}
func (slice StringSlice) Len() int { return len(slice) }
func StringIndexSlicer(strs []string, x string) int {
return IndexSlicer(StringSlice(strs), x)
}
// Returns the index position of x in slice or array xs providing xs's
// items are of the same time as x (integers or strings); returns -1 if x
// isn't in xs. Uses a slow linear search suitable for small amounts of
// unsorted data.
func IndexSlicer(slice Slicer, x interface{}) int {
for i := 0; i < slice.Len(); i++ {
if slice.EqualTo(i, x) {
return i
}
}
return -1
}
// Returns true if x is in slice or array xs providing xs's items are of
// the same time as x (integers or strings). Uses the Index() function
// which does a slow linear search suitable for small amounts of unsorted
// data.
func InSlice(xs interface{}, x interface{}) bool {
return Index(xs, x) > -1
}
// Returns the index position of x in slice or array xs providing xs's
// items are of the same time as x (integers or strings); returns -1 if x
// isn't in xs. Uses a slow linear search suitable for small amounts of
// unsorted data.
func Index(xs interface{}, x interface{}) int {
switch slice := xs.(type) {
case []int:
for i, y := range slice {
if y == x.(int) {
return i
}
}
case []string:
for i, y := range slice {
if y == x.(string) {
return i
}
}
}
return -1
}
func InSliceReflect(xs interface{}, x interface{}) bool {
return IndexReflect(xs, x) > -1
}
func IndexReflectX(xs interface{}, x interface{}) int { // Long-winded way
if slice := reflect.ValueOf(xs); slice.Kind() == reflect.Slice {
for i := 0; i < slice.Len(); i++ {
switch y := slice.Index(i).Interface().(type) {
case int:
if y == x.(int) {
return i
}
case string:
if y == x.(string) {
return i
}
}
}
}
return -1
}
func IndexReflect(xs interface{}, x interface{}) int {
if slice := reflect.ValueOf(xs); slice.Kind() == reflect.Slice {
for i := 0; i < slice.Len(); i++ {
if reflect.DeepEqual(x, slice.Index(i)) {
return i
}
}
}
return -1
}
func IntSliceIndex(xs []int, x int) int {
for i, y := range xs {
if x == y {
return i
}
}
return -1
}
func StringSliceIndex(xs []string, s string) int {
for i, x := range xs {
if x == s {
return i
}
}
return -1
}
func SliceIndex(limit int, predicate func(i int) bool) int {
for i := 0; i < limit; i++ {
if predicate(i) {
return i
}
}
return -1
}
func main() {
xs := []int{2, 4, 6, 8}
fmt.Println("5 @", Index(xs, 5), " 6 @", Index(xs, 6))
ys := []string{"C", "B", "K", "A"}
fmt.Println("Z @", Index(ys, "Z"), " A @", Index(ys, "A"))
fmt.Println("5 @", IndexReflectX(xs, 5), " 6 @", IndexReflectX(xs, 6))
fmt.Println("Z @", IndexReflectX(ys, "Z"), " A @",
IndexReflectX(ys, "A"))
fmt.Println("5 @", IndexReflect(xs, 5), " 6 @", IndexReflect(xs, 6))
fmt.Println("Z @", IndexReflect(ys, "Z"), " A @",
IndexReflect(ys, "A"))
fmt.Println("5 @", IntIndexSlicer(xs, 5),
" 6 @", IntIndexSlicer(xs, 6))
fmt.Println("Z @", StringIndexSlicer(ys, "Z"),
" A @", StringIndexSlicer(ys, "A"))
sliceIndex()
}
func sliceIndex() {
xs := []int{2, 4, 6, 8}
ys := []string{"C", "B", "K", "A"}
fmt.Println(
SliceIndex(len(xs), func(i int) bool { return xs[i] == 5 }),
SliceIndex(len(xs), func(i int) bool { return xs[i] == 6 }),
SliceIndex(len(ys), func(i int) bool { return ys[i] == "Z" }),
SliceIndex(len(ys), func(i int) bool { return ys[i] == "A" }))
}

127
src/filter/filter.go Normal file

@ -0,0 +1,127 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"strings"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // Use all the machine's cores
log.SetFlags(0)
algorithm,
minSize, maxSize, suffixes, files := handleCommandLine()
if algorithm == 1 {
sink(filterSize(minSize, maxSize, filterSuffixes(suffixes, source(files))))
} else {
channel1 := source(files)
channel2 := filterSuffixes(suffixes, channel1)
channel3 := filterSize(minSize, maxSize, channel2)
sink(channel3)
}
}
func handleCommandLine() (algorithm int, minSize, maxSize int64,
suffixes, files []string) {
flag.IntVar(&algorithm, "algorithm", 1, "1 or 2")
flag.Int64Var(&minSize, "min", -1,
"minimum file size (-1 means no minimum)")
flag.Int64Var(&maxSize, "max", -1,
"maximum file size (-1 means no maximum)")
var suffixesOpt *string = flag.String("suffixes", "",
"comma-separated list of file suffixes")
flag.Parse()
if algorithm != 1 && algorithm != 2 {
algorithm = 1
}
if minSize > maxSize && maxSize != -1 {
log.Fatalln("minimum size must be < maximum size")
}
suffixes = []string{}
if *suffixesOpt != "" {
suffixes = strings.Split(*suffixesOpt, ",")
}
files = flag.Args()
return algorithm, minSize, maxSize, suffixes, files
}
func source(files []string) <-chan string {
out := make(chan string, 1000)
go func() {
for _, filename := range files {
out <- filename
}
close(out)
}()
return out
}
// make the buffer the same size as for files to maximize throughput
func filterSuffixes(suffixes []string, in <-chan string) <-chan string {
out := make(chan string, cap(in))
go func() {
for filename := range in {
if len(suffixes) == 0 {
out <- filename
continue
}
ext := strings.ToLower(filepath.Ext(filename))
for _, suffix := range suffixes {
if ext == suffix {
out <- filename
break
}
}
}
close(out)
}()
return out
}
// make the buffer the same size as for files to maximize throughput
func filterSize(minimum, maximum int64, in <-chan string) <-chan string {
out := make(chan string, cap(in))
go func() {
for filename := range in {
if minimum == -1 && maximum == -1 {
out <- filename // don't do a stat call it not needed
continue
}
finfo, err := os.Stat(filename)
if err != nil {
continue // ignore files we can't process
}
size := finfo.Size()
if (minimum == -1 || minimum > -1 && minimum <= size) &&
(maximum == -1 || maximum > -1 && maximum >= size) {
out <- filename
}
}
close(out)
}()
return out
}
func sink(in <-chan string) {
for filename := range in {
fmt.Println(filename)
}
}

@ -0,0 +1,149 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Special thanks to Russ Cox for some excellent help with the original
// version of this example.
package main
import (
"crypto/sha1"
"fmt"
"io"
"log"
"os"
"path/filepath"
"runtime"
"sort"
"sync"
)
const maxSizeOfSmallFile = 1024 * 32
const maxGoroutines = 100
type pathsInfo struct {
size int64
paths []string
}
type fileInfo struct {
sha1 []byte
size int64
path string
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // Use all the machine's cores
if len(os.Args) == 1 || os.Args[1] == "-h" || os.Args[1] == "--help" {
fmt.Printf("usage: %s <path>\n", filepath.Base(os.Args[0]))
os.Exit(1)
}
infoChan := make(chan fileInfo, maxGoroutines*2)
go findDuplicates(infoChan, os.Args[1])
pathData := mergeResults(infoChan)
outputResults(pathData)
}
func findDuplicates(infoChan chan fileInfo, dirname string) {
waiter := &sync.WaitGroup{}
filepath.Walk(dirname, makeWalkFunc(infoChan, waiter))
waiter.Wait() // Blocks until all the work is done
close(infoChan)
}
func makeWalkFunc(infoChan chan fileInfo,
waiter *sync.WaitGroup) func(string, os.FileInfo, error) error {
return func(path string, info os.FileInfo, err error) error {
if err == nil && info.Size() > 0 &&
(info.Mode()&os.ModeType == 0) {
if info.Size() < maxSizeOfSmallFile ||
runtime.NumGoroutine() > maxGoroutines {
processFile(path, info, infoChan, nil)
} else {
waiter.Add(1)
go processFile(path, info, infoChan,
func() { waiter.Done() })
}
}
return nil // We ignore all errors
}
}
func processFile(filename string, info os.FileInfo,
infoChan chan fileInfo, done func()) {
if done != nil {
defer done()
}
file, err := os.Open(filename)
if err != nil {
log.Println("error:", err)
return
}
defer file.Close()
hash := sha1.New()
if size, err := io.Copy(hash, file);
size != info.Size() || err != nil {
if err != nil {
log.Println("error:", err)
} else {
log.Println("error: failed to read the whole file:", filename)
}
return
}
infoChan <- fileInfo{hash.Sum(nil), info.Size(), filename}
}
func mergeResults(infoChan <-chan fileInfo) map[string]*pathsInfo {
pathData := make(map[string]*pathsInfo)
format := fmt.Sprintf("%%016X:%%%dX", sha1.Size*2) // == "%016X:%40X"
for info := range infoChan {
key := fmt.Sprintf(format, info.size, info.sha1)
value, found := pathData[key]
if !found {
value = &pathsInfo{size: info.size}
pathData[key] = value
}
value.paths = append(value.paths, info.path)
}
return pathData
}
func outputResults(pathData map[string]*pathsInfo) {
keys := make([]string, 0, len(pathData))
for key := range pathData {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
value := pathData[key]
if len(value.paths) > 1 {
fmt.Printf("%d duplicate files (%s bytes):\n",
len(value.paths), commas(value.size))
sort.Strings(value.paths)
for _, name := range value.paths {
fmt.Printf("\t%s\n", name)
}
}
}
}
// commas() returns a string representing the whole number with comma
// grouping.
func commas(x int64) string {
value := fmt.Sprint(x)
for i := len(value) - 3; i > 0; i -= 3 {
value = value[:i] + "," + value[i:]
}
return value
}

64
src/font/font.go Normal file

@ -0,0 +1,64 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package font
import (
"fmt"
"log"
"unicode/utf8"
)
type Font struct {
family string
size int
}
func New(family string, size int) *Font {
return &Font{saneFamily("sans-serif", family), saneSize(10, size)}
}
func (font *Font) Family() string { return font.family }
func (font *Font) SetFamily(family string) {
font.family = saneFamily(font.family, family)
}
func (font *Font) Size() int { return font.size }
func (font *Font) SetSize(size int) {
font.size = saneSize(font.size, size)
}
func (font *Font) String() string {
return fmt.Sprintf("{font-family: %q; font-size: %dpt;}", font.family,
font.size)
}
func saneFamily(oldFamily, newFamily string) string {
if len(newFamily) < utf8.UTFMax &&
utf8.RuneCountInString(newFamily) < 1 {
log.Printf("font.saneFamily(): ignored invalid family '%s'",
newFamily)
return oldFamily
}
return newFamily
}
func saneSize(oldSize, newSize int) int {
if newSize < 5 || newSize > 144 {
log.Printf("font.saneSize(): ignored invalid size '%d'", newSize)
return oldSize
}
return newSize
}

67
src/font/font_test.go Normal file

@ -0,0 +1,67 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package font_test
import (
"fmt"
"font"
"testing"
)
func TestFont(t *testing.T) {
bodyFont := font.New("Nimbus Sans", 10)
titleFont := font.New("serif", 11)
f1(bodyFont, titleFont, t)
}
func f1(bodyFont, titleFont *font.Font, t *testing.T) {
if bodyFont.String() !=
`{font-family: "Nimbus Sans"; font-size: 10pt;}` {
t.Fatal("#1 bodyFont invalid CSS")
}
if bodyFont.Size() != 10 || bodyFont.Family() != "Nimbus Sans" {
t.Fatal("#2 bodyFont invalid attributes")
}
bodyFont.SetSize(12)
if bodyFont.Size() != 12 || bodyFont.Family() != "Nimbus Sans" {
t.Fatal("#3 bodyFont invalid attributes")
}
if bodyFont.String() !=
`{font-family: "Nimbus Sans"; font-size: 12pt;}` {
t.Fatal("#4 bodyFont invalid CSS")
}
bodyFont.SetFamily("")
if bodyFont.Size() != 12 || bodyFont.Family() != "Nimbus Sans" {
t.Fatal("#5 bodyFont invalid attributes")
}
if titleFont.String() != `{font-family: "serif"; font-size: 11pt;}` {
t.Fatal("#6 titleFont invalid CSS")
}
if titleFont.Size() != 11 || titleFont.Family() != "serif" {
t.Fatal("#7 titleFont invalid attributes")
}
titleFont.SetFamily("Helvetica")
titleFont.SetSize(20)
if titleFont.Size() != 20 || titleFont.Family() != "Helvetica" {
t.Fatal("#8 titleFont invalid attributes")
}
f2(bodyFont, titleFont)
}
func f2(bodyFont, titleFont *font.Font) {
fmt.Println(bodyFont)
fmt.Println(titleFont)
}

45
src/fuzzy/fuzzy.go Normal file

@ -0,0 +1,45 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"fuzzy/fuzzybool"
)
func main() {
a, _ := fuzzybool.New(0) // Safe to ignore err value when using
b, _ := fuzzybool.New(.25) // known valid values; must check if using
c, _ := fuzzybool.New(.75) // variables though.
d := c.Copy()
if err := d.Set(1); err != nil {
fmt.Println(err)
}
process(a, b, c, d)
s := []*fuzzybool.FuzzyBool{a, b, c, d}
fmt.Println(s)
}
func process(a, b, c, d *fuzzybool.FuzzyBool) {
fmt.Println("Original:", a, b, c, d)
fmt.Println("Not: ", a.Not(), b.Not(), c.Not(), d.Not())
fmt.Println("Not Not: ", a.Not().Not(), b.Not().Not(), c.Not().Not(),
d.Not().Not())
fmt.Print("0.And(.25)→", a.And(b), "• .25.And(.75)→", b.And(c),
"• .75.And(1)→", c.And(d), " • .25.And(.75,1)→", b.And(c, d), "\n")
fmt.Print("0.Or(.25)→", a.Or(b), "• .25.Or(.75)→", b.Or(c),
"• .75.Or(1)→", c.Or(d), " • .25.Or(.75,1)→", b.Or(c, d), "\n")
fmt.Println("a < c, a == c, a > c:", a.Less(c), a.Equal(c), c.Less(a))
fmt.Println("Bool: ", a.Bool(), b.Bool(), c.Bool(), d.Bool())
fmt.Println("Float: ", a.Float(), b.Float(), c.Float(), d.Float())
}

@ -0,0 +1,104 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fuzzybool
import "fmt"
type FuzzyBool struct{ value float32 }
func New(value interface{}) (*FuzzyBool, error) {
amount, err := float32ForValue(value)
return &FuzzyBool{amount}, err
}
func float32ForValue(value interface{}) (fuzzy float32, err error) {
switch value := value.(type) { // shadow variable
case float32:
fuzzy = value
case float64:
fuzzy = float32(value)
case int:
fuzzy = float32(value)
case bool:
fuzzy = 0
if value {
fuzzy = 1
}
default:
return 0, fmt.Errorf("float32ForValue(): %v is not a "+
"number or Boolean", value)
}
if fuzzy < 0 {
fuzzy = 0
} else if fuzzy > 1 {
fuzzy = 1
}
return fuzzy, nil
}
func (fuzzy *FuzzyBool) Set(value interface{}) (err error) {
fuzzy.value, err = float32ForValue(value)
return err
}
func (fuzzy *FuzzyBool) Copy() *FuzzyBool {
return &FuzzyBool{fuzzy.value}
}
func (fuzzy *FuzzyBool) String() string {
return fmt.Sprintf("%.0f%%", 100*fuzzy.value)
}
func (fuzzy *FuzzyBool) Not() *FuzzyBool {
return &FuzzyBool{1 - fuzzy.value}
}
func (fuzzy *FuzzyBool) And(first *FuzzyBool,
rest ...*FuzzyBool) *FuzzyBool {
minimum := fuzzy.value
rest = append(rest, first)
for _, other := range rest {
if minimum > other.value {
minimum = other.value
}
}
return &FuzzyBool{minimum}
}
func (fuzzy *FuzzyBool) Or(first *FuzzyBool,
rest ...*FuzzyBool) *FuzzyBool {
maximum := fuzzy.value
rest = append(rest, first)
for _, other := range rest {
if maximum < other.value {
maximum = other.value
}
}
return &FuzzyBool{maximum}
}
func (fuzzy *FuzzyBool) Less(other *FuzzyBool) bool {
return fuzzy.value < other.value
}
func (fuzzy *FuzzyBool) Equal(other *FuzzyBool) bool {
return fuzzy.value == other.value
}
func (fuzzy *FuzzyBool) Bool() bool {
return fuzzy.value >= .5
}
func (fuzzy *FuzzyBool) Float() float64 {
return float64(fuzzy.value)
}

@ -0,0 +1,95 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fuzzybool
import "fmt"
type FuzzyBool float32
func New(value interface{}) (FuzzyBool, error) {
var fuzzy float32
switch value := value.(type) { // shadow variable
case float32:
fuzzy = value
case float64:
fuzzy = float32(value)
case int:
fuzzy = float32(value)
case bool:
fuzzy = 0
if value {
fuzzy = 1
}
default:
return FuzzyBool(0), fmt.Errorf("fuzzybool.New(): %v is not a " +
"number or boolean\n", value)
}
if fuzzy < 0 {
fuzzy = 0
} else if fuzzy > 1 {
fuzzy = 1
}
return FuzzyBool(fuzzy), nil
}
func (fuzzy FuzzyBool) Copy() FuzzyBool {
return FuzzyBool(fuzzy)
}
func (fuzzy FuzzyBool) String() string {
return fmt.Sprintf("%.0f%%", 100*float32(fuzzy))
}
func (fuzzy FuzzyBool) Not() FuzzyBool {
return FuzzyBool(1 - float32(fuzzy))
}
func (fuzzy FuzzyBool) And(first FuzzyBool,
rest ...FuzzyBool) FuzzyBool {
minimum := fuzzy
rest = append(rest, first)
for _, other := range rest {
if minimum > other {
minimum = other
}
}
return FuzzyBool(minimum)
}
func (fuzzy FuzzyBool) Or(first FuzzyBool,
rest ...FuzzyBool) FuzzyBool {
maximum := fuzzy
rest = append(rest, first)
for _, other := range rest {
if maximum < other {
maximum = other
}
}
return FuzzyBool(maximum)
}
func (fuzzy FuzzyBool) Less(other FuzzyBool) bool {
return fuzzy < other
}
func (fuzzy FuzzyBool) Equal(other FuzzyBool) bool {
return fuzzy == other
}
func (fuzzy FuzzyBool) Bool() bool {
return float32(fuzzy) >= .5
}
func (fuzzy FuzzyBool) Float() float64 {
return float64(fuzzy)
}

@ -0,0 +1,44 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"fuzzy_immutable/fuzzybool"
)
func main() {
a, _ := fuzzybool.New(0)
b, _ := fuzzybool.New(.25)
c, _ := fuzzybool.New(.75)
d := c.Copy()
d, _ = fuzzybool.New(1)
process(a, b, c, d)
s := []fuzzybool.FuzzyBool{a, b, c, d}
fmt.Println(s)
}
func process(a, b, c, d fuzzybool.FuzzyBool) {
fmt.Println("Original:", a, b, c, d)
fmt.Println("Not: ", a.Not(), b.Not(), c.Not(), d.Not())
fmt.Println("Not Not: ", a.Not().Not(), b.Not().Not(), c.Not().Not(),
d.Not().Not())
fmt.Print("0.And(.25)→", a.And(b), " .25.And(.75)→", b.And(c),
" .75.And(1)→", c.And(d), " 0.And(.25,.75,1)→", a.And(b, c, d),
"\n")
fmt.Print("0.Or(.25)→", a.Or(b), " .25.Or(.75)→", b.Or(c),
" .75.Or(1)→", c.Or(d), " 0.Or(.25,.75,1)→", a.Or(b, c, d), "\n")
fmt.Println("a < c, a == c, a > c:", a.Less(c), a.Equal(c), c.Less(a))
fmt.Println("Bool: ", a.Bool(), b.Bool(), c.Bool(), d.Bool())
fmt.Println("Float: ", a.Float(), b.Float(), c.Float(), d.Float())
}

@ -0,0 +1,111 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fuzzybool
import "fmt"
type FuzzyBool float32
func New(value interface{}) (*FuzzyBool, error) {
var fuzzy FuzzyBool
result, err := float32ForValue(value)
fuzzy = FuzzyBool(result)
return &fuzzy, err
}
func float32ForValue(value interface{}) (fuzzy float32, err error) {
switch value := value.(type) { // shadow variable
case float32:
fuzzy = value
case float64:
fuzzy = float32(value)
case int:
fuzzy = float32(value)
case bool:
fuzzy = 0
if value {
fuzzy = 1
}
default:
return 0, fmt.Errorf("float32ForValue(): %v is not a "+
"number or Boolean", value)
}
if fuzzy < 0 {
fuzzy = 0
} else if fuzzy > 1 {
fuzzy = 1
}
return fuzzy, nil
}
func (fuzzy *FuzzyBool) Set(value interface{}) error {
result, err := float32ForValue(value)
*fuzzy = FuzzyBool(result)
return err
}
func (fuzzy *FuzzyBool) Copy() *FuzzyBool {
result := FuzzyBool(*fuzzy)
return &result
}
func (fuzzy *FuzzyBool) String() string {
return fmt.Sprintf("%.0f%%", 100*float32(*fuzzy))
}
func (fuzzy *FuzzyBool) Not() *FuzzyBool {
result := FuzzyBool(1 - float32(*fuzzy))
return &result
}
func (fuzzy *FuzzyBool) And(first *FuzzyBool,
rest ...*FuzzyBool) *FuzzyBool {
minimum := *fuzzy
rest = append(rest, first)
for _, other := range rest {
if minimum > *other {
minimum = *other
}
}
result := FuzzyBool(minimum)
return &result
}
func (fuzzy *FuzzyBool) Or(first *FuzzyBool,
rest ...*FuzzyBool) *FuzzyBool {
maximum := *fuzzy
rest = append(rest, first)
for _, other := range rest {
if maximum < *other {
maximum = *other
}
}
result := FuzzyBool(maximum)
return &result
}
func (fuzzy *FuzzyBool) Less(other *FuzzyBool) bool {
return *fuzzy < *other
}
func (fuzzy *FuzzyBool) Equal(other *FuzzyBool) bool {
return *fuzzy == *other
}
func (fuzzy *FuzzyBool) Bool() bool {
return float32(*fuzzy) >= .5
}
func (fuzzy *FuzzyBool) Float() float64 {
return float64(*fuzzy)
}

@ -0,0 +1,46 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"fuzzy_mutable/fuzzybool"
)
func main() {
a, _ := fuzzybool.New(0)
b, _ := fuzzybool.New(.25)
c, _ := fuzzybool.New(.75)
d := c.Copy()
if err := d.Set(1); err != nil {
fmt.Println(err)
}
process(a, b, c, d)
s := []*fuzzybool.FuzzyBool{a, b, c, d}
fmt.Println(s)
}
func process(a, b, c, d *fuzzybool.FuzzyBool) {
fmt.Println("Original:", a, b, c, d)
fmt.Println("Not: ", a.Not(), b.Not(), c.Not(), d.Not())
fmt.Println("Not Not: ", a.Not().Not(), b.Not().Not(), c.Not().Not(),
d.Not().Not())
fmt.Print("0.And(.25)→", a.And(b), " .25.And(.75)→", b.And(c),
" .75.And(1)→", c.And(d), " 0.And(.25,.75,1)→", a.And(b, c, d),
"\n")
fmt.Print("0.Or(.25)→", a.Or(b), " .25.Or(.75)→", b.Or(c),
" .75.Or(1)→", c.Or(d), " 0.Or(.25,.75,1)→", a.Or(b, c, d), "\n")
fmt.Println("a < c, a == c, a > c:", a.Less(c), a.Equal(c), c.Less(a))
fmt.Println("Bool: ", a.Bool(), b.Bool(), c.Bool(), d.Bool())
fmt.Println("Float: ", a.Float(), b.Float(), c.Float(), d.Float())
}

47
src/fuzzy_value/fuzzy.go Normal file

@ -0,0 +1,47 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"fuzzy_value/fuzzybool"
)
func main() {
var a fuzzybool.FuzzyBool // Same as: a, _ := fuzzybool.New(0)
b, _ := fuzzybool.New(.25) // known valid values; must check if using
c, _ := fuzzybool.New(.75) // variables though.
d := c.Copy()
if err := d.Set(1); err != nil {
fmt.Println(err)
}
process(a, b, c, d)
s := []fuzzybool.FuzzyBool{a, b, c, d}
fmt.Println(s)
m := map[fuzzybool.FuzzyBool]string{a: "a", b: "b", c: "c", c: "d"}
fmt.Println(m)
}
func process(a, b, c, d fuzzybool.FuzzyBool) {
fmt.Println("Original:", a, b, c, d)
fmt.Println("Not: ", a.Not(), b.Not(), c.Not(), d.Not())
fmt.Println("Not Not: ", a.Not().Not(), b.Not().Not(), c.Not().Not(),
d.Not().Not())
fmt.Print("0.And(.25)→", a.And(b), "• .25.And(.75)→", b.And(c),
"• .75.And(1)→", c.And(d), " • .25.And(.75,1)→", b.And(c, d), "\n")
fmt.Print("0.Or(.25)→", a.Or(b), "• .25.Or(.75)→", b.Or(c),
"• .75.Or(1)→", c.Or(d), " • .25.Or(.75,1)→", b.Or(c, d), "\n")
fmt.Println("a < c, a == c, a > c:", a.Less(c), a.Equal(c), c.Less(a))
fmt.Println("Bool: ", a.Bool(), b.Bool(), c.Bool(), d.Bool())
fmt.Println("Float: ", a.Float(), b.Float(), c.Float(), d.Float())
}

@ -0,0 +1,104 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fuzzybool
import "fmt"
type FuzzyBool struct{ value float32 }
func New(value interface{}) (FuzzyBool, error) {
amount, err := float32ForValue(value)
return FuzzyBool{amount}, err
}
func float32ForValue(value interface{}) (fuzzy float32, err error) {
switch value := value.(type) { // shadow variable
case float32:
fuzzy = value
case float64:
fuzzy = float32(value)
case int:
fuzzy = float32(value)
case bool:
fuzzy = 0
if value {
fuzzy = 1
}
default:
return 0, fmt.Errorf("float32ForValue(): %v is not a "+
"number or Boolean", value)
}
if fuzzy < 0 {
fuzzy = 0
} else if fuzzy > 1 {
fuzzy = 1
}
return fuzzy, nil
}
func (fuzzy *FuzzyBool) Set(value interface{}) (err error) {
fuzzy.value, err = float32ForValue(value)
return err
}
func (fuzzy FuzzyBool) Copy() FuzzyBool {
return FuzzyBool{fuzzy.value}
}
func (fuzzy FuzzyBool) String() string {
return fmt.Sprintf("%.0f%%", 100*fuzzy.value)
}
func (fuzzy FuzzyBool) Not() FuzzyBool {
return FuzzyBool{1 - fuzzy.value}
}
func (fuzzy FuzzyBool) And(first FuzzyBool,
rest ...FuzzyBool) FuzzyBool {
minimum := fuzzy.value
rest = append(rest, first)
for _, other := range rest {
if minimum > other.value {
minimum = other.value
}
}
return FuzzyBool{minimum}
}
func (fuzzy FuzzyBool) Or(first FuzzyBool,
rest ...FuzzyBool) FuzzyBool {
maximum := fuzzy.value
rest = append(rest, first)
for _, other := range rest {
if maximum < other.value {
maximum = other.value
}
}
return FuzzyBool{maximum}
}
func (fuzzy FuzzyBool) Less(other FuzzyBool) bool {
return fuzzy.value < other.value
}
func (fuzzy FuzzyBool) Equal(other FuzzyBool) bool {
return fuzzy.value == other.value
}
func (fuzzy FuzzyBool) Bool() bool {
return fuzzy.value >= .5
}
func (fuzzy FuzzyBool) Float() float64 {
return float64(fuzzy.value)
}

@ -0,0 +1,103 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
)
func main() {
if len(os.Args) == 1 || os.Args[1] == "-h" || os.Args[1] == "--help" {
fmt.Printf("usage: %s file\n", filepath.Base(os.Args[0]))
os.Exit(1)
}
separators := []string{"\t", "*", "|", "•"}
linesRead, lines := readUpToNLines(os.Args[1], 5)
counts := createCounts(lines, separators, linesRead)
separator := guessSep(counts, separators, linesRead)
report(separator)
}
func createCounts(lines, separators []string, linesRead int) [][]int {
counts := make([][]int, len(separators))
for sepIndex := range separators {
counts[sepIndex] = make([]int, linesRead)
for lineIndex, line := range lines {
counts[sepIndex][lineIndex] =
strings.Count(line, separators[sepIndex])
}
}
return counts
}
func guessSep(counts [][]int, separators []string, linesRead int) string {
for sepIndex := range separators {
same := true
count := counts[sepIndex][0]
for lineIndex := 1; lineIndex < linesRead; lineIndex++ {
if counts[sepIndex][lineIndex] != count {
same = false
break
}
}
if count > 0 && same {
return separators[sepIndex]
}
}
return ""
}
func report(separator string) {
switch separator {
case "":
fmt.Println("whitespace-separated or not separated at all")
case "\t":
fmt.Println("tab-separated")
default:
fmt.Printf("%s-separated\n", separator)
}
}
func readUpToNLines(filename string, maxLines int) (int, []string) {
var file *os.File
var err error
if file, err = os.Open(filename); err != nil {
log.Fatal("failed to open the file: ", err)
}
defer file.Close()
lines := make([]string, maxLines)
reader := bufio.NewReader(file)
i := 0
for ; i < maxLines; i++ {
line, err := reader.ReadString('\n')
if line != "" {
lines[i] = line
}
if err != nil {
if err == io.EOF {
break
}
log.Fatal("failed to finish reading the file: ", err)
}
} // Return the subslice actually used; could be < maxLines
return i, lines[:i]
}

29
src/hello/hello.go Normal file

@ -0,0 +1,29 @@
// Copyright © 2010-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// hello.go
package main
import (
"fmt"
"os"
"strings"
)
func main() {
who := "World!"
if len(os.Args) > 1 { /* os.Args[0] is "hello" or "hello.exe" */
who = strings.Join(os.Args[1:], " ")
}
fmt.Println("Hello", who)
}

@ -0,0 +1,74 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"image"
"os"
"path/filepath"
"runtime"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // Use all the machine's cores
if len(os.Args) == 1 || os.Args[1] == "-h" || os.Args[1] == "--help" {
fmt.Printf("usage: %s <image files>\n",
filepath.Base(os.Args[0]))
os.Exit(1)
}
files := commandLineFiles(os.Args[1:])
for _, filename := range files {
process(filename)
}
}
func process(filename string) {
if info, err := os.Stat(filename); err != nil ||
(info.Mode()&os.ModeType != 0) {
return // Ignore errors and nonregular files
}
file, err := os.Open(filename)
if err != nil {
return // Ignore errors
}
defer file.Close()
config, _, err := image.DecodeConfig(file)
if err != nil {
return // Ignore errors
}
fmt.Printf(`<img src="%s" width="%d" height="%d" />`,
filepath.Base(filename), config.Width, config.Height)
fmt.Println()
}
func commandLineFiles(files []string) []string {
if runtime.GOOS == "windows" {
args := make([]string, 0, len(files))
for _, name := range files {
if matches, err := filepath.Glob(name); err != nil {
args = append(args, name) // Invalid pattern
} else if matches != nil { // At least one match
args = append(args, matches...)
}
}
return args
}
return files
}

118
src/imagetag2/imagetag2.go Normal file

@ -0,0 +1,118 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"image"
"os"
"path/filepath"
"runtime"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
)
var workers = runtime.NumCPU()
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // Use all the machine's cores
if len(os.Args) == 1 || os.Args[1] == "-h" || os.Args[1] == "--help" {
fmt.Printf("usage: %s <image files>\n",
filepath.Base(os.Args[0]))
os.Exit(1)
}
files := commandLineFiles(os.Args[1:])
jobs := make(chan string, workers*16)
results := make(chan string)
done := make(chan struct{}, workers)
go addJobs(files, jobs)
for i := 0; i < workers; i++ {
go doJobs(done, results, jobs)
}
waitAndProcessResults(done, results)
}
func addJobs(files []string, jobs chan<- string) {
for _, filename := range files {
jobs <- filename
}
close(jobs)
}
func doJobs(done chan<- struct{}, results chan<- string,
jobs <-chan string) {
for job := range jobs {
if result, ok := process(job); ok {
results <- result
}
}
done <- struct{}{}
}
func waitAndProcessResults(done <-chan struct{}, results <-chan string) {
for working := workers; working > 0; {
select { // Blocking
case result := <-results:
fmt.Println(result)
case <-done:
working--
}
}
DONE:
for {
select { // Nonblocking
case result := <-results:
fmt.Println(result)
default:
break DONE
}
}
}
func process(filename string) (string, bool) {
if info, err := os.Stat(filename); err != nil ||
(info.Mode()&os.ModeType == 1) {
return "", false // Ignore errors and nonregular files
}
file, err := os.Open(filename)
if err != nil {
return "", false // Ignore errors
}
defer file.Close()
config, _, err := image.DecodeConfig(file)
if err != nil {
return "", false // Ignore errors
}
return fmt.Sprintf(`<img src="%s" width="%d" height="%d" />`,
filepath.Base(filename), config.Width, config.Height), true
}
func commandLineFiles(files []string) []string {
if runtime.GOOS == "windows" {
args := make([]string, 0, len(files))
for _, name := range files {
if matches, err := filepath.Glob(name); err != nil {
args = append(args, name) // Invalid pattern
} else if matches != nil { // At least one match
args = append(args, matches...)
}
}
return args
}
return files
}

@ -0,0 +1,139 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"sort"
"strings"
)
var original = []string{
"Nonmetals",
" Hydrogen",
" Carbon",
" Nitrogen",
" Oxygen",
"Inner Transitionals",
" Lanthanides",
" Europium",
" Cerium",
" Actinides",
" Uranium",
" Plutonium",
" Curium",
"Alkali Metals",
" Lithium",
" Sodium",
" Potassium",
}
func main() {
fmt.Println("| Original | Sorted |")
fmt.Println("|-------------------|-------------------|")
sorted := SortedIndentedStrings(original) // original is a []string
for i := range original { // set in a global var
fmt.Printf("|%-19s|%-19s|\n", original[i], sorted[i])
}
}
/*
Given a []string that has items with different levels of indent that
are used to indicate parent child relationships, sorts the items
case-insensitively with child items sorted underneath their parent
items, and so on recursively to any level of depth.
The amount of indent per level is computed by finding the first
indented item. Indentation must either be one or more spaces or one or
more tabs.
*/
func SortedIndentedStrings(slice []string) []string {
entries := populateEntries(slice)
return sortedEntries(entries)
}
func populateEntries(slice []string) Entries {
indent, indentSize := computeIndent(slice)
entries := make(Entries, 0)
for _, item := range slice {
i, level := 0, 0
for strings.HasPrefix(item[i:], indent) {
i += indentSize
level++
}
key := strings.ToLower(strings.TrimSpace(item))
addEntry(level, key, item, &entries)
}
return entries
}
func computeIndent(slice []string) (string, int) {
for _, item := range slice {
if len(item) > 0 && (item[0] == ' ' || item[0] == '\t') {
whitespace := rune(item[0])
for i, char := range item[1:] {
if char != whitespace {
return strings.Repeat(string(whitespace), i), i
}
}
}
}
return "", 0
}
func addEntry(level int, key, value string, entries *Entries) {
if level == 0 {
*entries = append(*entries, Entry{key, value, make(Entries, 0)})
} else {
/*
theEntries := *entries
lastEntry := &theEntries[theEntries.Len()-1]
addEntry(level-1, key, value, &lastEntry.children)
*/
addEntry(level-1, key, value,
&((*entries)[entries.Len()-1].children))
}
}
func sortedEntries(entries Entries) []string {
var indentedSlice []string
sort.Sort(entries)
for _, entry := range entries {
populateIndentedStrings(entry, &indentedSlice)
}
return indentedSlice
}
func populateIndentedStrings(entry Entry, indentedSlice *[]string) {
*indentedSlice = append(*indentedSlice, entry.value)
sort.Sort(entry.children)
for _, child := range entry.children {
populateIndentedStrings(child, indentedSlice)
}
}
type Entry struct {
key string
value string
children Entries
}
type Entries []Entry
func (entries Entries) Len() int { return len(entries) }
func (entries Entries) Less(i, j int) bool {
return entries[i].key < entries[j].key
}
func (entries Entries) Swap(i, j int) {
entries[i], entries[j] = entries[j], entries[i]
}

96
src/invoicedata/gob.go Normal file

@ -0,0 +1,96 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
// "bytes"
"encoding/gob"
"errors"
"fmt"
"io"
// "time"
)
/*
// Here is how to make a custom type satisfy the gob.Encoder and
// gob.Decoder interfaces.
type GobInvoice struct {
Id int
CustomerId int
Raised int64 // Seconds since the Unix epoch
Due int64 // Seconds since the Unix epoch
Paid bool
Note string
Items []*Item
}
func (invoice *Invoice) GobEncode() ([]byte, error) {
gobInvoice := GobInvoice{invoice.Id, invoice.CustomerId,
invoice.Raised.Unix(), invoice.Due.Unix(), invoice.Paid,
invoice.Note, invoice.Items}
var buffer bytes.Buffer
encoder := gob.NewEncoder(&buffer)
err := encoder.Encode(gobInvoice)
return buffer.Bytes(), err
}
func (invoice *Invoice) GobDecode(data []byte) error {
var gobInvoice GobInvoice
buffer := bytes.NewBuffer(data)
decoder := gob.NewDecoder(buffer)
if err := decoder.Decode(&gobInvoice); err != nil {
return err
}
raised := time.Unix(gobInvoice.Raised, 0)
due := time.Unix(gobInvoice.Due, 0)
*invoice = Invoice{gobInvoice.Id, gobInvoice.CustomerId, raised, due,
gobInvoice.Paid, gobInvoice.Note, gobInvoice.Items}
return nil
}
*/
type GobMarshaler struct{}
func (GobMarshaler) MarshalInvoices(writer io.Writer,
invoices []*Invoice) error {
encoder := gob.NewEncoder(writer)
if err := encoder.Encode(magicNumber); err != nil {
return err
}
if err := encoder.Encode(fileVersion); err != nil {
return err
}
return encoder.Encode(invoices)
}
func (GobMarshaler) UnmarshalInvoices(reader io.Reader) ([]*Invoice,
error) {
decoder := gob.NewDecoder(reader)
var magic int
if err := decoder.Decode(&magic); err != nil {
return nil, err
}
if magic != magicNumber {
return nil, errors.New("cannot read non-invoices gob file")
}
var version int
if err := decoder.Decode(&version); err != nil {
return nil, err
}
if version > fileVersion {
return nil, fmt.Errorf("version %d is too new to read", version)
}
var invoices []*Invoice
err := decoder.Decode(&invoices)
return invoices, err
}

246
src/invoicedata/inv.go Normal file

@ -0,0 +1,246 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/binary"
"errors"
"fmt"
"io"
"strconv"
"time"
)
type InvMarshaler struct{}
const invDateFormat = "20060102" // This date must always be used.
var byteOrder = binary.LittleEndian
func (InvMarshaler) MarshalInvoices(writer io.Writer,
invoices []*Invoice) error {
var write invWriterFunc = func(x interface{}) error {
return binary.Write(writer, byteOrder, x)
}
if err := write(uint32(magicNumber)); err != nil {
return err
}
if err := write(uint16(fileVersion)); err != nil {
return err
}
if err := write(int32(len(invoices))); err != nil {
return err
}
for _, invoice := range invoices {
if err := write.writeInvoice(invoice); err != nil {
return err
}
}
return nil
}
type invWriterFunc func(interface{}) error
func (write invWriterFunc) writeInvoice(invoice *Invoice) error {
for _, i := range []int{invoice.Id, invoice.CustomerId} {
if err := write(int32(i)); err != nil {
return err
}
}
for _, date := range []time.Time{invoice.Raised, invoice.Due} {
if err := write.writeDate(date); err != nil {
return err
}
}
if err := write.writeBool(invoice.Paid); err != nil {
return err
}
if err := write.writeString(invoice.Note); err != nil {
return err
}
if err := write(int32(len(invoice.Items))); err != nil {
return err
}
for _, item := range invoice.Items {
if err := write.writeItem(item); err != nil {
return err
}
}
return nil
}
func (write invWriterFunc) writeDate(date time.Time) error {
i, err := strconv.Atoi(date.Format(invDateFormat))
if err != nil {
return err
}
return write(int32(i))
}
func (write invWriterFunc) writeBool(b bool) error {
var v int8
if b {
v = 1
}
return write(v)
}
func (write invWriterFunc) writeString(s string) error {
if err := write(int32(len(s))); err != nil {
return err
}
return write([]byte(s))
}
func (write invWriterFunc) writeItem(item *Item) error {
if err := write.writeString(item.Id); err != nil {
return err
}
if err := write(item.Price); err != nil {
return err
}
if err := write(int16(item.Quantity)); err != nil {
return err
}
return write.writeString(item.Note)
}
func (InvMarshaler) UnmarshalInvoices(reader io.Reader) ([]*Invoice,
error) {
if err := checkInvVersion(reader); err != nil {
return nil, err
}
count, err := readIntFromInt32(reader)
if err != nil {
return nil, err
}
invoices := make([]*Invoice, 0, count)
for i := 0; i < count; i++ {
invoice, err := readInvInvoice(reader)
if err != nil {
return nil, err
}
invoices = append(invoices, invoice)
}
return invoices, nil
}
func readIntFromInt32(reader io.Reader) (int, error) {
var i32 int32
err := binary.Read(reader, byteOrder, &i32)
return int(i32), err
}
func readIntFromInt16(reader io.Reader) (int, error) {
var i16 int16
err := binary.Read(reader, byteOrder, &i16)
return int(i16), err
}
func readBoolFromInt8(reader io.Reader) (bool, error) {
var i8 int8
err := binary.Read(reader, byteOrder, &i8)
return i8 == 1, err
}
func checkInvVersion(reader io.Reader) error {
var magic uint32
if err := binary.Read(reader, byteOrder, &magic); err != nil {
return err
}
if magic != magicNumber {
return errors.New("cannot read non-invoices inv file")
}
var version uint16
if err := binary.Read(reader, byteOrder, &version); err != nil {
return err
}
if version > fileVersion {
return fmt.Errorf("version %d is too new to read", version)
}
return nil
}
func readInvInvoice(reader io.Reader) (invoice *Invoice, err error) {
invoice = &Invoice{}
for _, pId := range []*int{&invoice.Id, &invoice.CustomerId} {
if *pId, err = readIntFromInt32(reader); err != nil {
return nil, err
}
}
for _, pDate := range []*time.Time{&invoice.Raised, &invoice.Due} {
if *pDate, err = readInvDate(reader); err != nil {
return nil, err
}
}
if invoice.Paid, err = readBoolFromInt8(reader); err != nil {
return nil, err
}
if invoice.Note, err = readInvString(reader); err != nil {
return nil, err
}
var count int
if count, err = readIntFromInt32(reader); err != nil {
return nil, err
}
invoice.Items, err = readInvItems(reader, count)
return invoice, err
}
func readInvItems(reader io.Reader, count int) ([]*Item, error) {
items := make([]*Item, 0, count)
for i := 0; i < count; i++ {
item, err := readInvItem(reader)
if err != nil {
return nil, err
}
items = append(items, item)
}
return items, nil
}
func readInvDate(reader io.Reader) (time.Time, error) {
var n int32
if err := binary.Read(reader, byteOrder, &n); err != nil {
return time.Time{}, err
}
return time.Parse(invDateFormat, fmt.Sprint(n))
}
func readInvString(reader io.Reader) (string, error) {
var length int32
if err := binary.Read(reader, byteOrder, &length); err != nil {
return "", nil
}
raw := make([]byte, length)
if err := binary.Read(reader, byteOrder, &raw); err != nil {
return "", err
}
return string(raw), nil
}
func readInvItem(reader io.Reader) (item *Item, err error) {
item = &Item{}
if item.Id, err = readInvString(reader); err != nil {
return nil, err
}
if err = binary.Read(reader, byteOrder, &item.Price); err != nil {
return nil, err
}
if item.Quantity, err = readIntFromInt16(reader); err != nil {
return nil, err
}
item.Note, err = readInvString(reader)
return item, nil
}

@ -0,0 +1,205 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"compress/gzip"
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"time"
)
const (
fileType = "INVOICES" // Used by text formats
magicNumber = 0x125D // Used by binary formats
fileVersion = 100 // Used by all formats
dateFormat = "2006-01-02" // This date must always be used
nanosecondsToSeconds = 1e9
)
type Invoice struct {
Id int
CustomerId int
Raised time.Time
Due time.Time
Paid bool
Note string
Items []*Item
}
type Item struct {
Id string
Price float64
Quantity int
Note string
}
type InvoicesMarshaler interface {
MarshalInvoices(writer io.Writer, invoices []*Invoice) error
}
type InvoicesUnmarshaler interface {
UnmarshalInvoices(reader io.Reader) ([]*Invoice, error)
}
func main() {
log.SetFlags(0)
report := false
args := os.Args[1:]
if len(args) > 0 && (args[0] == "-t" || args[0] == "--time") {
report = true
args = args[1:]
}
if len(args) != 2 || args[0] == "-h" || args[0] == "--help" {
log.Fatalf("usage: %s [-t|--time] infile.ext outfile.ext\n"+
".ext may be any of .gob, .inv, .jsn, .json, .txt, "+
"or .xml, optionally gzipped (e.g., .gob.gz)\n",
filepath.Base(os.Args[0]))
}
inFilename, outFilename := args[0], args[1]
if inFilename == outFilename {
log.Fatalln("won't overwrite a file with itself")
}
start := time.Now()
invoices, err := readInvoiceFile(inFilename)
if err == nil && report {
duration := time.Now().Sub(start)
fmt.Printf("Read %s in %.3f seconds\n", inFilename,
float64(duration)/nanosecondsToSeconds)
}
if err != nil {
log.Fatalln("Failed to read:", err)
}
start = time.Now()
err = writeInvoiceFile(outFilename, invoices)
if err == nil && report {
duration := time.Now().Sub(start)
fmt.Printf("Wrote %s in %.3f seconds\n", outFilename,
float64(duration)/nanosecondsToSeconds)
}
if err != nil {
log.Fatalln("Failed to write:", err)
}
}
func readInvoiceFile(filename string) ([]*Invoice, error) {
file, closer, err := openInvoiceFile(filename)
if closer != nil {
defer closer()
}
if err != nil {
return nil, err
}
return readInvoices(file, suffixOf(filename))
}
func openInvoiceFile(filename string) (io.ReadCloser, func(), error) {
file, err := os.Open(filename)
if err != nil {
return nil, nil, err
}
closer := func() { file.Close() }
var reader io.ReadCloser = file
var decompressor *gzip.Reader
if strings.HasSuffix(filename, ".gz") {
if decompressor, err = gzip.NewReader(file); err != nil {
return file, closer, err
}
closer = func() { decompressor.Close(); file.Close() }
reader = decompressor
}
return reader, closer, nil
}
func readInvoices(reader io.Reader, suffix string) ([]*Invoice, error) {
var unmarshaler InvoicesUnmarshaler
switch suffix {
case ".gob":
unmarshaler = GobMarshaler{}
case ".inv":
unmarshaler = InvMarshaler{}
case ".jsn", ".json":
unmarshaler = JSONMarshaler{}
case ".txt":
unmarshaler = TxtMarshaler{}
case ".xml":
unmarshaler = XMLMarshaler{}
}
if unmarshaler != nil {
return unmarshaler.UnmarshalInvoices(reader)
}
return nil, fmt.Errorf("unrecognized input suffix: %s", suffix)
}
func writeInvoiceFile(filename string, invoices []*Invoice) error {
file, closer, err := createInvoiceFile(filename)
if closer != nil {
defer closer()
}
if err != nil {
return err
}
return writeInvoices(file, suffixOf(filename), invoices)
}
func createInvoiceFile(filename string) (io.WriteCloser, func(), error) {
file, err := os.Create(filename)
if err != nil {
return nil, nil, err
}
closer := func() { file.Close() }
var writer io.WriteCloser = file
var compressor *gzip.Writer
if strings.HasSuffix(filename, ".gz") {
compressor = gzip.NewWriter(file)
closer = func() { compressor.Close(); file.Close() }
writer = compressor
}
return writer, closer, nil
}
func writeInvoices(writer io.Writer, suffix string,
invoices []*Invoice) error {
var marshaler InvoicesMarshaler
switch suffix {
case ".gob":
marshaler = GobMarshaler{}
case ".inv":
marshaler = InvMarshaler{}
case ".jsn", ".json":
marshaler = JSONMarshaler{}
case ".txt":
marshaler = TxtMarshaler{}
case ".xml":
marshaler = XMLMarshaler{}
}
if marshaler != nil {
return marshaler.MarshalInvoices(writer, invoices)
}
return errors.New("unrecognized output suffix")
}
func suffixOf(filename string) string {
suffix := filepath.Ext(filename)
if suffix == ".gz" {
suffix = filepath.Ext(filename[:len(filename)-3])
}
return suffix
}

Binary file not shown.

106
src/invoicedata/jsn.go Normal file

@ -0,0 +1,106 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"time"
)
type JSONInvoice struct {
Id int
CustomerId int
Raised string // time.Time in Invoice struct
Due string // time.Time in Invoice struct
Paid bool
Note string
Items []*Item
}
func (invoice Invoice) MarshalJSON() ([]byte, error) {
jsonInvoice := JSONInvoice{
invoice.Id,
invoice.CustomerId,
invoice.Raised.Format(dateFormat),
invoice.Due.Format(dateFormat),
invoice.Paid,
invoice.Note,
invoice.Items,
}
return json.Marshal(jsonInvoice)
}
func (invoice *Invoice) UnmarshalJSON(data []byte) (err error) {
var jsonInvoice JSONInvoice
if err = json.Unmarshal(data, &jsonInvoice); err != nil {
return err
}
var raised, due time.Time
if raised, err = time.Parse(dateFormat, jsonInvoice.Raised);
err != nil {
return err
}
if due, err = time.Parse(dateFormat, jsonInvoice.Due); err != nil {
return err
}
*invoice = Invoice{
jsonInvoice.Id,
jsonInvoice.CustomerId,
raised,
due,
jsonInvoice.Paid,
jsonInvoice.Note,
jsonInvoice.Items,
}
return nil
}
type JSONMarshaler struct{}
func (JSONMarshaler) MarshalInvoices(writer io.Writer,
invoices []*Invoice) error {
encoder := json.NewEncoder(writer)
if err := encoder.Encode(fileType); err != nil {
return err
}
if err := encoder.Encode(fileVersion); err != nil {
return err
}
return encoder.Encode(invoices)
}
func (JSONMarshaler) UnmarshalInvoices(reader io.Reader) ([]*Invoice,
error) {
decoder := json.NewDecoder(reader)
var kind string
if err := decoder.Decode(&kind); err != nil {
return nil, err
}
if kind != fileType {
return nil, errors.New("cannot read non-invoices json file")
}
var version int
if err := decoder.Decode(&version); err != nil {
return nil, err
}
if version > fileVersion {
return nil, fmt.Errorf("version %d is too new to read", version)
}
var invoices []*Invoice
err := decoder.Decode(&invoices)
return invoices, err
}

167
src/invoicedata/txt.go Normal file

@ -0,0 +1,167 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
"errors"
"fmt"
"io"
"strings"
"time"
)
const noteSep = ":"
type TxtMarshaler struct{}
func (TxtMarshaler) MarshalInvoices(writer io.Writer,
invoices []*Invoice) error {
bufferedWriter := bufio.NewWriter(writer)
defer bufferedWriter.Flush()
var write writerFunc = func(format string,
args ...interface{}) error {
_, err := fmt.Fprintf(bufferedWriter, format, args...)
return err
}
if err := write("%s %d\n", fileType, fileVersion); err != nil {
return err
}
for _, invoice := range invoices {
if err := write.writeInvoice(invoice); err != nil {
return err
}
}
return nil
}
type writerFunc func(string, ...interface{}) error
func (write writerFunc) writeInvoice(invoice *Invoice) error {
note := ""
if invoice.Note != "" {
note = noteSep + " " + invoice.Note
}
if err := write("INVOICE ID=%d CUSTOMER=%d RAISED=%s DUE=%s "+
"PAID=%t%s\n", invoice.Id, invoice.CustomerId,
invoice.Raised.Format(dateFormat),
invoice.Due.Format(dateFormat), invoice.Paid, note); err != nil {
return err
}
if err := write.writeItems(invoice.Items); err != nil {
return err
}
return write("\f\n")
}
func (write writerFunc) writeItems(items []*Item) error {
for _, item := range items {
note := ""
if item.Note != "" {
note = noteSep + " " + item.Note
}
if err := write("ITEM ID=%s PRICE=%.2f QUANTITY=%d%s\n", item.Id,
item.Price, item.Quantity, note); err != nil {
return err
}
}
return nil
}
func (TxtMarshaler) UnmarshalInvoices(reader io.Reader) ([]*Invoice,
error) {
bufferedReader := bufio.NewReader(reader)
if err := checkTxtVersion(bufferedReader); err != nil {
return nil, err
}
var invoices []*Invoice
eof := false
for lino := 2; !eof; lino++ {
line, err := bufferedReader.ReadString('\n')
if err == io.EOF {
err = nil // io.EOF isn't really an error
eof = true // this will end the loop at the next iteration
} else if err != nil {
return nil, err // finish immediately for real errors
}
if invoices, err = parseTxtLine(lino, line, invoices); err != nil {
return nil, err
}
}
return invoices, nil
}
func checkTxtVersion(bufferedReader *bufio.Reader) error {
var version int
if _, err := fmt.Fscanf(bufferedReader, "INVOICES %d\n", &version);
err != nil {
return errors.New("cannot read non-invoices text file")
} else if version > fileVersion {
return fmt.Errorf("version %d is too new to read", version)
}
return nil
}
func parseTxtLine(lino int, line string, invoices []*Invoice) ([]*Invoice,
error) {
var err error
if strings.HasPrefix(line, "INVOICE") {
var invoice *Invoice
invoice, err = parseTxtInvoice(lino, line)
invoices = append(invoices, invoice)
} else if strings.HasPrefix(line, "ITEM") {
if len(invoices) == 0 {
err = fmt.Errorf("item outside of an invoice line %d", lino)
} else {
var item *Item
item, err = parseTxtItem(lino, line)
items := &invoices[len(invoices)-1].Items
*items = append(*items, item)
}
}
return invoices, err
}
func parseTxtInvoice(lino int, line string) (invoice *Invoice,
err error) {
invoice = &Invoice{}
var raised, due string
if _, err = fmt.Sscanf(line, "INVOICE ID=%d CUSTOMER=%d "+
"RAISED=%s DUE=%s PAID=%t", &invoice.Id, &invoice.CustomerId,
&raised, &due, &invoice.Paid); err != nil {
return nil, fmt.Errorf("invalid invoice %v line %d", err, lino)
}
if invoice.Raised, err = time.Parse(dateFormat, raised); err != nil {
return nil, fmt.Errorf("invalid raised %v line %d", err, lino)
}
if invoice.Due, err = time.Parse(dateFormat, due); err != nil {
return nil, fmt.Errorf("invalid due %v line %d", err, lino)
}
if i := strings.Index(line, noteSep); i > -1 {
invoice.Note = strings.TrimSpace(line[i+len(noteSep):])
}
return invoice, nil
}
func parseTxtItem(lino int, line string) (item *Item, err error) {
item = &Item{}
if _, err = fmt.Sscanf(line, "ITEM ID=%s PRICE=%f QUANTITY=%d",
&item.Id, &item.Price, &item.Quantity); err != nil {
return nil, fmt.Errorf("invalid item %v line %d", err, lino)
}
if i := strings.Index(line, noteSep); i > -1 {
item.Note = strings.TrimSpace(line[i+len(noteSep):])
}
return item, nil
}

154
src/invoicedata/xml.go Normal file

@ -0,0 +1,154 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/xml"
"fmt"
"io"
"strings"
"time"
)
type XMLMarshaler struct{}
type XMLInvoices struct {
XMLName xml.Name `xml:"INVOICES"`
Version int `xml:"version,attr"`
Invoice []*XMLInvoice `xml:"INVOICE"`
}
type XMLInvoice struct {
XMLName xml.Name `xml:"INVOICE"`
Id int `xml:",attr"`
CustomerId int `xml:",attr"`
Raised string `xml:",attr"`
Due string `xml:",attr"`
Paid bool `xml:",attr"`
Note string `xml:"NOTE"`
Item []*XMLItem `xml:"ITEM"`
}
type XMLItem struct {
XMLName xml.Name `xml:"ITEM"`
Id string `xml:",attr"`
Price float64 `xml:",attr"`
Quantity int `xml:",attr"`
Note string `xml:"NOTE"`
}
func XMLInvoicesForInvoices(invoices []*Invoice) *XMLInvoices {
xmlInvoices := &XMLInvoices{
Version: fileVersion,
Invoice: make([]*XMLInvoice, 0, len(invoices)),
}
for _, invoice := range invoices {
xmlInvoices.Invoice = append(xmlInvoices.Invoice,
XMLInvoiceForInvoice(invoice))
}
return xmlInvoices
}
func XMLInvoiceForInvoice(invoice *Invoice) *XMLInvoice {
xmlInvoice := &XMLInvoice{
Id: invoice.Id,
CustomerId: invoice.CustomerId,
Raised: invoice.Raised.Format(dateFormat),
Due: invoice.Due.Format(dateFormat),
Paid: invoice.Paid,
Note: invoice.Note,
Item: make([]*XMLItem, 0, len(invoice.Items)),
}
for _, item := range invoice.Items {
xmlItem := &XMLItem{
Id: item.Id,
Price: item.Price,
Quantity: item.Quantity,
Note: item.Note,
}
xmlInvoice.Item = append(xmlInvoice.Item, xmlItem)
}
return xmlInvoice
}
func (xmlInvoices *XMLInvoices) Invoices() (invoices []*Invoice,
err error) {
invoices = make([]*Invoice, 0, len(xmlInvoices.Invoice))
for _, xmlInvoice := range xmlInvoices.Invoice {
invoice, err := xmlInvoice.Invoice()
if err != nil {
return nil, err
}
invoices = append(invoices, invoice)
}
return invoices, nil
}
func (xmlInvoice *XMLInvoice) Invoice() (invoice *Invoice, err error) {
invoice = &Invoice{
Id: xmlInvoice.Id,
CustomerId: xmlInvoice.CustomerId,
Paid: xmlInvoice.Paid,
Note: strings.TrimSpace(xmlInvoice.Note),
Items: make([]*Item, 0, len(xmlInvoice.Item)),
}
if invoice.Raised, err = time.Parse(dateFormat, xmlInvoice.Raised);
err != nil {
return nil, err
}
if invoice.Due, err = time.Parse(dateFormat, xmlInvoice.Due);
err != nil {
return nil, err
}
for _, xmlItem := range xmlInvoice.Item {
item := &Item{
Id: xmlItem.Id,
Price: xmlItem.Price,
Quantity: xmlItem.Quantity,
Note: strings.TrimSpace(xmlItem.Note),
}
invoice.Items = append(invoice.Items, item)
}
return invoice, nil
}
func (XMLMarshaler) MarshalInvoices(writer io.Writer,
invoices []*Invoice) error {
if _, err := writer.Write([]byte(xml.Header)); err != nil {
return err
}
xmlInvoices := XMLInvoicesForInvoices(invoices)
encoder := xml.NewEncoder(writer)
return encoder.Encode(xmlInvoices)
}
func (XMLMarshaler) UnmarshalInvoices(reader io.Reader) ([]*Invoice,
error) {
xmlInvoices := &XMLInvoices{}
decoder := xml.NewDecoder(reader)
if err := decoder.Decode(xmlInvoices); err != nil {
return nil, err
}
if xmlInvoices.Version > fileVersion {
return nil, fmt.Errorf("version %d is too new to read",
xmlInvoices.Version)
}
return xmlInvoices.Invoices()
}

116
src/invoicedata_ans/gob.go Normal file

@ -0,0 +1,116 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
// "bytes"
"encoding/gob"
"errors"
"fmt"
"io"
// "time"
)
/*
type GobInvoice struct {
Id int
CustomerId int
DepartmentId string
Raised int64
Due int64
Paid bool
Note string
Items []*Item
}
func (invoice *Invoice) GobEncode() ([]byte, error) {
gobInvoice := GobInvoice{
invoice.Id,
invoice.CustomerId,
invoice.DepartmentId,
invoice.Raised.Unix(),
invoice.Due.Unix(),
invoice.Paid,
invoice.Note,
invoice.Items,
}
var buffer bytes.Buffer
encoder := gob.NewEncoder(&buffer)
err := encoder.Encode(gobInvoice)
return buffer.Bytes(), err
}
func (invoice *Invoice) GobDecode(data []byte) error {
var gobInvoice GobInvoice
buffer := bytes.NewBuffer(data)
decoder := gob.NewDecoder(buffer)
if err := decoder.Decode(&gobInvoice); err != nil {
return err
}
raised := time.Unix(gobInvoice.Raised, 0)
due := time.Unix(gobInvoice.Due, 0)
*invoice = Invoice{
gobInvoice.Id,
gobInvoice.CustomerId,
gobInvoice.DepartmentId,
raised,
due,
gobInvoice.Paid,
gobInvoice.Note,
gobInvoice.Items,
}
return nil
}
*/
type GobMarshaler struct{}
func (GobMarshaler) MarshalInvoices(writer io.Writer,
invoices []*Invoice) error {
encoder := gob.NewEncoder(writer)
if err := encoder.Encode(magicNumber); err != nil {
return err
}
if err := encoder.Encode(fileVersion); err != nil {
return err
}
return encoder.Encode(invoices)
}
func (GobMarshaler) UnmarshalInvoices(reader io.Reader) ([]*Invoice,
error) {
decoder := gob.NewDecoder(reader)
var magic int
if err := decoder.Decode(&magic); err != nil {
return nil, err
}
if magic != magicNumber {
return nil, errors.New("cannot read non-invoices gob file")
}
var version int
if err := decoder.Decode(&version); err != nil {
return nil, err
}
if version > fileVersion {
return nil, fmt.Errorf("version %d is too new to read", version)
}
var invoices []*Invoice
if err := decoder.Decode(&invoices); err != nil {
return nil, err
}
if version < fileVersion {
if err := update(invoices); err != nil {
return nil, err
}
}
return invoices, nil
}

277
src/invoicedata_ans/inv.go Normal file

@ -0,0 +1,277 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/binary"
"errors"
"fmt"
"io"
"strconv"
"time"
)
type InvMarshaler struct{}
const invDateFormat = "20060102"
var byteOrder = binary.LittleEndian
func (InvMarshaler) MarshalInvoices(writer io.Writer,
invoices []*Invoice) error {
var write invWriterFunc = func(x interface{}) error {
return binary.Write(writer, byteOrder, x)
}
if err := write(uint32(magicNumber)); err != nil {
return err
}
if err := write(uint16(fileVersion)); err != nil {
return err
}
if err := write(int32(len(invoices))); err != nil {
return err
}
for _, invoice := range invoices {
if err := write.writeInvoice(invoice); err != nil {
return err
}
}
return nil
}
type invWriterFunc func(interface{}) error
func (write invWriterFunc) writeInvoice(invoice *Invoice) error {
for _, i := range []int{invoice.Id, invoice.CustomerId} {
if err := write(int32(i)); err != nil {
return err
}
}
if err := write.writeString(invoice.DepartmentId); err != nil {
return err
}
for _, date := range []time.Time{invoice.Raised, invoice.Due} {
if err := write.writeDate(date); err != nil {
return err
}
}
if err := write.writeBool(invoice.Paid); err != nil {
return err
}
if err := write.writeString(invoice.Note); err != nil {
return err
}
if err := write(int32(len(invoice.Items))); err != nil {
return err
}
for _, item := range invoice.Items {
if err := write.writeItem(item); err != nil {
return err
}
}
return nil
}
func (write invWriterFunc) writeDate(date time.Time) error {
i, err := strconv.Atoi(date.Format(invDateFormat))
if err != nil {
return err
}
return write(int32(i))
}
func (write invWriterFunc) writeBool(b bool) error {
var v int8
if b {
v = 1
}
return write(v)
}
func (write invWriterFunc) writeString(s string) error {
if err := write(int32(len(s))); err != nil {
return err
}
return write([]byte(s))
}
func (write invWriterFunc) writeItem(item *Item) error {
if err := write.writeString(item.Id); err != nil {
return err
}
if err := write(item.Price); err != nil {
return err
}
for _, i := range []int{item.Quantity, item.TaxBand} {
if err := write(int16(i)); err != nil {
return err
}
}
return write.writeString(item.Note)
}
func (InvMarshaler) UnmarshalInvoices(reader io.Reader) ([]*Invoice,
error) {
version, err := checkInvVersion(reader)
if err != nil {
return nil, err
}
count, err := readIntFromInt32(reader)
if err != nil {
return nil, err
}
invoices := make([]*Invoice, 0, count)
for i := 0; i < count; i++ {
invoice, err := readInvInvoice(version, reader)
if err != nil {
return nil, err
}
invoices = append(invoices, invoice)
}
return invoices, nil
}
func readIntFromInt32(reader io.Reader) (int, error) {
var i32 int32
err := binary.Read(reader, byteOrder, &i32)
return int(i32), err
}
func readIntFromInt16(reader io.Reader) (int, error) {
var i16 int16
err := binary.Read(reader, byteOrder, &i16)
return int(i16), err
}
func readBoolFromInt8(reader io.Reader) (bool, error) {
var i8 int8
err := binary.Read(reader, byteOrder, &i8)
return i8 == 1, err
}
func checkInvVersion(reader io.Reader) (int, error) {
var magic uint32
if err := binary.Read(reader, byteOrder, &magic); err != nil {
return 0, err
}
if magic != magicNumber {
return 0, errors.New("cannot read non-invoices inv file")
}
var version uint16
if err := binary.Read(reader, byteOrder, &version); err != nil {
return 0, err
}
if version > fileVersion {
return 0, fmt.Errorf("version %d is too new to read", version)
}
return int(version), nil
}
func readInvInvoice(version int, reader io.Reader) (invoice *Invoice,
err error) {
invoice = &Invoice{}
for _, i := range []*int{&invoice.Id, &invoice.CustomerId} {
if *i, err = readIntFromInt32(reader); err != nil {
return nil, err
}
}
if version == fileVersion {
if invoice.DepartmentId, err = readInvString(reader); err != nil {
return nil, err
}
}
for _, date := range []*time.Time{&invoice.Raised, &invoice.Due} {
if *date, err = readInvDate(reader); err != nil {
return nil, err
}
}
if invoice.Paid, err = readBoolFromInt8(reader); err != nil {
return nil, err
}
if invoice.Note, err = readInvString(reader); err != nil {
return nil, err
}
var count int
if count, err = readIntFromInt32(reader); err != nil {
return nil, err
}
if invoice.Items, err = readInvItems(version, reader, count);
err != nil {
return nil, err
}
if version < fileVersion {
updateInvoice(invoice)
}
return invoice, nil
}
func readInvItems(version int, reader io.Reader, count int) ([]*Item,
error) {
items := make([]*Item, 0, count)
for i := 0; i < count; i++ {
item, err := readInvItem(version, reader)
if err != nil {
return nil, err
}
items = append(items, item)
}
return items, nil
}
func readInvDate(reader io.Reader) (time.Time, error) {
var n int32
if err := binary.Read(reader, byteOrder, &n); err != nil {
return time.Time{}, err
}
return time.Parse(invDateFormat, fmt.Sprint(n))
}
func readInvString(reader io.Reader) (string, error) {
var length int32
if err := binary.Read(reader, byteOrder, &length); err != nil {
return "", err
}
raw := make([]byte, length)
if err := binary.Read(reader, byteOrder, &raw); err != nil {
return "", err
}
return string(raw), nil
}
func readInvItem(version int, reader io.Reader) (item *Item, err error) {
item = &Item{}
if item.Id, err = readInvString(reader); err != nil {
return nil, err
}
if err = binary.Read(reader, byteOrder, &item.Price); err != nil {
return nil, err
}
if item.Quantity, err = readIntFromInt16(reader); err != nil {
return nil, err
}
if version == fileVersion {
if item.TaxBand, err = readIntFromInt16(reader); err != nil {
return nil, err
}
}
if item.Note, err = readInvString(reader); err != nil {
return nil, err
}
if version < fileVersion {
if err = updateItem(item); err != nil {
return nil, err
}
}
return item, nil
}

@ -0,0 +1,228 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"compress/gzip"
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
const (
magicNumber = 0x125D
fileVersion = 101
fileType = "INVOICES"
dateFormat = "2006-01-02" // This date must always be used (see text).
)
type Invoice struct { // fileVersion
Id int // 100
CustomerId int // 100
DepartmentId string // 101
Raised time.Time // 100
Due time.Time // 100
Paid bool // 100
Note string // 100
Items []*Item // 100
}
type Item struct { // fileVersion
Id string // 100
Price float64 // 100
Quantity int // 100
TaxBand int // 101
Note string // 100
}
type InvoicesMarshaler interface {
MarshalInvoices(writer io.Writer, invoices []*Invoice) error
}
type InvoicesUnmarshaler interface {
UnmarshalInvoices(reader io.Reader) ([]*Invoice, error)
}
func main() {
log.SetFlags(0)
if len(os.Args) != 3 || os.Args[1] == "-h" || os.Args[1] == "--help" {
log.Fatalf("usage: %s infile.ext outfile.ext\n"+
".ext may be any of .gob, .inv, .jsn, .json, .txt, "+
"or .xml, optionally gzipped (e.g., .gob.gz)\n",
filepath.Base(os.Args[0]))
}
inFilename, outFilename := os.Args[1], os.Args[2]
if inFilename == outFilename {
log.Fatalln("won't overwrite a file with itself")
}
invoices, err := readInvoiceFile(inFilename)
if err != nil {
log.Fatalln("Failed to read:", err)
}
if err := writeInvoiceFile(outFilename, invoices); err != nil {
log.Fatalln("Failed to write:", err)
}
}
func readInvoiceFile(filename string) ([]*Invoice, error) {
file, closer, err := openInvoiceFile(filename)
if closer != nil {
defer closer()
}
if err != nil {
return nil, err
}
return readInvoices(file, suffixOf(filename))
}
func openInvoiceFile(filename string) (io.ReadCloser, func(), error) {
file, err := os.Open(filename)
if err != nil {
return nil, nil, err
}
closer := func() { file.Close() }
var reader io.ReadCloser = file
var decompressor *gzip.Reader
if strings.HasSuffix(filename, ".gz") {
if decompressor, err = gzip.NewReader(file); err != nil {
return file, closer, err
}
closer = func() { decompressor.Close(); file.Close() }
reader = decompressor
}
return reader, closer, nil
}
func readInvoices(reader io.Reader, suffix string) ([]*Invoice, error) {
var unmarshaler InvoicesUnmarshaler
switch suffix {
case ".gob":
unmarshaler = GobMarshaler{}
case ".inv":
unmarshaler = InvMarshaler{}
case ".jsn", ".json":
unmarshaler = JSONMarshaler{}
case ".txt":
unmarshaler = TxtMarshaler{}
case ".xml":
unmarshaler = XMLMarshaler{}
}
if unmarshaler != nil {
return unmarshaler.UnmarshalInvoices(reader)
}
return nil, fmt.Errorf("unrecognized input suffix: %s", suffix)
}
func writeInvoiceFile(filename string, invoices []*Invoice) error {
file, closer, err := createInvoiceFile(filename)
if closer != nil {
defer closer()
}
if err != nil {
return err
}
return writeInvoices(file, suffixOf(filename), invoices)
}
func createInvoiceFile(filename string) (io.WriteCloser, func(), error) {
file, err := os.Create(filename)
if err != nil {
return nil, nil, err
}
closer := func() { file.Close() }
var writer io.WriteCloser = file
var compressor *gzip.Writer
if strings.HasSuffix(filename, ".gz") {
compressor = gzip.NewWriter(file)
closer = func() { compressor.Close(); file.Close() }
writer = compressor
}
return writer, closer, nil
}
func writeInvoices(writer io.Writer, suffix string,
invoices []*Invoice) error {
var marshaler InvoicesMarshaler
switch suffix {
case ".gob":
marshaler = GobMarshaler{}
case ".inv":
marshaler = InvMarshaler{}
case ".jsn", ".json":
marshaler = JSONMarshaler{}
case ".txt":
marshaler = TxtMarshaler{}
case ".xml":
marshaler = XMLMarshaler{}
}
if marshaler != nil {
return marshaler.MarshalInvoices(writer, invoices)
}
return errors.New("unrecognized output suffix")
}
func update(invoices []*Invoice) error {
for _, invoice := range invoices {
updateInvoice(invoice)
for _, item := range invoice.Items {
if err := updateItem(item); err != nil {
return err
}
}
}
return nil
}
func suffixOf(filename string) string {
suffix := filepath.Ext(filename)
if suffix == ".gz" {
suffix = filepath.Ext(filename[:len(filename)-3])
}
return suffix
}
func updateInvoice(invoice *Invoice) {
switch {
case invoice.Id < 3000:
invoice.DepartmentId = "GEN"
case invoice.Id < 4000:
invoice.DepartmentId = "MKT"
case invoice.Id < 5000:
invoice.DepartmentId = "COM"
case invoice.Id < 6000:
invoice.DepartmentId = "EXP"
case invoice.Id < 7000:
invoice.DepartmentId = "INP"
case invoice.Id < 8000:
invoice.DepartmentId = "TZZ"
case invoice.Id < 9000:
invoice.DepartmentId = "V20"
default:
invoice.DepartmentId = "X15"
}
}
func updateItem(item *Item) (err error) {
if item.TaxBand, err = strconv.Atoi(item.Id[2:3]); err != nil {
return fmt.Errorf("invalid item ID: %s", item.Id)
}
return nil
}

141
src/invoicedata_ans/jsn.go Normal file

@ -0,0 +1,141 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"time"
)
var version int
type JSONInvoice struct {
Id int
CustomerId int
DepartmentId string
Raised string
Due string
Paid bool
Note string
Items []*Item
}
type JSONInvoice100 struct {
Id int
CustomerId int
Raised string
Due string
Paid bool
Note string
Items []*Item
}
func (invoice Invoice) MarshalJSON() ([]byte, error) {
jsonInvoice := JSONInvoice{
invoice.Id,
invoice.CustomerId,
invoice.DepartmentId,
invoice.Raised.Format(dateFormat),
invoice.Due.Format(dateFormat),
invoice.Paid,
invoice.Note,
invoice.Items,
}
return json.Marshal(jsonInvoice)
}
func (invoice *Invoice) UnmarshalJSON(data []byte) (err error) {
var jsonInvoice JSONInvoice
if version == fileVersion {
if err = json.Unmarshal(data, &jsonInvoice); err != nil {
return err
}
} else {
var jsonInvoice100 JSONInvoice100
if err = json.Unmarshal(data, &jsonInvoice100); err != nil {
return err
}
jsonInvoice = JSONInvoice{
jsonInvoice100.Id,
jsonInvoice100.CustomerId,
"",
jsonInvoice100.Raised,
jsonInvoice100.Due,
jsonInvoice100.Paid,
jsonInvoice100.Note,
jsonInvoice100.Items,
}
}
var raised, due time.Time
if raised, err = time.Parse(dateFormat, jsonInvoice.Raised); err != nil {
return err
}
if due, err = time.Parse(dateFormat, jsonInvoice.Due); err != nil {
return err
}
*invoice = Invoice{
jsonInvoice.Id,
jsonInvoice.CustomerId,
jsonInvoice.DepartmentId,
raised,
due,
jsonInvoice.Paid,
jsonInvoice.Note,
jsonInvoice.Items,
}
return nil
}
type JSONMarshaler struct{}
func (JSONMarshaler) MarshalInvoices(writer io.Writer,
invoices []*Invoice) error {
encoder := json.NewEncoder(writer)
if err := encoder.Encode(fileType); err != nil {
return err
}
if err := encoder.Encode(fileVersion); err != nil {
return err
}
return encoder.Encode(invoices)
}
func (JSONMarshaler) UnmarshalInvoices(reader io.Reader) ([]*Invoice,
error) {
decoder := json.NewDecoder(reader)
var kind string
if err := decoder.Decode(&kind); err != nil {
return nil, err
}
if kind != fileType {
return nil, errors.New("cannot read non-invoices json file")
}
if err := decoder.Decode(&version); err != nil {
return nil, err
}
if version > fileVersion {
return nil, fmt.Errorf("version %d is too new to read", version)
}
var invoices []*Invoice
if err := decoder.Decode(&invoices); err != nil {
return nil, err
}
if err := update(invoices); err != nil {
return nil, err
}
return invoices, nil
}

195
src/invoicedata_ans/txt.go Normal file

@ -0,0 +1,195 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
"errors"
"fmt"
"io"
"strings"
"time"
)
const noteSep = ":"
type TxtMarshaler struct{}
func (TxtMarshaler) MarshalInvoices(writer io.Writer,
invoices []*Invoice) error {
bufferedWriter := bufio.NewWriter(writer)
defer bufferedWriter.Flush()
var write writerFunc = func(format string,
args ...interface{}) error {
_, err := fmt.Fprintf(bufferedWriter, format, args...)
return err
}
if err := write("%s %d\n", fileType, fileVersion); err != nil {
return err
}
for _, invoice := range invoices {
if err := write.writeInvoice(invoice); err != nil {
return err
}
}
return nil
}
type writerFunc func(string, ...interface{}) error
func (write writerFunc) writeInvoice(invoice *Invoice) error {
note := ""
if invoice.Note != "" {
note = noteSep + " " + invoice.Note
}
if err := write("INVOICE ID=%d CUSTOMER=%d DEPARTMENT=%s RAISED=%s "+
"DUE=%s PAID=%t%s\n", invoice.Id, invoice.CustomerId,
invoice.DepartmentId, invoice.Raised.Format(dateFormat),
invoice.Due.Format(dateFormat), invoice.Paid, note); err != nil {
return err
}
if err := write.writeItems(invoice.Items); err != nil {
return err
}
return write("\f\n")
}
func (write writerFunc) writeItems(items []*Item) error {
for _, item := range items {
note := ""
if item.Note != "" {
note = noteSep + " " + item.Note
}
if err := write("ITEM ID=%s PRICE=%.2f QUANTITY=%d TAXBAND=%d%s\n",
item.Id, item.Price, item.Quantity, item.TaxBand, note);
err != nil {
return err
}
}
return nil
}
func (TxtMarshaler) UnmarshalInvoices(reader io.Reader) (
invoices []*Invoice, err error) {
bufferedReader := bufio.NewReader(reader)
var version int
if version, err = checkTxtVersion(bufferedReader); err != nil {
return nil, err
}
var line string
eof := false
for lino := 2; !eof; lino++ {
line, err = bufferedReader.ReadString('\n')
if err == io.EOF {
err = nil
eof = true
} else if err != nil {
return nil, err
}
if invoices, err = parseTxtLine(version, lino, line, invoices);
err != nil {
return nil, err
}
}
return invoices, nil
}
func checkTxtVersion(bufferedReader *bufio.Reader) (version int,
err error) {
if _, err = fmt.Fscanf(bufferedReader, "INVOICES %d\n", &version);
err != nil {
err = errors.New("cannot read non-invoices text file")
} else if version > fileVersion {
err = fmt.Errorf("version %d is too new to read", version)
}
return version, err
}
func parseTxtLine(version, lino int, line string, invoices []*Invoice) (
[]*Invoice, error) {
var err error
if strings.HasPrefix(line, "INVOICE") {
var invoice *Invoice
invoice, err = parseTxtInvoice(version, lino, line)
invoices = append(invoices, invoice)
} else if strings.HasPrefix(line, "ITEM") {
if len(invoices) == 0 {
err = fmt.Errorf("item outside of an invoice line %d", lino)
} else {
var item *Item
item, err = parseTxtItem(version, lino, line)
items := &invoices[len(invoices)-1].Items
*items = append(*items, item)
}
}
return invoices, err
}
func parseTxtInvoice(version, lino int, line string) (invoice *Invoice,
err error) {
invoice = &Invoice{}
var raised, due string
if version == fileVersion {
if _, err = fmt.Sscanf(line, "INVOICE ID=%d CUSTOMER=%d "+
"DEPARTMENT=%s RAISED=%s DUE=%s PAID=%t", &invoice.Id,
&invoice.CustomerId, &invoice.DepartmentId, &raised, &due,
&invoice.Paid); err != nil {
return nil, fmt.Errorf("invalid invoice %v line %d", err, lino)
}
} else {
if _, err = fmt.Sscanf(line, "INVOICE ID=%d CUSTOMER=%d "+
"RAISED=%s DUE=%s PAID=%t", &invoice.Id,
&invoice.CustomerId, &raised, &due, &invoice.Paid);
err != nil {
return nil, fmt.Errorf("invalid invoice %v line %d", err, lino)
}
}
if invoice.Raised, err = time.Parse(dateFormat, raised); err != nil {
return nil, fmt.Errorf("invalid raised %v line %d", err, lino)
}
if invoice.Due, err = time.Parse(dateFormat, due); err != nil {
return nil, fmt.Errorf("invalid due %v line %d", err, lino)
}
if i := strings.Index(line, noteSep); i > -1 {
invoice.Note = strings.TrimSpace(line[i+len(noteSep):])
}
if version < fileVersion {
updateInvoice(invoice)
}
return invoice, nil
}
func parseTxtItem(version, lino int, line string) (item *Item,
err error) {
item = &Item{}
if version == fileVersion {
if _, err = fmt.Sscanf(line, "ITEM ID=%s PRICE=%f "+
"QUANTITY=%d TAXBAND=%d", &item.Id, &item.Price,
&item.Quantity, &item.TaxBand); err != nil {
return nil, fmt.Errorf("invalid item %v line %d", err, lino)
}
} else {
if _, err = fmt.Sscanf(line, "ITEM ID=%s PRICE=%f QUANTITY=%d",
&item.Id, &item.Price, &item.Quantity); err != nil {
return nil, fmt.Errorf("invalid item %v line %d", err, lino)
}
}
if i := strings.Index(line, noteSep); i > -1 {
item.Note = strings.TrimSpace(line[i+len(noteSep):])
}
if version < fileVersion {
updateItem(item)
}
return item, nil
}

181
src/invoicedata_ans/xml.go Normal file

@ -0,0 +1,181 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/xml"
"fmt"
"io"
"strings"
"time"
)
type XMLMarshaler struct{}
type XMLInvoices struct {
XMLName xml.Name `xml:"INVOICES"`
Version int `xml:"version,attr"`
Invoice []*XMLInvoice `xml:"INVOICE"`
}
type XMLInvoice struct {
XMLName xml.Name `xml:"INVOICE"`
Id int `xml:",attr"`
CustomerId int `xml:",attr"`
DepartmentId string `xml:",attr"`
Raised string `xml:",attr"`
Due string `xml:",attr"`
Paid bool `xml:",attr"`
Note string `xml:"NOTE"`
Item []*XMLItem `xml:"ITEM"`
}
type XMLItem struct {
XMLName xml.Name `xml:"ITEM"`
Id string `xml:",attr"`
Price float64 `xml:",attr"`
Quantity int `xml:",attr"`
TaxBand int `xml:",attr"`
Note string `xml:"NOTE"`
}
func XMLInvoicesForInvoices(invoices []*Invoice) *XMLInvoices {
xmlInvoices := &XMLInvoices{
Version: fileVersion,
Invoice: make([]*XMLInvoice, 0, len(invoices)),
}
for _, invoice := range invoices {
xmlInvoices.Invoice = append(xmlInvoices.Invoice,
XMLInvoiceForInvoice(invoice))
}
return xmlInvoices
}
func XMLInvoiceForInvoice(invoice *Invoice) *XMLInvoice {
xmlInvoice := &XMLInvoice{
Id: invoice.Id,
CustomerId: invoice.CustomerId,
DepartmentId: invoice.DepartmentId,
Raised: invoice.Raised.Format(dateFormat),
Due: invoice.Due.Format(dateFormat),
Paid: invoice.Paid,
Note: invoice.Note,
Item: make([]*XMLItem, 0, len(invoice.Items)),
}
for _, item := range invoice.Items {
xmlItem := &XMLItem{
Id: item.Id,
Price: item.Price,
Quantity: item.Quantity,
TaxBand: item.TaxBand,
Note: item.Note,
}
xmlInvoice.Item = append(xmlInvoice.Item, xmlItem)
}
return xmlInvoice
}
func (xmlInvoices *XMLInvoices) Invoices(version int) (invoices []*Invoice,
err error) {
invoices = make([]*Invoice, 0, len(xmlInvoices.Invoice))
for _, xmlInvoice := range xmlInvoices.Invoice {
invoice, err := xmlInvoice.Invoice(version)
if err != nil {
return nil, err
}
invoices = append(invoices, invoice)
}
return invoices, nil
}
func (xmlInvoice *XMLInvoice) Invoice(version int) (invoice *Invoice,
err error) {
invoice = &Invoice{
Id: xmlInvoice.Id,
CustomerId: xmlInvoice.CustomerId,
Paid: xmlInvoice.Paid,
Note: strings.TrimSpace(xmlInvoice.Note),
Items: make([]*Item, 0, len(xmlInvoice.Item)),
}
if version == fileVersion {
invoice.DepartmentId = xmlInvoice.DepartmentId
}
if invoice.Raised, err = time.Parse(dateFormat, xmlInvoice.Raised);
err != nil {
return nil, err
}
if invoice.Due, err = time.Parse(dateFormat, xmlInvoice.Due);
err != nil {
return nil, err
}
if invoice.Items, err = itemsForXMLItems(version, xmlInvoice.Item);
err != nil {
return nil, err
}
if version < fileVersion {
updateInvoice(invoice)
}
return invoice, nil
}
func itemsForXMLItems(version int, xmlItems []*XMLItem) (items []*Item,
err error) {
for _, xmlItem := range xmlItems {
item := &Item{
Id: xmlItem.Id,
Price: xmlItem.Price,
Quantity: xmlItem.Quantity,
Note: strings.TrimSpace(xmlItem.Note),
}
if version == fileVersion {
item.TaxBand = xmlItem.TaxBand
} else if version < fileVersion {
if err = updateItem(item); err != nil {
return nil, err
}
}
items = append(items, item)
}
return items, nil
}
func (XMLMarshaler) MarshalInvoices(writer io.Writer,
invoices []*Invoice) error {
if _, err := writer.Write([]byte(xml.Header)); err != nil {
return err
}
xmlInvoices := XMLInvoicesForInvoices(invoices)
encoder := xml.NewEncoder(writer)
return encoder.Encode(xmlInvoices)
}
func (XMLMarshaler) UnmarshalInvoices(reader io.Reader) ([]*Invoice,
error) {
xmlInvoices := &XMLInvoices{}
decoder := xml.NewDecoder(reader)
if err := decoder.Decode(xmlInvoices); err != nil {
return nil, err
}
if xmlInvoices.Version > fileVersion {
return nil, fmt.Errorf("version %d is too new to read",
xmlInvoices.Version)
}
return xmlInvoices.Invoices(xmlInvoices.Version)
}

164
src/linkcheck/linkcheck.go Normal file

@ -0,0 +1,164 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"linkcheck/linkutil"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
)
var (
externalLinkRx *regexp.Regexp
addChannel chan string
queryChannel chan string
seenChannel chan bool
)
func init() {
externalLinkRx = regexp.MustCompile("^(http|ftp|mailto):")
// These *must* be unbuffered so that they block and properly serialize
// access to the map
addChannel = make(chan string)
queryChannel = make(chan string)
seenChannel = make(chan bool)
}
func main() {
log.SetFlags(0)
if len(os.Args) != 2 || os.Args[1] == "-h" || os.Args[1] == "--help" {
log.Fatalf("usage: %s url\n", filepath.Base(os.Args[0]))
}
href := os.Args[1]
if !strings.HasPrefix(href, "http://") {
href = "http://" + href
}
url, err := url.Parse(href)
if err != nil {
log.Fatalln("- failed to read url:", err)
}
prepareMap()
checkPage(href, "http://"+url.Host)
}
func prepareMap() {
go func() {
seen := make(map[string]bool)
for {
select {
case url := <-addChannel:
seen[url] = true
case url := <-queryChannel:
_, found := seen[url]
seenChannel <- found
}
}
}()
}
func alreadySeen(url string) bool {
queryChannel <- url
if <-seenChannel {
return true
}
addChannel <- url
return false
}
func checkPage(url, site string) {
if alreadySeen(url) {
return
}
links, err := linkutil.LinksFromURL(url)
if err != nil {
log.Println("-", err)
return
}
fmt.Println("+ read", url)
done := make(chan bool, len(links))
defer close(done)
pending := 0
var messages []string
for _, link := range links {
pending += processLink(link, site, url, &messages, done)
}
if len(messages) > 0 {
fmt.Println("+ links on", url)
for _, message := range messages {
fmt.Println(" ", message)
}
}
for i := 0; i < pending; i++ {
<-done
}
}
func processLink(link, site, url string, messages *[]string,
done chan<- bool) int {
localAndParsable, link := classifyLink(link, site)
if localAndParsable {
go func() {
checkPage(link, site)
done <- true
}()
return 1
}
if message := checkExists(link, url); message != "" {
*messages = append(*messages, message)
}
return 0
}
func classifyLink(link, site string) (bool, string) {
var local, parsable bool
url := link
lowerLink := strings.ToLower(link)
if strings.HasSuffix(lowerLink, ".htm") ||
strings.HasSuffix(lowerLink, ".html") {
parsable = true
}
if !externalLinkRx.MatchString(link) {
local = true
url = site
if link[0] != '/' && url[len(url)-1] != '/' {
url += "/"
}
url += link
}
return local && parsable, url
}
func checkExists(link, url string) string {
if alreadySeen(link) {
return ""
}
if _, err := http.Head(link); err != nil {
errStr := err.Error()
if strings.Contains(errStr, "unsupported protocol scheme") {
return fmt.Sprint("- can't check nonhttp link: ", link)
} else if strings.Contains(errStr, "connection timed out") {
return fmt.Sprint("- timed out on: ", link)
} else {
return fmt.Sprintf("- bad link %s on page %s: %v", link, url,
err)
}
}
return fmt.Sprint("+ checked ", link)
}

@ -0,0 +1,124 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link href="style.css" rel="stylesheet" type="text/css" />
<link rel="icon" href="favicon.png" type="image/png" />
<link rel="shortcut icon" href="favicon.png" type="image/png" />
<title>Qtrac Ltd.</title>
<meta name="description" content="Qtrac Ltd., home page, computer
textbooks, free and open source software, C++, Go, Lout, Qt, Python, and PyQt, training and consultancy"/>
<meta name="keywords" content="C++, Go, golang, lout, Qt, Python, PyQt, training, consultancy, programming"/>
<script src="BookImages.js"></script>
<script>
var bookImages = new BookImages();
</script>
</head>
<body>
<!-- Navigation -->
<div class="navbar">
<p class="nav-caption nav-caption-first">Qtrac Ltd.</p>
<ul class="nav">
<li class="nav-here">Home</li>
</ul>
<p class="nav-caption">Our Books</p>
<ul class="nav">
<li class="nav-link"><a href="gobook.html">Programming in Go</a></li>
<li class="nav-link"><a href="aqpbook.html">Advanced Qt Programming</a></li>
<li class="nav-link"><a href="py3book.html">Programming in Python&nbsp;3</a></li>
<li class="nav-link"><a href="pyqtbook.html">Rapid GUI Programming with Python and Qt</a></li>
<li class="nav-link"><a href="marksummerfield.html">C++ GUI Programming with Qt 4</a></li>
</ul>
<p class="nav-caption">Our Free Software</p>
<ul class="nav">
<li class="nav-link"><a href="diffpdf.html">DiffPDF</a></li>
<li class="nav-link"><a href="comparepdf.html">comparepdf</a></li>
<li class="nav-link"><a href="alt_key.html">Alt_Key</a>
<ul class="nav-sub">
<li class="nav-link-sub"><a href="alt_key_api.html">Alt_Key
Library</a></li>
</ul>
</li>
<li><a href="http://code.google.com/p/optarg/">optarg Go package</a></li>
</ul>
<p class="nav-caption">All Our Writing &amp; Software</p>
<ul class="nav">
<li class="nav-link"><a href="marksummerfield.html">Mark Summerfield</a></li>
</ul>
<a class="nav-book" id="bookurl" name="bookurl" href="aqpbook.html">
<img class="copyright" id="bookimage" alt="Book Cover" border="0" src="aqpvs.png" width="200" height="264" /></a>
<!-- Change the above src to this page's book if it is a *book*.html page -->
<!-- Footer -->
<p class="copyright">
Copyright &copy; 2006-11 <a href="about.html">Qtrac&nbsp;Ltd.</a>
All&nbsp;Rights&nbsp;Reserved.</p>
<p class="copyright">
<a href="http://endsoftpatents.org/innovating-without-patents"><img
style="border-width:0" src="esp-red_chicklet.png" width="80" height="15"></a>
</p>
</div>
<!-- Header -->
<h1 id="top" class="title">Qtrac Ltd.</h1>
<h2 class="subtitle">Quality Training Research and Consultancy</h2>
<!-- Body -->
<p>
Qtrac Ltd. (incorporating Qtraining.eu) provides training, consultancy,
programming, and documentation services, specializing in the programming
languages Python and C++, and in the Qt
application development framework&mdash;both PyQt4 and C++/Qt&nbsp;4. All
services are provided by author and programmer <a
href="marksummerfield.html">Mark Summerfield</a> (<a
href="mailto:mark@qtrac.eu">mark@qtrac.eu</a>).
</p>
<h2>Services</h2>
<ul>
<li>Consultancy, code reviews, mentoring, programming, and training in:
<ul style="list-style-type: circle;">
<li>C++, Python&nbsp;2, Python&nbsp;3, and <a
href="http://golang.org">Go</a> programming;
<li>C++/Qt&nbsp;4, PyQt4, and PySide GUI programming;
<li><a href="http://www.froglogic.com"/>Squish</a> GUI application
testing;
<li>Regular expressions.</li>
</ul>
<li>Technical authoring and editing.</li>
<li>Technical book and document typesetting using the <a
href="http://savannah.nongnu.org/projects/lout/">lout</a> typesetting
system.</li>
</ul>
<h2>Writing</h2>
<p>
Our books are listed on the right.
Our most recent publication is
<a href="aqpbook.html">Advanced Qt Programming</a>.
We are currently writing
<a href="gobook.html">Programming in Go: Creating Applications for the
21st Century</a>.
We also have a free download, <a
href="http://ptgmedia.pearsoncmg.com/imprint_downloads/informit/promotions/python/python2python3.pdf">
Moving from Python 2 to Python 3</a> (4 pages, 676K PDF)&mdash;a very
concise summary of Python&nbsp;2&nbsp;&harr;&nbsp;3.1 differences plus
new Python&nbsp;3.1/3.2 features.
</p>
<h2>Free Open Source Software</h2>
<p>
We maintain three free and open source GUI programs, the most popular of
which is <a href="diffpdf.html">DiffPDF</a>. This is a GUI application
for comparing PDF files page by page, either textually or by their
appearance. Available prebuilt for Debian, Fedora, Ubuntu,
Mac&nbsp;OS&nbsp;X, and Windows, and in source form for other platforms.
</p>
<p><a href="#top">Top</a></p><hr>
</body>
</html>

@ -0,0 +1,18 @@
#top
about.html
alt_key.html
alt_key_api.html
aqpbook.html
comparepdf.html
diffpdf.html
gobook.html
http://code.google.com/p/optarg/
http://endsoftpatents.org/innovating-without-patents
http://golang.org
http://ptgmedia.pearsoncmg.com/imprint_downloads/informit/promotions/python/python2python3.pdf
http://savannah.nongnu.org/projects/lout/
http://www.froglogic.com
mailto:mark@qtrac.eu
marksummerfield.html
py3book.html
pyqtbook.html

@ -0,0 +1,62 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package linkutil
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"regexp"
)
var hrefRx *regexp.Regexp
func init() {
hrefRx = regexp.MustCompile(`<a[^>]+href=['"]?([^'">]+)['"]?`)
}
func LinksFromURL(url string) ([]string, error) {
response, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to get page: %s", err)
}
links, err := LinksFromReader(response.Body)
if err != nil {
return nil, fmt.Errorf("failed to parse page: %s", err)
}
return links, nil
}
func LinksFromReader(reader io.Reader) ([]string, error) {
html, err := ioutil.ReadAll(reader)
if err != nil {
return nil, err
}
// FindAllSubmatch returns a slice of slices of slices!
// The outer level is each match, the next level is the groups, 0 for
// the whole match 1 for first set of ()s etc; the innermost level
// being individual () matches
uniqueLinks := make(map[string]bool)
for _, submatch := range hrefRx.FindAllSubmatch(html, -1) {
uniqueLinks[string(submatch[1])] = true
}
links := make([]string, len(uniqueLinks))
i := 0
for link := range uniqueLinks {
links[i] = link
i++
}
return links, nil
}

@ -0,0 +1,70 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package linkutil_test
import (
"bufio"
"io"
"linkcheck/linkutil"
"os"
"reflect"
"sort"
"testing"
)
func TestLinksFromReader(t *testing.T) {
file, err := os.Open("index.html")
if err != nil {
t.Fatal(err)
}
defer file.Close()
links, err := linkutil.LinksFromReader(file)
if err != nil {
t.Fatal(err)
}
sort.Strings(links)
file, err = os.Open("index.links")
if err != nil {
t.Fatal(err)
}
defer file.Close()
reader := bufio.NewReader(file)
var lines []string
for {
line, err := reader.ReadString('\n')
if line != "" {
lines = append(lines, line[:len(line)-1])
}
if err != nil {
if err != io.EOF {
t.Fatal(err)
}
break
}
}
sort.Strings(lines)
if !reflect.DeepEqual(links, lines) {
for i := 0; i < len(links); i++ {
if i < len(lines) {
if links[i] != lines[i] {
t.Fatalf("%q != %q", links[i], lines[i])
}
} else {
t.Fatalf("found more links than lines, e.g.: %q", links[i])
}
}
t.Fatalf("found fewer links than lines (%d vs. %d)", len(links),
len(lines))
}
}

@ -0,0 +1,67 @@
#EXTM3U
#EXTINF:315,David Bowie - Space Oddity
Music/David Bowie/Singles 1/01-Space Oddity.ogg
#EXTINF:-1,David Bowie - Changes
Music/David Bowie/Singles 1/02-Changes.ogg
#EXTINF:258,David Bowie - Starman
Music/David Bowie/Singles 1/03-Starman.ogg
#EXTINF:194,David Bowie - Ziggy Stardust
Music/David Bowie/Singles 1/04-Ziggy Stardust.ogg
#EXTINF:206,David Bowie - Suffragette City
Music/David Bowie/Singles 1/05-Suffragette City.ogg
#EXTINF:247,David Bowie - The Jean Genie
Music/David Bowie/Singles 1/07-The Jean Genie.ogg
#EXTINF:231,David Bowie - Life On Mars?
Music/David Bowie/Singles 1/09-Life On Mars.ogg
#EXTINF:174,David Bowie - Sorrow
Music/David Bowie/Singles 1/10-Sorrow.ogg
#EXTINF:270,David Bowie - Rebel Rebel
Music/David Bowie/Singles 1/11-Rebel Rebel.ogg
#EXTINF:178,David Bowie - Rock & Roll Suicide
Music/David Bowie/Singles 1/12-Rock and Roll Suicide.ogg
#EXTINF:364,David Bowie - Diamond Dogs
Music/David Bowie/Singles 1/13-Diamond Dogs.ogg
#EXTINF:311,David Bowie - Young Americans
Music/David Bowie/Singles 1/15-Young Americans.ogg
#EXTINF:254,David Bowie - Fame
Music/David Bowie/Singles 1/16-Fame.ogg
#EXTINF:240,David Bowie - Golden Years
Music/David Bowie/Singles 1/17-Golden Years.ogg
#EXTINF:331,David Bowie - TVC 15
Music/David Bowie/Singles 1/18-TVC 15.ogg
#EXTINF:183,David Bowie - Sound & Vision
Music/David Bowie/Singles 1/19-Sound & Vision.ogg
#EXTINF:217,David Bowie - Heroes
Music/David Bowie/Singles 2/01-Heroes.ogg
#EXTINF:214,David Bowie - Beauty & The Beast
Music/David Bowie/Singles 2/02-Beauty and The Beast.ogg
#EXTINF:197,David Bowie - Boys Keep Swinging
Music/David Bowie/Singles 2/03-Boys Keep Swinging.ogg
#EXTINF:240,David Bowie - D.J.
Music/David Bowie/Singles 2/04-D.J..ogg
#EXTINF:-1,David Bowie - Alabama Song
Music/David Bowie/Singles 2/05-Alabama Song.ogg
#EXTINF:264,David Bowie - Ashes To Ashes
Music/David Bowie/Singles 2/06-Ashes To Ashes.ogg
#EXTINF:287,David Bowie - Fashion
Music/David Bowie/Singles 2/07-Fashion.ogg
#EXTINF:311,David Bowie - Scary Monsters (And Super Creeps)
Music/David Bowie/Singles 2/08-Scary Monsters (And Super Creeps).ogg
#EXTINF:237,David Bowie - Under Pressure
Music/David Bowie/Singles 2/09-Under Pressure.ogg
#EXTINF:361,David Bowie - Wild Is The Wind
Music/David Bowie/Singles 2/10-Wild Is The Wind.ogg
#EXTINF:250,David Bowie - Let's Dance
Music/David Bowie/Singles 2/11-Let's Dance.ogg
#EXTINF:256,David Bowie - China Girl
Music/David Bowie/Singles 2/12-China Girl.ogg
#EXTINF:237,David Bowie - Modern Love
Music/David Bowie/Singles 2/13-Modern Love.ogg
#EXTINF:191,David Bowie - Blue Jean
Music/David Bowie/Singles 2/14-Blue Jean.ogg
#EXTINF:230,David Bowie - This Is Not America
Music/David Bowie/Singles 2/15-This Is Not America.ogg
#EXTINF:337,David Bowie - Absolute Beginners
Music/David Bowie/Singles 2/17-Absolute Beginners.ogg
#EXTINF:251,David Bowie - Day In Day Out
Music/David Bowie/Singles 2/18-Day In Day Out.ogg

@ -0,0 +1,102 @@
[playlist]
File1=Music/David Bowie/Singles 1/01-Space Oddity.ogg
Title1=David Bowie - Space Oddity
Length1=315
File2=Music/David Bowie/Singles 1/02-Changes.ogg
Title2=David Bowie - Changes
Length2=-1
File3=Music/David Bowie/Singles 1/03-Starman.ogg
Title3=David Bowie - Starman
Length3=258
File4=Music/David Bowie/Singles 1/04-Ziggy Stardust.ogg
Title4=David Bowie - Ziggy Stardust
Length4=194
File5=Music/David Bowie/Singles 1/05-Suffragette City.ogg
Title5=David Bowie - Suffragette City
Length5=206
File6=Music/David Bowie/Singles 1/07-The Jean Genie.ogg
Title6=David Bowie - The Jean Genie
Length6=247
File7=Music/David Bowie/Singles 1/09-Life On Mars.ogg
Title7=David Bowie - Life On Mars?
Length7=231
File8=Music/David Bowie/Singles 1/10-Sorrow.ogg
Title8=David Bowie - Sorrow
Length8=174
File9=Music/David Bowie/Singles 1/11-Rebel Rebel.ogg
Title9=David Bowie - Rebel Rebel
Length9=270
File10=Music/David Bowie/Singles 1/12-Rock and Roll Suicide.ogg
Title10=David Bowie - Rock & Roll Suicide
Length10=178
File11=Music/David Bowie/Singles 1/13-Diamond Dogs.ogg
Title11=David Bowie - Diamond Dogs
Length11=364
File12=Music/David Bowie/Singles 1/15-Young Americans.ogg
Title12=David Bowie - Young Americans
Length12=311
File13=Music/David Bowie/Singles 1/16-Fame.ogg
Title13=David Bowie - Fame
Length13=254
File14=Music/David Bowie/Singles 1/17-Golden Years.ogg
Title14=David Bowie - Golden Years
Length14=240
File15=Music/David Bowie/Singles 1/18-TVC 15.ogg
Title15=David Bowie - TVC 15
Length15=331
File16=Music/David Bowie/Singles 1/19-Sound & Vision.ogg
Title16=David Bowie - Sound & Vision
Length16=183
File17=Music/David Bowie/Singles 2/01-Heroes.ogg
Title17=David Bowie - Heroes
Length17=217
File18=Music/David Bowie/Singles 2/02-Beauty and The Beast.ogg
Title18=David Bowie - Beauty & The Beast
Length18=214
File19=Music/David Bowie/Singles 2/03-Boys Keep Swinging.ogg
Title19=David Bowie - Boys Keep Swinging
Length19=197
File20=Music/David Bowie/Singles 2/04-D.J..ogg
Title20=David Bowie - D.J.
Length20=240
File21=Music/David Bowie/Singles 2/05-Alabama Song.ogg
Title21=David Bowie - Alabama Song
Length21=-1
File22=Music/David Bowie/Singles 2/06-Ashes To Ashes.ogg
Title22=David Bowie - Ashes To Ashes
Length22=264
File23=Music/David Bowie/Singles 2/07-Fashion.ogg
Title23=David Bowie - Fashion
Length23=287
File24=Music/David Bowie/Singles 2/08-Scary Monsters (And Super Creeps).ogg
Title24=David Bowie - Scary Monsters (And Super Creeps)
Length24=311
File25=Music/David Bowie/Singles 2/09-Under Pressure.ogg
Title25=David Bowie - Under Pressure
Length25=237
File26=Music/David Bowie/Singles 2/10-Wild Is The Wind.ogg
Title26=David Bowie - Wild Is The Wind
Length26=361
File27=Music/David Bowie/Singles 2/11-Let's Dance.ogg
Title27=David Bowie - Let's Dance
Length27=250
File28=Music/David Bowie/Singles 2/12-China Girl.ogg
Title28=David Bowie - China Girl
Length28=256
File29=Music/David Bowie/Singles 2/13-Modern Love.ogg
Title29=David Bowie - Modern Love
Length29=237
File30=Music/David Bowie/Singles 2/14-Blue Jean.ogg
Title30=David Bowie - Blue Jean
Length30=191
File31=Music/David Bowie/Singles 2/15-This Is Not America.ogg
Title31=David Bowie - This Is Not America
Length31=230
File32=Music/David Bowie/Singles 2/17-Absolute Beginners.ogg
Title32=David Bowie - Absolute Beginners
Length32=337
File33=Music/David Bowie/Singles 2/18-Day In Day Out.ogg
Title33=David Bowie - Day In Day Out
Length33=251
NumberOfEntries=33
Version=2

99
src/m3u2pls/m3u2pls.go Normal file

@ -0,0 +1,99 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
"strings"
)
type Song struct {
Title string
Filename string
Seconds int
}
func main() {
if len(os.Args) == 1 || !strings.HasSuffix(os.Args[1], ".m3u") {
fmt.Printf("usage: %s <file.m3u>\n", filepath.Base(os.Args[0]))
os.Exit(1)
}
if rawBytes, err := ioutil.ReadFile(os.Args[1]); err != nil {
log.Fatal(err)
} else {
songs := readM3uPlaylist(string(rawBytes))
writePlsPlaylist(songs)
}
}
func readM3uPlaylist(data string) (songs []Song) {
var song Song
for _, line := range strings.Split(data, "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#EXTM3U") {
continue
}
if strings.HasPrefix(line, "#EXTINF:") {
song.Title, song.Seconds = parseExtinfLine(line)
} else {
song.Filename = strings.Map(mapPlatformDirSeparator, line)
}
if song.Filename != "" && song.Title != "" && song.Seconds != 0 {
songs = append(songs, song)
song = Song{}
}
}
return songs
}
func parseExtinfLine(line string) (title string, seconds int) {
if i := strings.IndexAny(line, "-0123456789"); i > -1 {
const separator = ","
line = line[i:]
if j := strings.Index(line, separator); j > -1 {
title = line[j+len(separator):]
var err error
if seconds, err = strconv.Atoi(line[:j]); err != nil {
log.Printf("failed to read the duration for '%s': %v\n",
title, err)
seconds = -1
}
}
}
return title, seconds
}
func mapPlatformDirSeparator(char rune) rune {
if char == '/' || char == '\\' {
return filepath.Separator
}
return char
}
func writePlsPlaylist(songs []Song) {
fmt.Println("[playlist]")
for i, song := range songs {
i++
fmt.Printf("File%d=%s\n", i, song.Filename)
fmt.Printf("Title%d=%s\n", i, song.Title)
fmt.Printf("Length%d=%d\n", i, song.Seconds)
}
fmt.Printf("NumberOfEntries=%d\nVersion=2\n", len(songs))
}

@ -0,0 +1,92 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"io/ioutil"
"log"
"os"
"testing"
)
func TestReadM3uPlaylist(t *testing.T) {
log.SetFlags(0)
log.Println("TEST m3u2pls")
songs := readM3uPlaylist(M3U)
for i, song := range songs {
if song.Title != ExpectedSongs[i].Title {
t.Fatalf("%q != %q", song.Title, ExpectedSongs[i].Title)
}
if song.Filename != ExpectedSongs[i].Filename {
t.Fatalf("%q != %q", song.Filename,
ExpectedSongs[i].Filename)
}
if song.Seconds != ExpectedSongs[i].Seconds {
t.Fatalf("%d != %d", song.Seconds,
ExpectedSongs[i].Seconds)
}
}
}
func TestWritePlsPlaylist(t *testing.T) {
songs := readM3uPlaylist(M3U)
var err error
reader, writer := os.Stdin, os.Stdout
if reader, writer, err = os.Pipe(); err != nil {
t.Fatal(err)
}
os.Stdout = writer
writePlsPlaylist(songs)
writer.Close()
actual, err := ioutil.ReadAll(reader)
if err != nil {
t.Fatal(err)
}
reader.Close()
if string(actual) != ExpectedPls {
t.Fatal("actual != expected")
}
}
const M3U = `#EXTM3U
#EXTINF:315,David Bowie - Space Oddity
Music/David Bowie/Singles 1/01-Space Oddity.ogg
#EXTINF:-1,David Bowie - Changes
Music/David Bowie/Singles 1/02-Changes.ogg
#EXTINF:258,David Bowie - Starman
Music/David Bowie/Singles 1/03-Starman.ogg`
var ExpectedSongs = []Song{
{"David Bowie - Space Oddity",
"Music/David Bowie/Singles 1/01-Space Oddity.ogg", 315},
{"David Bowie - Changes",
"Music/David Bowie/Singles 1/02-Changes.ogg", -1},
{"David Bowie - Starman",
"Music/David Bowie/Singles 1/03-Starman.ogg", 258},
}
var ExpectedPls = `[playlist]
File1=Music/David Bowie/Singles 1/01-Space Oddity.ogg
Title1=David Bowie - Space Oddity
Length1=315
File2=Music/David Bowie/Singles 1/02-Changes.ogg
Title2=David Bowie - Changes
Length2=-1
File3=Music/David Bowie/Singles 1/03-Starman.ogg
Title3=David Bowie - Starman
Length3=258
NumberOfEntries=3
Version=2
`

81
src/memoize/memoize.go Normal file

@ -0,0 +1,81 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bytes"
"fmt"
"strings"
)
type memoizeFunction func(int, ...int) interface{}
var Fibonacci memoizeFunction
var RomanForDecimal memoizeFunction
func init() {
// Of course the iterative version of fibonacci (that doesn't need
// memoize) is much more efficient.
Fibonacci = Memoize(func(x int, xs ...int) interface{} {
if x < 2 {
return x
}
return Fibonacci(x-1).(int) + Fibonacci(x-2).(int)
})
decimals := []int{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}
romans := []string{"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X",
"IX", "V", "IV", "I"}
RomanForDecimal = Memoize(func(x int, xs ...int) interface{} {
if x < 0 || x > 3999 {
panic("RomanForDecimal() only handles integers [0, 3999]")
}
var buffer bytes.Buffer
for i, decimal := range decimals {
remainder := x / decimal
x %= decimal
if remainder > 0 {
buffer.WriteString(strings.Repeat(romans[i], remainder))
}
}
return buffer.String()
})
}
func main() {
fmt.Println("Fibonacci(45) =", Fibonacci(45).(int))
for _, x := range []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 25, 30, 40, 50, 60, 69, 70, 80,
90, 99, 100, 200, 300, 400, 500, 600, 666, 700, 800, 900,
1000, 1009, 1444, 1666, 1945, 1997, 1999, 2000, 2008, 2010,
2012, 2500, 3000, 3999} {
fmt.Printf("%4d = %s\n", x, RomanForDecimal(x).(string))
}
}
func Memoize(function memoizeFunction) memoizeFunction {
cache := make(map[string]interface{})
return func(x int, xs ...int) interface{} {
key := fmt.Sprint(x)
for _, i := range xs {
key += fmt.Sprintf(",%d", i)
}
if value, found := cache[key]; found {
return value
}
value := function(x, xs...)
cache[key] = value
return value
}
}

@ -0,0 +1,103 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"math"
"qtrac.eu/omap"
"strings"
)
type Point struct{ X, Y int }
func (point Point) String() string {
return fmt.Sprintf("(%d, %d)", point.X, point.Y)
}
func main() {
words := []string{"Puttering", "About", "in", "a", "Small", "Land"}
wordForWord := omap.NewCaseFoldedKeyed()
fmt.Println(wordForWord.Len(), "words")
for _, word := range words {
wordForWord.Insert(word, strings.ToUpper(word))
}
wordForWord.Do(func(key, value interface{}) {
fmt.Printf("%v→%v\n", key, value)
})
fmt.Println("length before deleting:", wordForWord.Len())
_, containsSmall := wordForWord.Find("small")
fmt.Println("contains small:", containsSmall)
for _, key := range []string{"big", "medium", "small"} {
fmt.Printf("%t ", wordForWord.Delete(key))
}
_, containsSmall = wordForWord.Find("small")
fmt.Println("\nlength after deleting: ", wordForWord.Len())
fmt.Println("contains small:", containsSmall)
showMap(wordForWord, words, "words", 20)
searchMap(wordForWord, "small", "big")
fmt.Println()
distanceForPoint := omap.New(func(a, b interface{}) bool {
α, β := a.(*Point), b.(*Point)
if α.X != β.X {
return α.X < β.X
}
return α.Y < β.Y
})
fmt.Println(distanceForPoint.Len(), "points")
points := []*Point{{3, 1}, {1, 2}, {2, 3}, {1, 3}, {3, 2}, {2, 1}, {2, 2}}
for _, point := range points {
distance := math.Hypot(float64(point.X), float64(point.Y))
distanceForPoint.Insert(point, distance)
}
distanceForPoint.Do(func(key, value interface{}) {
fmt.Printf("%v → %.2v\n", key, value)
})
// No &distanceForPoint because it is already a pointer
showMap(distanceForPoint, points, "points", 5)
searchMap(distanceForPoint, &Point{1, 1}, &Point{3, 2})
}
func showMap(omap *omap.Map, data interface{}, name string,
width int) {
fmt.Println("original: ", data)
fmt.Print("omap keys: [")
gap := ""
omap.Do(func(key, _ interface{}) {
fmt.Print(gap, key)
gap = " "
})
fmt.Println("]")
fmt.Print("omap values: [")
gap = ""
omap.Do(func(_, value interface{}) {
fmt.Printf("%s%.*v", gap, width, value)
gap = " "
})
fmt.Println("]")
fmt.Println(omap.Len(), name)
}
func searchMap(omap *omap.Map, keys ...interface{}) {
for _, key := range keys {
if value, found := omap.Find(key); found {
fmt.Printf("\"%v\" is in the omap with value %v\n", key, value)
} else {
fmt.Printf("\"%v\" isn't in the omap\n", key)
}
}
}

108
src/oslice/oslice.go Normal file

@ -0,0 +1,108 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package oslice
import "strings"
func New(less func(interface{}, interface{}) bool) *Slice {
return &Slice{less: less}
}
func NewStringSlice() *Slice {
return &Slice{less: func(a, b interface{}) bool {
return a.(string) < b.(string)
}}
}
func NewCaseFoldedSlice() *Slice {
return &Slice{less: func(a, b interface{}) bool {
return strings.ToLower(a.(string)) < strings.ToLower(b.(string))
}}
}
func NewIntSlice() *Slice {
return &Slice{less: func(a, b interface{}) bool {
return a.(int) < b.(int)
}}
}
type Slice struct {
slice []interface{}
less func(interface{}, interface{}) bool
}
func (slice *Slice) Clear() {
slice.slice = nil
}
func (slice *Slice) Add(x interface{}) {
if slice.slice == nil {
slice.slice = []interface{}{x}
} else if index := bisectLeft(slice.slice, slice.less, x);
index == len(slice.slice) {
slice.slice = append(slice.slice, x)
} else { // See Chapter 4's InsertStringSlice for the logic
updatedSlice := make([]interface{}, len(slice.slice)+1)
at := copy(updatedSlice, slice.slice[:index])
at += copy(updatedSlice[at:], []interface{}{x})
copy(updatedSlice[at:], slice.slice[index:])
slice.slice = updatedSlice
}
}
func (slice *Slice) Remove(x interface{}) bool {
index := bisectLeft(slice.slice, slice.less, x)
for ; index < len(slice.slice); index++ {
if !slice.less(slice.slice[index], x) &&
!slice.less(x, slice.slice[index]) {
slice.slice = append(slice.slice[:index],
slice.slice[index+1:]...)
return true
}
}
return false
}
func (slice *Slice) Index(x interface{}) int {
index := bisectLeft(slice.slice, slice.less, x)
if index >= len(slice.slice) ||
slice.less(slice.slice[index], x) ||
slice.less(x, slice.slice[index]) {
return -1
}
return index
}
func (slice *Slice) At(index int) interface{} {
return slice.slice[index]
}
func (slice *Slice) Len() int {
return len(slice.slice)
}
// Return's the index position where x belongs in the slice
func bisectLeft(slice []interface{},
less func(interface{}, interface{}) bool, x interface{}) int {
left, right := 0, len(slice)
for left < right {
middle := int((left + right) / 2)
if less(slice[middle], x) {
left = middle + 1
} else {
right = middle
}
}
return left
}

91
src/oslice/oslice_test.go Normal file

@ -0,0 +1,91 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package oslice_test
import (
"fmt"
"oslice"
"reflect"
"testing"
)
func TestSortedIntList(t *testing.T) {
slice := oslice.NewIntSlice()
for _, x := range []int{5, 8, -1, 3, 4, 22} {
slice.Add(x)
}
checkList(slice, []int{-1, 3, 4, 5, 8, 22}, t)
for _, x := range []int{5, 5, 6} {
slice.Add(x)
}
checkList(slice, []int{-1, 3, 4, 5, 5, 5, 6, 8, 22}, t)
if slice.Index(4) != 2 {
t.Fatal("4 missing")
}
printSlice(slice)
if slice.Index(99) != -1 {
t.Fatal("99 wrongly present")
}
if slice.Remove(99) != false {
t.Fatal("99 wrongly removed")
}
if slice.Remove(5) != true {
t.Fatal("5 not removed")
}
checkList(slice, []int{-1, 3, 4, 5, 5, 6, 8, 22}, t)
if slice.Remove(5) != true {
t.Fatal("5 not removed")
}
checkList(slice, []int{-1, 3, 4, 5, 6, 8, 22}, t)
if slice.Remove(5) != true {
t.Fatal("5 not removed")
}
checkList(slice, []int{-1, 3, 4, 6, 8, 22}, t)
if slice.Index(5) != -1 {
t.Fatalf("5 wrongly present at %d", slice.Index(5))
}
printSlice(slice)
slice.Clear()
if slice.Len() != 0 {
t.Fatal("cleared list isn't empty")
}
if slice.Remove(9) != false {
t.Fatal("9 wrongly removed")
}
if slice.Index(9) != -1 {
t.Fatal("9 wrongly found")
}
}
func printSlice(slice *oslice.Slice) {
fmt.Print("[")
sep := ", "
for i := 0; i < slice.Len(); i++ {
if i+1 == slice.Len() {
sep = "]\n"
}
fmt.Print(slice.At(i), sep)
}
}
func checkList(slice *oslice.Slice, ints []int, t *testing.T) {
if slice.Len() != len(ints) {
t.Fatalf("slice.Len()=%d != %d", slice.Len(), len(ints))
}
for i := 0; i < slice.Len(); i++ {
if !reflect.DeepEqual(slice.At(i), ints[i]) {
t.Fatalf("mismtach At(%d) %v vs. %d", i, slice.At(i), ints[i])
}
}
}

171
src/pack/pack.go Normal file

@ -0,0 +1,171 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"fmt"
"io"
"log"
"os"
"path/filepath"
"runtime"
"strings"
)
func main() {
log.SetFlags(0)
if len(os.Args) < 3 || os.Args[1] == "-h" || os.Args[1] == "--help" {
fmt.Printf("usage: %s archive.{zip,tar,tar.gz} "+
"file1 [file2 [... fileN]]\n", filepath.Base(os.Args[0]))
os.Exit(1)
}
filename := os.Args[1]
if !validSuffix(filename) {
log.Fatalln("unrecognized archive suffix")
}
files := commandLineFiles(os.Args[2:])
if err := createArchive(filename, files); err != nil {
log.Fatalln(err)
}
}
func validSuffix(filename string) bool {
for _, suffix := range []string{".zip", ".tar", ".tar.gz"} {
if strings.HasSuffix(filename, suffix) {
return true
}
}
return false
}
func commandLineFiles(files []string) []string {
if runtime.GOOS == "windows" {
args := make([]string, 0, len(files))
for _, name := range files {
if matches, err := filepath.Glob(name); err != nil {
args = append(args, name) // Invalid pattern
} else if matches != nil { // At least one match
args = append(args, matches...)
}
}
return args
}
return files
}
func createArchive(filename string, files []string) error {
if strings.HasSuffix(filename, ".zip") {
return createZip(filename, files)
}
return createTar(filename, files)
}
func createZip(filename string, files []string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
zipper := zip.NewWriter(file)
defer zipper.Close()
for _, name := range files {
if err := writeFileToZip(zipper, name); err != nil {
return err
}
}
return nil
}
func writeFileToZip(zipper *zip.Writer, filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = sanitizedName(filename)
writer, err := zipper.CreateHeader(header)
if err != nil {
return err
}
_, err = io.Copy(writer, file)
return err
}
func createTar(filename string, files []string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
var fileWriter io.WriteCloser = file
if strings.HasSuffix(filename, ".gz") {
fileWriter = gzip.NewWriter(file)
defer fileWriter.Close()
}
writer := tar.NewWriter(fileWriter)
defer writer.Close()
for _, name := range files {
if err := writeFileToTar(writer, name); err != nil {
return err
}
}
return nil
}
func writeFileToTar(writer *tar.Writer, filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
return err
}
header := &tar.Header{
Name: sanitizedName(filename),
Mode: int64(stat.Mode()),
Uid: os.Getuid(),
Gid: os.Getgid(),
Size: stat.Size(),
ModTime: stat.ModTime(),
}
if err = writer.WriteHeader(header); err != nil {
return err
}
_, err = io.Copy(writer, file)
return err
}
func sanitizedName(filename string) string {
if len(filename) > 1 && filename[1] == ':' &&
runtime.GOOS == "windows" {
filename = filename[2:]
}
filename = filepath.ToSlash(filename)
filename = strings.TrimLeft(filename, "/.")
return strings.Replace(filename, "../", "", -1)
}

@ -0,0 +1,64 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"os"
"path/filepath"
"unicode/utf8"
)
var IsPalindrome func(string) bool // Holds a reference to a function
func init() {
if len(os.Args) > 1 &&
(os.Args[1] == "-a" || os.Args[1] == "--ascii") {
os.Args = append(os.Args[:1], os.Args[2:]...) // Strip out arg.
IsPalindrome = func(s string) bool { // Simple ASCII-only version
if len(s) <= 1 {
return true
}
if s[0] != s[len(s)-1] {
return false
}
return IsPalindrome(s[1 : len(s)-1])
}
} else {
IsPalindrome = func(s string) bool { // UTF-8 version
if utf8.RuneCountInString(s) <= 1 {
return true
}
first, sizeOfFirst := utf8.DecodeRuneInString(s)
last, sizeOfLast := utf8.DecodeLastRuneInString(s)
if first != last {
return false
}
return IsPalindrome(s[sizeOfFirst : len(s)-sizeOfLast])
}
}
}
func main() {
if len(os.Args) == 1 {
fmt.Printf("usage: %s [-a|--ascii] word1 [word2 [... wordN]]\n",
filepath.Base(os.Args[0]))
os.Exit(1)
}
words := os.Args[1:]
for _, word := range words {
fmt.Printf("%5t %q\n", IsPalindrome(word), word)
}
}

@ -0,0 +1,67 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"os"
"path/filepath"
"unicode/utf8"
)
var IsPalindrome func(string) bool
func init() {
if len(os.Args) > 1 &&
(os.Args[1] == "-a" || os.Args[1] == "--ascii") {
os.Args = append(os.Args[:1], os.Args[2:]...) // Strip out arg.
IsPalindrome = func(s string) bool { // Simple ASCII-only version
j := len(s) - 1
for i := 0; i < len(s)/2; i++ {
if s[i] != s[j] {
return false
}
j--
}
return true
}
} else {
IsPalindrome = func(s string) bool { // UTF-8 version
for len(s) > 0 {
first, sizeOfFirst := utf8.DecodeRuneInString(s)
if sizeOfFirst == len(s) {
break // s only has one character
}
last, sizeOfLast := utf8.DecodeLastRuneInString(s)
if first != last {
return false
}
s = s[sizeOfFirst : len(s)-sizeOfLast]
}
return true
}
}
}
func main() {
if len(os.Args) == 1 {
fmt.Printf("usage: %s [-a|--ascii] word1 [word2 [... wordN]]\n",
filepath.Base(os.Args[0]))
os.Exit(1)
}
words := os.Args[1:]
for _, word := range words {
fmt.Printf("%5t %q\n", IsPalindrome(word), word)
}
}

@ -0,0 +1,89 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Algorithm taken from:
// http://en.literateprograms.org/Pi_with_Machin%27s_formula_%28Python%29
package main
import (
"fmt"
"math/big"
"os"
"path/filepath"
"strconv"
)
func main() {
places := handleCommandLine(1000)
scaledPi := fmt.Sprint(π(places))
fmt.Printf("3.%s\n", scaledPi[1:])
}
func handleCommandLine(defaultValue int) int {
if len(os.Args) > 1 {
if os.Args[1] == "-h" || os.Args[1] == "--help" {
usage := "usage: %s [digits]\n e.g.: %s 10000"
app := filepath.Base(os.Args[0])
fmt.Fprintln(os.Stderr, fmt.Sprintf(usage, app, app))
os.Exit(1)
}
if x, err := strconv.Atoi(os.Args[1]); err != nil {
fmt.Fprintf(os.Stderr, "ignoring invalid number of "+
"digits: will display %d\n", defaultValue)
} else {
return x
}
}
return defaultValue
}
func π(places int) *big.Int {
digits := big.NewInt(int64(places))
unity := big.NewInt(0)
ten := big.NewInt(10)
exponent := big.NewInt(0)
unity.Exp(ten, exponent.Add(digits, ten), nil)
pi := big.NewInt(4)
left := arccot(big.NewInt(5), unity)
left.Mul(left, big.NewInt(4))
right := arccot(big.NewInt(239), unity)
left.Sub(left, right)
pi.Mul(pi, left)
return pi.Div(pi, big.NewInt(0).Exp(ten, ten, nil))
}
func arccot(x, unity *big.Int) *big.Int {
sum := big.NewInt(0)
sum.Div(unity, x)
xpower := big.NewInt(0)
xpower.Div(unity, x)
n := big.NewInt(3)
sign := big.NewInt(-1)
zero := big.NewInt(0)
square := big.NewInt(0)
square.Mul(x, x)
for {
xpower.Div(xpower, square)
term := big.NewInt(0)
term.Div(xpower, n)
if term.Cmp(zero) == 0 {
break
}
addend := big.NewInt(0)
sum.Add(sum, addend.Mul(sign, term))
sign.Neg(sign)
n.Add(n, big.NewInt(2))
}
return sum
}

@ -0,0 +1,67 @@
#EXTM3U
#EXTINF:315,David Bowie - Space Oddity
Music/David Bowie/Singles 1/01-Space Oddity.ogg
#EXTINF:-1,David Bowie - Changes
Music/David Bowie/Singles 1/02-Changes.ogg
#EXTINF:258,David Bowie - Starman
Music/David Bowie/Singles 1/03-Starman.ogg
#EXTINF:194,David Bowie - Ziggy Stardust
Music/David Bowie/Singles 1/04-Ziggy Stardust.ogg
#EXTINF:206,David Bowie - Suffragette City
Music/David Bowie/Singles 1/05-Suffragette City.ogg
#EXTINF:247,David Bowie - The Jean Genie
Music/David Bowie/Singles 1/07-The Jean Genie.ogg
#EXTINF:231,David Bowie - Life On Mars?
Music/David Bowie/Singles 1/09-Life On Mars.ogg
#EXTINF:174,David Bowie - Sorrow
Music/David Bowie/Singles 1/10-Sorrow.ogg
#EXTINF:270,David Bowie - Rebel Rebel
Music/David Bowie/Singles 1/11-Rebel Rebel.ogg
#EXTINF:178,David Bowie - Rock & Roll Suicide
Music/David Bowie/Singles 1/12-Rock and Roll Suicide.ogg
#EXTINF:364,David Bowie - Diamond Dogs
Music/David Bowie/Singles 1/13-Diamond Dogs.ogg
#EXTINF:311,David Bowie - Young Americans
Music/David Bowie/Singles 1/15-Young Americans.ogg
#EXTINF:254,David Bowie - Fame
Music/David Bowie/Singles 1/16-Fame.ogg
#EXTINF:240,David Bowie - Golden Years
Music/David Bowie/Singles 1/17-Golden Years.ogg
#EXTINF:331,David Bowie - TVC 15
Music/David Bowie/Singles 1/18-TVC 15.ogg
#EXTINF:183,David Bowie - Sound & Vision
Music/David Bowie/Singles 1/19-Sound & Vision.ogg
#EXTINF:217,David Bowie - Heroes
Music/David Bowie/Singles 2/01-Heroes.ogg
#EXTINF:214,David Bowie - Beauty & The Beast
Music/David Bowie/Singles 2/02-Beauty and The Beast.ogg
#EXTINF:197,David Bowie - Boys Keep Swinging
Music/David Bowie/Singles 2/03-Boys Keep Swinging.ogg
#EXTINF:240,David Bowie - D.J.
Music/David Bowie/Singles 2/04-D.J..ogg
#EXTINF:-1,David Bowie - Alabama Song
Music/David Bowie/Singles 2/05-Alabama Song.ogg
#EXTINF:264,David Bowie - Ashes To Ashes
Music/David Bowie/Singles 2/06-Ashes To Ashes.ogg
#EXTINF:287,David Bowie - Fashion
Music/David Bowie/Singles 2/07-Fashion.ogg
#EXTINF:311,David Bowie - Scary Monsters (And Super Creeps)
Music/David Bowie/Singles 2/08-Scary Monsters (And Super Creeps).ogg
#EXTINF:237,David Bowie - Under Pressure
Music/David Bowie/Singles 2/09-Under Pressure.ogg
#EXTINF:361,David Bowie - Wild Is The Wind
Music/David Bowie/Singles 2/10-Wild Is The Wind.ogg
#EXTINF:250,David Bowie - Let's Dance
Music/David Bowie/Singles 2/11-Let's Dance.ogg
#EXTINF:256,David Bowie - China Girl
Music/David Bowie/Singles 2/12-China Girl.ogg
#EXTINF:237,David Bowie - Modern Love
Music/David Bowie/Singles 2/13-Modern Love.ogg
#EXTINF:191,David Bowie - Blue Jean
Music/David Bowie/Singles 2/14-Blue Jean.ogg
#EXTINF:230,David Bowie - This Is Not America
Music/David Bowie/Singles 2/15-This Is Not America.ogg
#EXTINF:337,David Bowie - Absolute Beginners
Music/David Bowie/Singles 2/17-Absolute Beginners.ogg
#EXTINF:251,David Bowie - Day In Day Out
Music/David Bowie/Singles 2/18-Day In Day Out.ogg

@ -0,0 +1,102 @@
[playlist]
File1=Music/David Bowie/Singles 1/01-Space Oddity.ogg
Title1=David Bowie - Space Oddity
Length1=315
File2=Music/David Bowie/Singles 1/02-Changes.ogg
Title2=David Bowie - Changes
Length2=-1
File3=Music/David Bowie/Singles 1/03-Starman.ogg
Title3=David Bowie - Starman
Length3=258
File4=Music/David Bowie/Singles 1/04-Ziggy Stardust.ogg
Title4=David Bowie - Ziggy Stardust
Length4=194
File5=Music/David Bowie/Singles 1/05-Suffragette City.ogg
Title5=David Bowie - Suffragette City
Length5=206
File6=Music/David Bowie/Singles 1/07-The Jean Genie.ogg
Title6=David Bowie - The Jean Genie
Length6=247
File7=Music/David Bowie/Singles 1/09-Life On Mars.ogg
Title7=David Bowie - Life On Mars?
Length7=231
File8=Music/David Bowie/Singles 1/10-Sorrow.ogg
Title8=David Bowie - Sorrow
Length8=174
File9=Music/David Bowie/Singles 1/11-Rebel Rebel.ogg
Title9=David Bowie - Rebel Rebel
Length9=270
File10=Music/David Bowie/Singles 1/12-Rock and Roll Suicide.ogg
Title10=David Bowie - Rock & Roll Suicide
Length10=178
File11=Music/David Bowie/Singles 1/13-Diamond Dogs.ogg
Title11=David Bowie - Diamond Dogs
Length11=364
File12=Music/David Bowie/Singles 1/15-Young Americans.ogg
Title12=David Bowie - Young Americans
Length12=311
File13=Music/David Bowie/Singles 1/16-Fame.ogg
Title13=David Bowie - Fame
Length13=254
File14=Music/David Bowie/Singles 1/17-Golden Years.ogg
Title14=David Bowie - Golden Years
Length14=240
File15=Music/David Bowie/Singles 1/18-TVC 15.ogg
Title15=David Bowie - TVC 15
Length15=331
File16=Music/David Bowie/Singles 1/19-Sound & Vision.ogg
Title16=David Bowie - Sound & Vision
Length16=183
File17=Music/David Bowie/Singles 2/01-Heroes.ogg
Title17=David Bowie - Heroes
Length17=217
File18=Music/David Bowie/Singles 2/02-Beauty and The Beast.ogg
Title18=David Bowie - Beauty & The Beast
Length18=214
File19=Music/David Bowie/Singles 2/03-Boys Keep Swinging.ogg
Title19=David Bowie - Boys Keep Swinging
Length19=197
File20=Music/David Bowie/Singles 2/04-D.J..ogg
Title20=David Bowie - D.J.
Length20=240
File21=Music/David Bowie/Singles 2/05-Alabama Song.ogg
Title21=David Bowie - Alabama Song
Length21=-1
File22=Music/David Bowie/Singles 2/06-Ashes To Ashes.ogg
Title22=David Bowie - Ashes To Ashes
Length22=264
File23=Music/David Bowie/Singles 2/07-Fashion.ogg
Title23=David Bowie - Fashion
Length23=287
File24=Music/David Bowie/Singles 2/08-Scary Monsters (And Super Creeps).ogg
Title24=David Bowie - Scary Monsters (And Super Creeps)
Length24=311
File25=Music/David Bowie/Singles 2/09-Under Pressure.ogg
Title25=David Bowie - Under Pressure
Length25=237
File26=Music/David Bowie/Singles 2/10-Wild Is The Wind.ogg
Title26=David Bowie - Wild Is The Wind
Length26=361
File27=Music/David Bowie/Singles 2/11-Let's Dance.ogg
Title27=David Bowie - Let's Dance
Length27=250
File28=Music/David Bowie/Singles 2/12-China Girl.ogg
Title28=David Bowie - China Girl
Length28=256
File29=Music/David Bowie/Singles 2/13-Modern Love.ogg
Title29=David Bowie - Modern Love
Length29=237
File30=Music/David Bowie/Singles 2/14-Blue Jean.ogg
Title30=David Bowie - Blue Jean
Length30=191
File31=Music/David Bowie/Singles 2/15-This Is Not America.ogg
Title31=David Bowie - This Is Not America
Length31=230
File32=Music/David Bowie/Singles 2/17-Absolute Beginners.ogg
Title32=David Bowie - Absolute Beginners
Length32=337
File33=Music/David Bowie/Singles 2/18-Day In Day Out.ogg
Title33=David Bowie - Day In Day Out
Length33=251
NumberOfEntries=33
Version=2

155
src/playlist/playlist.go Normal file

@ -0,0 +1,155 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
"strings"
)
type Song struct {
Title string
Filename string
Seconds int
}
func main() {
if len(os.Args) == 1 ||
(!strings.HasSuffix(os.Args[1], ".pls") &&
!strings.HasSuffix(os.Args[1], ".m3u")) {
fmt.Printf("usage: %s <file.[pls|m3u]>\n",
filepath.Base(os.Args[0]))
os.Exit(1)
}
if rawBytes, err := ioutil.ReadFile(os.Args[1]); err != nil {
log.Fatal(err)
} else {
data := string(rawBytes)
if strings.HasSuffix(os.Args[1], ".pls") {
songs := readPlsPlaylist(data)
writeM3uPlaylist(songs)
} else {
songs := readM3uPlaylist(data)
writePlsPlaylist(songs)
}
}
}
func readPlsPlaylist(data string) (songs []Song) {
var song Song
for _, line := range strings.Split(data, "\n") {
if line = strings.TrimSpace(line); line == "" {
continue
}
switch name, value := parsePlsLine(line); name {
case "File":
song.Filename = strings.Map(
mapPlatformDirSeparator, value)
case "Title":
song.Title = value
case "Length":
var err error
if song.Seconds, err = strconv.Atoi(value); err != nil {
log.Printf("failed to read the duration for '%s': %v\n",
song.Title, err)
song.Seconds = -1
}
}
if song.Filename != "" && song.Title != "" && song.Seconds != 0 {
songs = append(songs, song)
song = Song{}
}
}
return songs
}
func parsePlsLine(line string) (name, value string) {
const separator = "="
if i := strings.Index(line, separator); i > -1 {
if j := strings.IndexAny(line, "0123456789"); j > -1 && j < i {
name = line[:j]
value = line[i+len(separator):]
}
}
return name, value
}
func mapPlatformDirSeparator(char rune) rune {
if char == '/' || char == '\\' {
return filepath.Separator
}
return char
}
func writePlsPlaylist(songs []Song) {
fmt.Println("[playlist]")
for i, song := range songs {
i++
fmt.Printf("File%d=%s\n", i, song.Filename)
fmt.Printf("Title%d=%s\n", i, song.Title)
fmt.Printf("Length%d=%d\n", i, song.Seconds)
}
fmt.Printf("NumberOfEntries=%d\nVersion=2\n", len(songs))
}
func readM3uPlaylist(data string) (songs []Song) {
var song Song
for _, line := range strings.Split(data, "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#EXTM3U") {
continue
}
if strings.HasPrefix(line, "#EXTINF:") {
song.Title, song.Seconds = parseExtinfLine(line)
} else {
song.Filename = strings.Map(mapPlatformDirSeparator, line)
}
if song.Filename != "" && song.Title != "" && song.Seconds != 0 {
songs = append(songs, song)
song = Song{}
}
}
return songs
}
func parseExtinfLine(line string) (title string, seconds int) {
if i := strings.IndexAny(line, "-0123456789"); i > -1 {
const separator = ","
line = line[i:]
if j := strings.Index(line, separator); j > -1 {
title = line[j+len(separator):]
var err error
if seconds, err = strconv.Atoi(line[:j]); err != nil {
log.Printf("failed to read the duration for '%s': %v\n",
title, err)
seconds = -1
}
}
}
return title, seconds
}
func writeM3uPlaylist(songs []Song) {
fmt.Println("#EXTM3U")
for _, song := range songs {
fmt.Printf("#EXTINF:%d,%s\n", song.Seconds, song.Title)
fmt.Println(song.Filename)
}
}

@ -0,0 +1,88 @@
// Copyright © 2010-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
"fmt"
"math"
"os"
"runtime"
)
const result = "Polar radius=%.02f θ=%.02f° → Cartesian x=%.02f y=%.02f\n"
var prompt = "Enter a radius and an angle (in degrees), e.g., 12.5 90, " +
"or %s to quit."
type polar struct {
radius float64
θ float64
}
type cartesian struct {
x float64
y float64
}
func init() {
if runtime.GOOS == "windows" {
prompt = fmt.Sprintf(prompt, "Ctrl+Z, Enter")
} else { // Unix-like
prompt = fmt.Sprintf(prompt, "Ctrl+D")
}
}
func main() {
questions := make(chan polar)
defer close(questions)
answers := createSolver(questions)
defer close(answers)
interact(questions, answers)
}
func createSolver(questions chan polar) chan cartesian {
answers := make(chan cartesian)
go func() {
for {
polarCoord := <-questions
θ := polarCoord.θ * math.Pi / 180.0 // degrees to radians
x := polarCoord.radius * math.Cos(θ)
y := polarCoord.radius * math.Sin(θ)
answers <- cartesian{x, y}
}
}()
return answers
}
func interact(questions chan polar, answers chan cartesian) {
reader := bufio.NewReader(os.Stdin)
fmt.Println(prompt)
for {
fmt.Printf("Radius and angle: ")
line, err := reader.ReadString('\n')
if err != nil {
break
}
var radius, θ float64
if _, err := fmt.Sscanf(line, "%f %f", &radius, &θ); err != nil {
fmt.Fprintln(os.Stderr, "invalid input")
continue
}
questions <- polar{radius, θ}
coord := <-answers
fmt.Printf(result, radius, θ, coord.x, coord.y)
}
fmt.Println()
}

294
src/qtrac.eu/omap/omap.go Normal file

@ -0,0 +1,294 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The data structure is a left-leaning red-black tree based on Robert
// Sedgewick's implementations as described in
// http://www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf and
// http://www.cs.princeton.edu/~rs/talks/LLRB/RedBlack.pdf, with some of
// the code based on Lee Stanza's C++ code at
// http://www.teachsolaisgames.com/articles/balanced_left_leaning.html
// Thanks also to Russ Cox for many useful suggestions.
// Package omap implements an efficient key-ordered map.
//
// Keys and values may be of any type, but all keys must be comparable
// using the less than function that is passed in to the omap.New()
// function, or the less than function provided by the omap.New*()
// construction functions.
package omap
import "strings"
// NewStringKeyed returns an empty Map that accepts case-sensitive
// string keys.
func NewStringKeyed() *Map {
return &Map{less: func(a, b interface{}) bool {
return a.(string) < b.(string)
}}
}
// NewCaseFoldedKeyed returns an empty Map that accepts case-insensitive
// string keys.
func NewCaseFoldedKeyed() *Map {
return &Map{less: func(a, b interface{}) bool {
return strings.ToLower(a.(string)) < strings.ToLower(b.(string))
}}
}
// NewIntKeyed returns an empty Map that accepts int keys.
func NewIntKeyed() *Map {
return &Map{less: func(a, b interface{}) bool {
return a.(int) < b.(int)
}}
}
// NewFloat64Keyed returns an empty Map that accepts float64 keys.
func NewFloat64Keyed() *Map {
return &Map{less: func(a, b interface{}) bool {
return a.(float64) < b.(float64)
}}
}
// New returns an empty Map that uses the given less than function to
// compare keys. For example:
// type Point { X, Y int }
// pointMap := omap.New(func(a, b interface{}) bool {
// α, β := a.(Point), b.(Point)
// if α.X != β.X {
// return α.X < β.X
// }
// return α.Y < β.Y
// })
func New(less func(interface{}, interface{}) bool) *Map {
return &Map{less: less}
}
// Map is a key-ordered map.
// The zero value is an invalid map! Use one of the construction functions
// (e.g., New()), to create a map for a specific key type.
type Map struct {
root *node
less func(interface{}, interface{}) bool
length int
}
type node struct {
key, value interface{}
red bool
left, right *node
}
// Insert inserts a new key-value into the Map and returns true; or
// replaces an existing key-value pair's value if the keys are equal and
// returns false. For example:
// inserted := myMap.Insert(key, value).
func (m *Map) Insert(key, value interface{}) (inserted bool) {
m.root, inserted = m.insert(m.root, key, value)
m.root.red = false
if inserted {
m.length++
}
return inserted
}
// Find returns the value and true if the key is in the Map or nil and
// false otherwise. For example:
// value, found := myMap.Find(key).
func (m *Map) Find(key interface{}) (value interface{}, found bool) {
root := m.root
for root != nil {
if m.less(key, root.key) {
root = root.left
} else if m.less(root.key, key) {
root = root.right
} else {
return root.value, true
}
}
return nil, false
}
// Delete deletes the key-value with the given key from the Map and returns
// true, or does nothing and returns false if there is no key-value with
// the given key. For example:
// deleted := myMap.Delete(key).
func (m *Map) Delete(key interface{}) (deleted bool) {
if m.root != nil {
if m.root, deleted = m.remove(m.root, key); m.root != nil {
m.root.red = false
}
}
if deleted {
m.length--
}
return deleted
}
// Do calls the given function on every key-value in the Map in order.
func (m *Map) Do(function func(interface{}, interface{})) {
do(m.root, function)
}
// Len returns the number of key-value pairs in the map.
func (m *Map) Len() int {
return m.length
}
func (m *Map) insert(root *node, key, value interface{}) (*node, bool) {
inserted := false
if root == nil { // If the key was in the tree it would belong here
return &node{key: key, value: value, red: true}, true
}
if isRed(root.left) && isRed(root.right) {
colorFlip(root)
}
if m.less(key, root.key) {
root.left, inserted = m.insert(root.left, key, value)
} else if m.less(root.key, key) {
root.right, inserted = m.insert(root.right, key, value)
} else { // The key is already in the tree so just replace its value
root.value = value
}
if isRed(root.right) && !isRed(root.left) {
root = rotateLeft(root)
}
if isRed(root.left) && isRed(root.left.left) {
root = rotateRight(root)
}
return root, inserted
}
func isRed(root *node) bool { return root != nil && root.red }
func colorFlip(root *node) {
root.red = !root.red
if root.left != nil {
root.left.red = !root.left.red
}
if root.right != nil {
root.right.red = !root.right.red
}
}
func rotateLeft(root *node) *node {
x := root.right
root.right = x.left
x.left = root
x.red = root.red
root.red = true
return x
}
func rotateRight(root *node) *node {
x := root.left
root.left = x.right
x.right = root
x.red = root.red
root.red = true
return x
}
func do(root *node, function func(interface{}, interface{})) {
if root != nil {
do(root.left, function)
function(root.key, root.value)
do(root.right, function)
}
}
// We do not provide an exported First() method because this is an
// implementation detail.
func first(root *node) *node {
for root.left != nil {
root = root.left
}
return root
}
func (m *Map) remove(root *node, key interface{}) (*node, bool) {
deleted := false
if m.less(key, root.key) {
if root.left != nil {
if !isRed(root.left) && !isRed(root.left.left) {
root = moveRedLeft(root)
}
root.left, deleted = m.remove(root.left, key)
}
} else {
if isRed(root.left) {
root = rotateRight(root)
}
if !m.less(key, root.key) && !m.less(root.key, key) &&
root.right == nil {
return nil, true
}
if root.right != nil {
if !isRed(root.right) && !isRed(root.right.left) {
root = moveRedRight(root)
}
if !m.less(key, root.key) && !m.less(root.key, key) {
smallest := first(root.right)
root.key = smallest.key
root.value = smallest.value
root.right = deleteMinimum(root.right)
deleted = true
} else {
root.right, deleted = m.remove(root.right, key)
}
}
}
return fixUp(root), deleted
}
func moveRedLeft(root *node) *node {
colorFlip(root)
if root.right != nil && isRed(root.right.left) {
root.right = rotateRight(root.right)
root = rotateLeft(root)
colorFlip(root)
}
return root
}
func moveRedRight(root *node) *node {
colorFlip(root)
if root.left != nil && isRed(root.left.left) {
root = rotateRight(root)
colorFlip(root)
}
return root
}
func deleteMinimum(root *node) *node {
if root.left == nil {
return nil
}
if !isRed(root.left) && !isRed(root.left.left) {
root = moveRedLeft(root)
}
root.left = deleteMinimum(root.left)
return fixUp(root)
}
func fixUp(root *node) *node {
if isRed(root.right) {
root = rotateLeft(root)
}
if isRed(root.left) && isRed(root.left.left) {
root = rotateRight(root)
}
if isRed(root.left) && isRed(root.right) {
colorFlip(root)
}
return root
}

@ -0,0 +1,127 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The tests here are very incomplete and just to show examples of how it
// can be done.
package omap_test
import (
"qtrac.eu/omap"
"strings"
"testing"
)
func TestStringKeyOMapInsertion(t *testing.T) {
wordForWord := omap.NewCaseFoldedKeyed()
for _, word := range []string{"one", "Two", "THREE", "four", "Five"} {
wordForWord.Insert(word, word)
}
var words []string
wordForWord.Do(func(_, value interface{}) {
words = append(words, value.(string))
})
actual, expected := strings.Join(words, ""), "FivefouroneTHREETwo"
if actual != expected {
t.Errorf("%q != %q", actual, expected)
}
}
func TestIntKeyOMapFind(t *testing.T) {
intMap := omap.NewIntKeyed()
for _, number := range []int{9, 1, 8, 2, 7, 3, 6, 4, 5, 0} {
intMap.Insert(number, number*10)
}
for _, number := range []int{0, 1, 5, 8, 9} {
value, found := intMap.Find(number)
if !found {
t.Errorf("failed to find %d", number)
}
actual, expected := value.(int), number*10
if actual != expected {
t.Errorf("value is %d should be %d", actual, expected)
}
}
for _, number := range []int{-1, -21, 10, 11, 148} {
_, found := intMap.Find(number)
if found {
t.Errorf("should not have found %d", number)
}
}
}
func TestIntKeyOMapDelete(t *testing.T) {
intMap := omap.NewIntKeyed()
for _, number := range []int{9, 1, 8, 2, 7, 3, 6, 4, 5, 0} {
intMap.Insert(number, number*10)
}
if intMap.Len() != 10 {
t.Errorf("map len %d should be 10", intMap.Len())
}
length := 9
for i, number := range []int{0, 1, 5, 8, 9} {
if deleted := intMap.Delete(number); !deleted {
t.Errorf("failed to delete %d", number)
}
if intMap.Len() != length-i {
t.Errorf("map len %d should be %d", intMap.Len(), length-i)
}
}
for _, number := range []int{-1, -21, 10, 11, 148} {
if deleted := intMap.Delete(number); deleted {
t.Errorf("should not have deleted nonexistent %d", number)
}
}
if intMap.Len() != 5 {
t.Errorf("map len %d should be 5", intMap.Len())
}
}
func TestPassing(t *testing.T) {
intMap := omap.NewIntKeyed()
intMap.Insert(7, 7)
passMap(intMap, t)
}
func passMap(m *omap.Map, t *testing.T) {
for _, number := range []int{9, 3, 6, 4, 5, 0} {
m.Insert(number, number)
}
if m.Len() != 7 {
t.Errorf("should have %d items", 7)
}
}
// Thanks to Russ Cox for improving these benchmarks
func BenchmarkOMapFindSuccess(b *testing.B) {
b.StopTimer() // Don't time creation and population
intMap := omap.NewIntKeyed()
for i := 0; i < 1e6; i++ {
intMap.Insert(i, i)
}
b.StartTimer() // Time the Find() method succeeding
for i := 0; i < b.N; i++ {
intMap.Find(i % 1e6)
}
}
func BenchmarkOMapFindFailure(b *testing.B) {
b.StopTimer() // Don't time creation and population
intMap := omap.NewIntKeyed()
for i := 0; i < 1e6; i++ {
intMap.Insert(2*i, i)
}
b.StartTimer() // Time the Find() method failing
for i := 0; i < b.N; i++ {
intMap.Find(2*(i%1e6) + 1)
}
}

@ -0,0 +1,134 @@
// Copyright © 2010-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"log"
"math"
"math/cmplx"
"net/http"
"strconv"
)
const (
pageTop = `<!DOCTYPE HTML><html><head>
<style>.error{color:#FF0000;}</style></head>
<title>Quadratic Equation Solver</title><body>
<h3>Quadratic Equation Solver</h3><p>Solves equations of the form
a<i>x</i>² + b<i>x</i> + c</p>`
form = `<form action="/" method="POST">
<input type="text" name="a" size="1"><label for="a"><i>x</i>²</label> +
<input type="text" name="b" size="1"><label for="b"><i>x</i></label> +
<input type="text" name="c" size="1"><label for="c"> </label>
<input type="submit" name="calculate" value="Calculate">
</form>`
pageBottom = "</body></html>"
error = `<p class="error">%s</p>`
solution = "<p>%s → %s</p>"
)
func main() {
http.HandleFunc("/", homePage)
if err := http.ListenAndServe(":9001", nil); err != nil {
log.Fatal("failed to start server", err)
}
}
func homePage(writer http.ResponseWriter, request *http.Request) {
err := request.ParseForm() // Must be called before writing response
fmt.Fprint(writer, pageTop, form)
if err != nil {
fmt.Fprintf(writer, error, err)
} else {
if floats, message, ok := processRequest(request); ok {
question := formatQuestion(request.Form)
x1, x2 := solve(floats)
answer := formatSolutions(x1, x2)
fmt.Fprintf(writer, solution, question, answer)
} else if message != "" {
fmt.Fprintf(writer, error, message)
}
}
fmt.Fprint(writer, pageBottom)
}
func processRequest(request *http.Request) ([3]float64, string, bool) {
var floats [3]float64
count := 0
for index, key := range []string{"a", "b", "c"} {
if slice, found := request.Form[key]; found && len(slice) > 0 {
if slice[0] != "" {
if x, err := strconv.ParseFloat(slice[0], 64);
err != nil {
return floats, "'" + slice[0] + "' is invalid", false
} else {
floats[index] = x
}
} else { // as a courtesy to users treat blanks as 0
request.Form[key][0] = "0"
floats[index] = 0
}
count++
}
}
if count != 3 { // the first time the form is empty;
return floats, "", false // this isn't an error but there's
} // nothing to calculate
if EqualFloat(floats[0], 0, -1) {
return floats, "the x² factor may not be 0", false
}
return floats, "", true
}
func formatQuestion(form map[string][]string) string {
// Ugly formatting, e.g., 1x² + -1x + -5 should be x² - x - 5,
// 1x² + 0x + -2 should be x² - 2, etc.
return fmt.Sprintf("%s<i>x</i>² + %s<i>x</i> + %s", form["a"][0],
form["b"][0], form["c"][0])
}
func formatSolutions(x1, x2 complex128) string {
// Ugly formatting since it always shows a complex even if the imag
// part is 0i.
if EqualComplex(x1, x2) {
return fmt.Sprintf("<i>x</i>=%f", x1)
}
return fmt.Sprintf("<i>x</i>=%f or <i>x</i>=%f", x1, x2)
}
func solve(floats [3]float64) (complex128, complex128) {
a, b, c := complex(floats[0], 0), complex(floats[1], 0),
complex(floats[2], 0)
root := cmplx.Sqrt(cmplx.Pow(b, 2) - (4 * a * c))
x1 := (-b + root) / (2 * a)
x2 := (-b - root) / (2 * a)
return x1, x2
}
// EqualFloat() returns true if x and y are approximately equal to the
// given limit. Pass a limit of -1 to get the greatest accuracy the machine
// can manage.
func EqualFloat(x, y, limit float64) bool {
if limit <= 0.0 {
limit = math.SmallestNonzeroFloat64
}
return math.Abs(x-y) <=
(limit * math.Min(math.Abs(x), math.Abs(y)))
}
func EqualComplex(x, y complex128) bool {
return EqualFloat(real(x), real(y), -1) &&
EqualFloat(imag(x), imag(y), -1)
}

@ -0,0 +1,171 @@
// Copyright © 2010-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"log"
"math"
"math/cmplx"
"net/http"
"strconv"
)
const (
decimals = 3
pageTop = `<!DOCTYPE HTML><html><head>
<style>.error{color:#FF0000;}</style></head>
<title>Quadratic Equation Solver</title><body>
<h3>Quadratic Equation Solver</h3><p>Solves equations of the form
a<i>x</i>² + b<i>x</i> + c</p>`
form = `<form action="/" method="POST">
<input type="text" name="a" size="1"><label for="a"><i>x</i>²</label> +
<input type="text" name="b" size="1"><label for="b"><i>x</i></label> +
<input type="text" name="c" size="1"><label for="c"> </label>
<input type="submit" name="calculate" value="Calculate">
</form>`
pageBottom = "</body></html>"
error = `<p class="error">%s</p>`
solution = "<p>%s → %s</p>"
oneSolution = "<i>x</i>=%s"
twoSolutions = "<i>x</i>=%s or <i>x</i>=%s"
noSolution = "<i>there are no solutions</i>"
)
func main() {
http.HandleFunc("/", homePage)
if err := http.ListenAndServe(":9001", nil); err != nil {
log.Fatal("failed to start server", err)
}
}
func homePage(writer http.ResponseWriter, request *http.Request) {
err := request.ParseForm() // Must be called before writing response
fmt.Fprint(writer, pageTop, form)
if err != nil {
fmt.Fprintf(writer, error, err)
} else {
if floats, message, ok := processRequest(request); ok {
question := formatQuestion(request.Form)
x1, x2 := solve(floats)
answer := formatSolutions(x1, x2)
fmt.Fprintf(writer, solution, question, answer)
} else if message != "" {
fmt.Fprintf(writer, error, message)
}
}
fmt.Fprint(writer, pageBottom)
}
func processRequest(request *http.Request) ([3]float64, string, bool) {
var floats [3]float64
count := 0
for index, key := range []string{"a", "b", "c"} {
if slice, found := request.Form[key]; found && len(slice) > 0 {
if slice[0] != "" {
if x, err := strconv.ParseFloat(slice[0], 64);
err != nil {
return floats, "'" + slice[0] + "' is invalid", false
} else {
floats[index] = x
}
} else { // as a courtesy to users treat blanks as 0
request.Form[key][0] = "0"
floats[index] = 0
}
count++
}
}
if count != 3 { // the first time the form is empty;
return floats, "", false // this isn't an error but there's
} // nothing to calculate
if EqualFloat(floats[0], 0, -1) {
return floats, "the x² factor may not be 0", false
}
return floats, "", true
}
func formatQuestion(form map[string][]string) string {
result := formatSignAndNumber("", form["a"][0], "<i>x</i>²")
result += formatSignAndNumber(" ", form["b"][0], "<i>x</i>")
result += formatSignAndNumber(" ", form["c"][0], "")
return result
}
func formatSignAndNumber(signPad, number, suffix string) string {
if number == "" || number == "0" || number == "0.0" {
return ""
}
var sign string
if signPad != "" {
sign = signPad + "+" + signPad
}
if number[0] == '-' {
sign = signPad + "-" + signPad
number = number[1:]
}
if suffix != "" && number == "1" {
return sign + suffix
}
return sign + number + suffix
}
func formatSolutions(x1, x2 complex128) string {
exactlyOneSolution := false
if cmplx.IsNaN(x1) && cmplx.IsNaN(x2) {
return noSolution
}
if cmplx.IsNaN(x1) {
exactlyOneSolution = true
x1 = x2
} else if cmplx.IsNaN(x2) || EqualComplex(x1, x2) {
exactlyOneSolution = true
}
if exactlyOneSolution {
return fmt.Sprintf(oneSolution, formatComplex(x1))
}
return fmt.Sprintf(twoSolutions, formatComplex(x1), formatComplex(x2))
}
func formatComplex(x complex128) string {
if EqualFloat(imag(x), 0, -1) {
return fmt.Sprintf("%.*f", decimals, real(x))
}
return fmt.Sprintf("%.*f", decimals, x)
}
func solve(floats [3]float64) (complex128, complex128) {
a, b, c := complex(floats[0], 0), complex(floats[1], 0),
complex(floats[2], 0)
root := cmplx.Sqrt(cmplx.Pow(b, 2) - (4 * a * c))
x1 := (-b + root) / (2 * a)
x2 := (-b - root) / (2 * a)
return x1, x2
}
// EqualFloat() returns true if x and y are approximately equal to the
// given limit. Pass a limit of -1 to get the greatest accuracy the machine
// can manage.
func EqualFloat(x, y, limit float64) bool {
if limit <= 0.0 {
limit = math.SmallestNonzeroFloat64
}
return math.Abs(x-y) <=
(limit * math.Min(math.Abs(x), math.Abs(y)))
}
func EqualComplex(x, y complex128) bool {
return EqualFloat(real(x), real(y), -1) &&
EqualFloat(imag(x), imag(y), -1)
}

115
src/safemap/safemap.go Normal file

@ -0,0 +1,115 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package safemap
type safeMap chan commandData
type commandData struct {
action commandAction
key string
value interface{}
result chan<- interface{}
data chan<- map[string]interface{}
updater UpdateFunc
}
type commandAction int
const (
remove commandAction = iota
end
find
insert
length
update
)
type findResult struct {
value interface{}
found bool
}
type SafeMap interface {
Insert(string, interface{})
Delete(string)
Find(string) (interface{}, bool)
Len() int
Update(string, UpdateFunc)
Close() map[string]interface{}
}
type UpdateFunc func(interface{}, bool) interface{}
func New() SafeMap {
sm := make(safeMap) // type safeMap chan commandData
go sm.run()
return sm
}
func (sm safeMap) run() {
store := make(map[string]interface{})
for command := range sm {
switch command.action {
case insert:
store[command.key] = command.value
case remove:
delete(store, command.key)
case find:
value, found := store[command.key]
command.result <- findResult{value, found}
case length:
command.result <- len(store)
case update:
value, found := store[command.key]
store[command.key] = command.updater(value, found)
case end:
close(sm)
command.data <- store
}
}
}
func (sm safeMap) Insert(key string, value interface{}) {
sm <- commandData{action: insert, key: key, value: value}
}
func (sm safeMap) Delete(key string) {
sm <- commandData{action: remove, key: key}
}
func (sm safeMap) Find(key string) (value interface{}, found bool) {
reply := make(chan interface{})
sm <- commandData{action: find, key: key, result: reply}
result := (<-reply).(findResult)
return result.value, result.found
}
func (sm safeMap) Len() int {
reply := make(chan interface{})
sm <- commandData{action: length, result: reply}
return (<-reply).(int)
}
// If the updater calls a safeMap method we will get deadlock!
func (sm safeMap) Update(key string, updater UpdateFunc) {
sm <- commandData{action: update, key: key, updater: updater}
}
// Close() may only be called once per safe map; all other methods can be
// called as often as desired from any number of goroutines
func (sm safeMap) Close() map[string]interface{} {
reply := make(chan map[string]interface{})
sm <- commandData{action: end, data: reply}
return <-reply
}

@ -0,0 +1,96 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package safemap_test
import (
"fmt"
"safemap"
"sync"
"testing"
)
func TestSafeMap(t *testing.T) {
store := safemap.New()
fmt.Printf("Initially has %d items\n", store.Len())
deleted := []int{0, 2, 3, 5, 7, 20, 399, 25, 30, 1000, 91, 97, 98, 99}
var waiter sync.WaitGroup
waiter.Add(1)
go func() { // Concurrent Inserter
for i := 0; i < 100; i++ {
store.Insert(fmt.Sprintf("0x%04X", i), i)
if i > 0 && i%15 == 0 {
fmt.Printf("Inserted %d items\n", store.Len())
}
}
fmt.Printf("Inserted %d items\n", store.Len())
waiter.Done()
}()
waiter.Add(1)
go func() { // Concurrent Deleter
for _, i := range deleted {
key := fmt.Sprintf("0x%04X", i)
before := store.Len()
store.Delete(key)
fmt.Printf("Deleted m[%s] (%d) before=%d after=%d\n",
key, i, before, store.Len())
}
waiter.Done()
}()
waiter.Add(1)
go func() { // Concurrent Finder
for _, i := range deleted {
for _, j := range []int{i, i + 1} {
key := fmt.Sprintf("0x%04X", j)
value, found := store.Find(key)
if found {
fmt.Printf("Found m[%s] == %d\n", key, value)
} else {
fmt.Printf("Not found m[%s] (%d)\n", key, j)
}
}
}
waiter.Done()
}()
waiter.Wait()
updater := func(value interface{}, found bool) interface{} {
if found {
return value.(int) * 1000
}
return 1
}
for _, i := range []int{5, 10, 15, 20, 25, 30, 35} {
key := fmt.Sprintf("0x%04X", i)
if value, found := store.Find(key); found {
fmt.Printf("Original m[%s] == %d\t", key, value)
store.Update(key, updater)
if value, found := store.Find(key); found {
fmt.Printf("Updated m[%s] == %5d\n", key, value)
}
}
}
fmt.Printf("Finished with %d items\n", store.Len())
// not needed here but useful if you want to free up the goroutine
data := store.Close()
fmt.Println("Closed")
fmt.Printf("len == %d\n", len(data))
//for k, v := range data { fmt.Printf("%s = %v\n", k, v) }
}

114
src/safeslice/safeslice.go Normal file

@ -0,0 +1,114 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package safeslice
type safeSlice chan commandData
type commandData struct {
action commandAction
index int
item interface{}
result chan<- interface{}
data chan<- []interface{}
updater UpdateFunc
}
type commandAction int
const (
insert commandAction = iota
remove
at
update
end
length
)
type UpdateFunc func(interface{}) interface{}
type SafeSlice interface {
Append(interface{}) // Append the given item to the slice
At(int) interface{} // Return the item at the given index position
Close() []interface{} // Close the channel and return the slice
Delete(int) // Delete the item at the given index position
Len() int // Return the number of items in the slice
Update(int, UpdateFunc) // Update the item at the given index position
}
func New() SafeSlice {
slice := make(safeSlice)
go slice.run()
return slice
}
func (slice safeSlice) run() {
list := make([]interface{}, 0)
for command := range slice {
switch command.action {
case insert:
list = append(list, command.item)
case remove: // potentially expensive for long lists
if 0 <= command.index && command.index < len(list) {
list = append(list[:command.index],
list[command.index+1:]...)
}
case at:
if 0 <= command.index && command.index < len(list) {
command.result <- list[command.index]
} else {
command.result <- nil
}
case length:
command.result <- len(list)
case update:
if 0 <= command.index && command.index < len(list) {
list[command.index] = command.updater(list[command.index])
}
case end:
close(slice)
command.data <- list
}
}
}
func (slice safeSlice) Append(item interface{}) {
slice <- commandData{action: insert, item: item}
}
func (slice safeSlice) Delete(index int) {
slice <- commandData{action: remove, index: index}
}
func (slice safeSlice) At(index int) interface{} {
reply := make(chan interface{})
slice <- commandData{at, index, nil, reply, nil, nil}
return <-reply
}
func (slice safeSlice) Len() int {
reply := make(chan interface{})
slice <- commandData{action: length, result: reply}
return (<-reply).(int)
}
// If the updater calls a safeSlice method we will get deadlock!
func (slice safeSlice) Update(index int, updater UpdateFunc) {
slice <- commandData{action: update, index: index, updater: updater}
}
func (slice safeSlice) Close() []interface{} {
reply := make(chan []interface{})
slice <- commandData{action: end, data: reply}
return <-reply
}

@ -0,0 +1,84 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package safeslice_test
import (
"fmt"
"safeslice"
"sync"
"testing"
)
func TestSafeSlice(t *testing.T) {
store := safeslice.New()
fmt.Printf("Initially has %d items\n", store.Len())
deleted := []int{0, 2, 3, 5, 7, 20, 399, 25, 30, 1000, 91, 97, 98, 99}
var waiter sync.WaitGroup
waiter.Add(1)
go func() { // Concurrent Inserter
for i := 0; i < 100; i++ {
store.Append(fmt.Sprintf("%04X", i))
if i > 0 && i%15 == 0 {
fmt.Printf("Inserted %d items\n", store.Len())
}
}
fmt.Printf("Inserted %d items\n", store.Len())
waiter.Done()
}()
waiter.Add(1)
go func() { // Concurrent Deleter
for _, i := range deleted {
before := store.Len()
store.Delete(i)
fmt.Printf("Deleted m[%d] before=%d after=%d\n",
i, before, store.Len())
}
waiter.Done()
}()
waiter.Add(1)
go func() { // Concurrent Finder
for _, i := range deleted {
for _, j := range []int{i, i + 1} {
item := store.At(j)
if item != nil {
fmt.Printf("Found m[%d] == %s\n", j, item)
} else {
fmt.Printf("Not found m[%d]\n", j)
}
}
}
waiter.Done()
}()
waiter.Wait()
fmt.Printf("Finished with %d items\n", store.Len())
updater := func(value interface{}) interface{} {
return value.(string) + ":updated"
}
for i := 0; i < store.Len() && i < 5; i++ {
fmt.Printf("m[%d] == %s -> ", i, store.At(i))
store.Update(i, updater)
fmt.Printf("%s\n", store.At(i))
}
list := store.Close()
fmt.Println("Closed")
fmt.Printf("len == %d\n", len(list))
fmt.Println()
}

125
src/shaper1/shaper1.go Normal file

@ -0,0 +1,125 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"image"
"image/color"
"log"
"os"
"shaper1/shapes"
)
func main() {
log.SetFlags(0)
const width, height = 400, 200
img := shapes.FilledImage(width, height,
color.RGBA{0xFF, 0xFF, 0xFF, 0xFF})
x, y := width/4, height/2
red := color.RGBA{0xFF, 0, 0, 0xFF}
blue := color.RGBA{0, 0, 0xFF, 0xFF}
// Purely for testing New() vs. New*()
if len(os.Args) == 1 {
fmt.Println("Using NewCircle() & NewRegularPolygon()")
circle := shapes.NewCircle(blue, 90)
circle.SetFill(red) // Uses the aggregated shape.SetFill method
octagon := shapes.NewRegularPolygon(red, 75, 8)
octagon.SetFill(blue) // Uses the aggregated circle.shape.SetFill
polygon := shapes.NewRegularPolygon(image.Black, 65, 4)
if err := shapes.DrawShapes(img, x, y, circle, octagon, polygon);
err != nil {
fmt.Println(err)
}
sanityCheck("circle", circle)
sanityCheck("octagon", octagon)
sanityCheck("polygon", polygon)
} else {
fmt.Println("Using New()")
// The Shapers returned by New can only call
// Shaper methods (Fill(), SetFill(), and Draw());
// however, we can use type assertion if we need to access other
// methods.
if _, err := shapes.New("Misshapen", shapes.Option{blue, 5});
err == nil {
fmt.Println("unexpectedly got a non-nil invalid shape!")
}
circle, _ := shapes.New("circle", shapes.Option{blue, 5})
circle.SetFill(red)
circle.(shapes.CircularShaper).SetRadius(90)
octagon, _ := shapes.New("octagon", shapes.Option{red, 10})
octagon.SetFill(blue)
// This type assertion changes the original octagon because the new
// octagon is in effect a reference to a shapes.RegularPolygonalShaper
// object
if octagon, ok := octagon.(shapes.RegularPolygonalShaper); ok {
octagon.SetRadius(75)
}
polygon, _ := shapes.New("square", shapes.Option{Radius: 65})
if err := shapes.DrawShapes(img, x, y, circle, octagon, polygon);
err != nil {
fmt.Println(err)
}
sanityCheck("circle", circle)
sanityCheck("octagon", octagon)
sanityCheck("polygon", polygon)
}
polygon := shapes.NewRegularPolygon(color.RGBA{0, 0x7F, 0, 0xFF}, 65, 4)
showShapeDetails(polygon)
y = 30
for i, radius := range []int{60, 55, 50, 45, 40} {
polygon.SetRadius(radius)
polygon.SetSides(i + 5)
x += radius
y += height / 8
if err := shapes.DrawShapes(img, x, y, polygon); err != nil {
fmt.Println(err)
}
}
filename := "shapes.png"
if err := shapes.SaveImage(img, filename); err != nil {
log.Println(err)
} else {
fmt.Println("Saved", filename)
}
fmt.Println("OK")
img = shapes.FilledImage(width, height, image.White)
x, y = width/3, height/4
}
func sanityCheck(name string, shape shapes.Shaper) {
fmt.Print("name=", name, " ")
fmt.Print("fill=", shape.Fill(), " ")
if shape, ok := shape.(shapes.CircularShaper); ok {
fmt.Print("radius=", shape.Radius(), " ")
if shape, ok := shape.(shapes.RegularPolygonalShaper); ok {
fmt.Print("sides=", shape.Sides(), " ")
}
}
fmt.Println()
}
func showShapeDetails(shape shapes.Shaper) {
fmt.Print("fill=", shape.Fill(), " ") // All shapes have a fill color
if shape, ok := shape.(shapes.CircularShaper); ok { // shadow variable
fmt.Print("radius=", shape.Radius(), " ")
if shape, ok := shape.(shapes.RegularPolygonalShaper); ok {//shadow
fmt.Print("sides=", shape.Sides(), " ")
}
}
fmt.Println()
}

@ -0,0 +1,351 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package shapes
import (
"fmt"
"image"
"image/color"
"image/draw"
"image/jpeg"
"image/png"
"log"
"math"
"os"
"path/filepath"
"runtime"
"strings"
)
var saneLength, saneRadius, saneSides func(int) int
func init() {
saneLength = makeBoundedIntFunc(1, 4096)
saneRadius = makeBoundedIntFunc(1, 1024)
saneSides = makeBoundedIntFunc(3, 60)
}
func makeBoundedIntFunc(minimum, maximum int) func(int) int {
return func(x int) int {
valid := x
switch {
case x < minimum:
valid = minimum
case x > maximum:
valid = maximum
}
if valid != x {
log.Printf("%s(): replaced %d with %d\n", caller(1), x, valid)
}
return valid
}
}
type Shaper interface {
Fill() color.Color
SetFill(fill color.Color)
Draw(img draw.Image, x, y int) error
}
type CircularShaper interface {
Shaper // Fill(); SetFill(); Draw()
Radius() int
SetRadius(radius int)
}
type RegularPolygonalShaper interface {
CircularShaper // Fill(); SetFill(); Draw(); Radius(); SetRadius()
Sides() int
SetSides(sides int)
}
/*
This is unexported so that we are forced to use NewCircle() to create
one thus ensuring that we always start with valid values since the zero
values are not acceptable in this case. Of course the privacy can only
be enforced outside the shapes package.
newShape() is unexported since we don't want undrawable shapes to be
created.
*/
type shape struct{ fill color.Color }
func newShape(fill color.Color) shape {
if fill == nil { // We silently treat a nil color as black
fill = color.Black
}
return shape{fill}
}
func (shape shape) Fill() color.Color { return shape.fill }
func (shape *shape) SetFill(fill color.Color) {
if fill == nil { // We silently treat a nil color as black
fill = color.Black
}
shape.fill = fill
}
// The zero value is invalid! Use NewCircle() to create a valid Circle.
type Circle struct {
shape
radius int
}
// By calling newShape() we pass on any checking to newShape() without
// having to know what if any is required.
func NewCircle(fill color.Color, radius int) *Circle {
return &Circle{newShape(fill), saneRadius(radius)}
}
func (circle *Circle) Radius() int {
return circle.radius
}
func (circle *Circle) SetRadius(radius int) {
circle.radius = saneRadius(radius)
}
func (circle *Circle) Draw(img draw.Image, x, y int) error {
// Algorithm taken from
// http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
// No need to check the radius is in bounds because you can only
// create circles using NewCircle() which guarantees it is within
// bounds. But the x, y might be outside the image so we check.
if err := checkBounds(img, x, y); err != nil {
return err
}
fill, radius := circle.fill, circle.radius
x0, y0 := x, y
f := 1 - radius
ddF_x, ddF_y := 1, -2*radius
x, y = 0, radius
img.Set(x0, y0+radius, fill)
img.Set(x0, y0-radius, fill)
img.Set(x0+radius, y0, fill)
img.Set(x0-radius, y0, fill)
for x < y {
if f >= 0 {
y--
ddF_y += 2
f += ddF_y
}
x++
ddF_x += 2
f += ddF_x
img.Set(x0+x, y0+y, fill)
img.Set(x0-x, y0+y, fill)
img.Set(x0+x, y0-y, fill)
img.Set(x0-x, y0-y, fill)
img.Set(x0+y, y0+x, fill)
img.Set(x0-y, y0+x, fill)
img.Set(x0+y, y0-x, fill)
img.Set(x0-y, y0-x, fill)
}
return nil
}
func (circle *Circle) String() string {
return fmt.Sprintf("circle(fill=%v, radius=%d)", circle.fill,
circle.radius)
}
func checkBounds(img image.Image, x, y int) error {
if !image.Rect(x, y, x, y).In(img.Bounds()) {
return fmt.Errorf("%s(): point (%d, %d) is outside the image\n",
caller(1), x, y)
}
return nil
}
func caller(steps int) string {
name := "?"
if pc, _, _, ok := runtime.Caller(steps + 1); ok {
name = filepath.Base(runtime.FuncForPC(pc).Name())
}
return name
}
// The zero value is invalid! Use NewRegularPolygon() to create a valid
// RegularPolygon.
type RegularPolygon struct {
*Circle
sides int
}
func NewRegularPolygon(fill color.Color, radius,
sides int) *RegularPolygon {
// By calling NewCircle() we pass on any checking (e.g., bounds
// checking) to NewCircle() without having to know what if any is
// required.
return &RegularPolygon{NewCircle(fill, radius), saneSides(sides)}
}
func (polygon *RegularPolygon) Sides() int {
return polygon.sides
}
func (polygon *RegularPolygon) SetSides(sides int) {
polygon.sides = saneSides(sides)
}
func (polygon *RegularPolygon) Draw(img draw.Image, x, y int) error {
// No need to check the radius or sides are in bounds because you can
// only create polygons using NewRegularPolygon() which guarantees they are
// within bounds. But the x, y might be outside the image so we check.
// len(points) == sides + 1
if err := checkBounds(img, x, y); err != nil {
return err
}
points := getPoints(x, y, polygon.sides, float64(polygon.Radius()))
for i := 0; i < polygon.sides; i++ { // Draw lines between the apexes
drawLine(img, points[i], points[i+1], polygon.Fill())
}
return nil
}
func getPoints(x, y, sides int, radius float64) []image.Point {
points := make([]image.Point, sides+1)
// Compute the shape's apexes (thanks to Jasmin Blanchette)
fullCircle := 2 * math.Pi
x0, y0 := float64(x), float64(y)
for i := 0; i < sides; i++ {
θ := float64(float64(i) * fullCircle / float64(sides))
x1 := x0 + (radius * math.Sin(θ))
y1 := y0 + (radius * math.Cos(θ))
points[i] = image.Pt(int(x1), int(y1))
}
points[sides] = points[0] // close the shape
return points
}
// Based on my Perl Image::Base.pm module's line() method
func drawLine(img draw.Image, start, end image.Point,
fill color.Color) {
x0, x1 := start.X, end.X
y0, y1 := start.Y, end.Y
Δx := math.Abs(float64(x1 - x0))
Δy := math.Abs(float64(y1 - y0))
if Δx >= Δy { // shallow slope
if x0 > x1 {
x0, y0, x1, y1 = x1, y1, x0, y0
}
y := y0
yStep := 1
if y0 > y1 {
yStep = -1
}
remainder := float64(int(Δx/2)) - Δx
for x := x0; x <= x1; x++ {
img.Set(x, y, fill)
remainder += Δy
if remainder >= 0.0 {
remainder -= Δx
y += yStep
}
}
} else { // steep slope
if y0 > y1 {
x0, y0, x1, y1 = x1, y1, x0, y0
}
x := x0
xStep := 1
if x0 > x1 {
xStep = -1
}
remainder := float64(int(Δy/2)) - Δy
for y := y0; y <= y1; y++ {
img.Set(x, y, fill)
remainder += Δx
if remainder >= 0.0 {
remainder -= Δy
x += xStep
}
}
}
}
func (polygon *RegularPolygon) String() string {
return fmt.Sprintf("polygon(fill=%v, radius=%d, sides=%d)",
polygon.Fill(), polygon.Radius(), polygon.sides)
}
type Option struct {
Fill color.Color
Radius int
}
// Factory function: The returned Shaper can only call
// Shaper methods unless we use type assertion: see main() in
// ../main.go for examples.
// Note that this function could return a CircularShaper (in which case
// no type assertion would be needed to set the Radius); but we prefer the
// to be more general since that allows us to add other non-CircularShaper
// shapes later without requiring existing callers to be changed.
func New(shape string, option Option) (Shaper, error) {
sidesForShape := map[string]int{"triangle": 3, "square": 4,
"pentagon": 5, "hexagon": 6, "heptagon": 7, "octagon": 8,
"enneagon": 9, "nonagon": 9, "decagon": 10}
if sides, found := sidesForShape[shape]; found {
return NewRegularPolygon(option.Fill, option.Radius, sides), nil
}
if shape != "circle" {
return nil, fmt.Errorf("shapes.New(): invalid shape '%s'", shape)
}
return NewCircle(option.Fill, option.Radius), nil
}
func FilledImage(width, height int, fill color.Color) draw.Image {
if fill == nil { // We silently treat a nil color as black
fill = color.Black
}
width = saneLength(width)
height = saneLength(height)
img := image.NewRGBA(image.Rect(0, 0, width, height))
draw.Draw(img, img.Bounds(), &image.Uniform{fill}, image.ZP, draw.Src)
return img
}
func DrawShapes(img draw.Image, x, y int, shapes ...Shaper) error {
for _, shape := range shapes {
if err := shape.Draw(img, x, y); err != nil {
return err
}
// Thicker so that it shows up better in screenshots
if err := shape.Draw(img, x+1, y); err != nil {
return err
}
if err := shape.Draw(img, x, y+1); err != nil {
return err
}
}
return nil
}
func SaveImage(img image.Image, filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
switch strings.ToLower(filepath.Ext(filename)) {
case ".jpg", ".jpeg":
return jpeg.Encode(file, img, nil)
case ".png":
return png.Encode(file, img)
}
return fmt.Errorf("shapes.SaveImage(): '%s' has an unrecognized "+
"suffix", filename)
}

121
src/shaper2/shaper2.go Normal file

@ -0,0 +1,121 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"image"
"image/color"
"log"
"os"
"shaper2/shapes"
)
func main() {
log.SetFlags(0)
const width, height = 400, 200
img := shapes.FilledImage(width, height,
color.RGBA{0xFF, 0xFF, 0xFF, 0xFF})
x, y := width/4, height/2
red := color.RGBA{0xFF, 0, 0, 0xFF}
blue := color.RGBA{0, 0, 0xFF, 0xFF}
// Purely for testing New() vs. New*()
if len(os.Args) == 1 {
fmt.Println("Using NewCircle() & NewRegularPolygon()")
circle := shapes.NewCircle(blue, 90)
circle.SetFill(red) // Uses the aggregated shape.SetFill method
octagon := shapes.NewRegularPolygon(red, 75, 8)
octagon.SetFill(blue) // Uses the aggregated circle.shape.SetFill
polygon := shapes.NewRegularPolygon(image.Black, 65, 4)
if err := shapes.DrawShapes(img, x, y, circle, octagon, polygon);
err != nil {
fmt.Println(err)
}
sanityCheck("circle", circle)
sanityCheck("octagon", octagon)
sanityCheck("polygon", polygon)
} else {
fmt.Println("Using New()")
// The Shapers returned by New can only call Shaper methods
// (Draw(), Fill(), SetFill()); however, we can use type
// assertion if we need to access other methods.
if _, err := shapes.New("Misshapen", shapes.Option{blue, 5});
err == nil {
fmt.Println("unexpectedly got a non-nil invalid shape!")
}
circle, _ := shapes.New("circle", shapes.Option{blue, 5})
circle.SetFill(red)
circle.(shapes.Radiuser).SetRadius(90)
octagon, _ := shapes.New("octagon", shapes.Option{red, 10})
octagon.SetFill(blue)
if octagon, ok := octagon.(shapes.Radiuser); ok {
octagon.SetRadius(75)
}
polygon, _ := shapes.New("square", shapes.Option{Radius: 65})
if err := shapes.DrawShapes(img, x, y, circle, octagon, polygon);
err != nil {
fmt.Println(err)
}
sanityCheck("circle", circle)
sanityCheck("octagon", octagon)
sanityCheck("polygon", polygon)
}
polygon := shapes.NewRegularPolygon(color.RGBA{0, 0x7F, 0, 0xFF}, 65, 4)
showShapeDetails(polygon)
y = 30
for i, radius := range []int{60, 55, 50, 45, 40} {
polygon.SetRadius(radius)
polygon.SetSides(i + 5)
x += radius
y += height / 8
if err := shapes.DrawShapes(img, x, y, polygon); err != nil {
fmt.Println(err)
}
}
filename := "shapes.png"
if err := shapes.SaveImage(img, filename); err != nil {
log.Println(err)
} else {
fmt.Println("Saved", filename)
}
fmt.Println("OK")
img = shapes.FilledImage(width, height, image.White)
x, y = width/3, height/4
}
func sanityCheck(name string, shape shapes.Shaper) {
fmt.Print("name=", name, " ")
fmt.Print("fill=", shape.Fill(), " ")
if shape, ok := shape.(shapes.Radiuser); ok {
fmt.Print("radius=", shape.Radius(), " ")
}
if shape, ok := shape.(shapes.Sideser); ok {
fmt.Print("sides=", shape.Sides(), " ")
}
fmt.Println()
}
func showShapeDetails(shape shapes.Shaper) {
fmt.Print("fill=", shape.Fill(), " ") // All shapes have a fill color
if shape, ok := shape.(shapes.Radiuser); ok { // shadow variable
fmt.Print("radius=", shape.Radius(), " ")
}
if shape, ok := shape.(shapes.Sideser); ok { // shadow variable
fmt.Print("sides=", shape.Sides(), " ")
}
fmt.Println()
}

@ -0,0 +1,358 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package shapes
import (
"fmt"
"image"
"image/color"
"image/draw"
"image/jpeg"
"image/png"
"log"
"math"
"os"
"path/filepath"
"runtime"
"strings"
)
var saneLength, saneRadius, saneSides func(int) int
func init() {
saneLength = makeBoundedIntFunc(1, 4096)
saneRadius = makeBoundedIntFunc(1, 1024)
saneSides = makeBoundedIntFunc(3, 60)
}
func makeBoundedIntFunc(minimum, maximum int) func(int) int {
return func(x int) int {
valid := x
switch {
case x < minimum:
valid = minimum
case x > maximum:
valid = maximum
}
if valid != x {
log.Printf("%s(): replaced %d with %d\n", caller(1), x, valid)
}
return valid
}
}
type Shaper interface {
Drawer // Draw()
Filler // Fill(); SetFill()
}
type Drawer interface {
Draw(img draw.Image, x, y int) error
}
type Filler interface {
Fill() color.Color
SetFill(fill color.Color)
}
type Radiuser interface {
Radius() int
SetRadius(radius int)
}
type Sideser interface {
Sides() int
SetSides(sides int)
}
/*
This is unexported so that we are forced to use NewCircle() to create
one thus ensuring that we always start with valid values since the zero
values are not acceptable in this case. Of course the privacy can only
be enforced outside the shapes package.
newShape() is unexported since we don't want undrawable shapes to be
created.
This satisfies the Filler interface.
*/
type shape struct{ fill color.Color }
func newShape(fill color.Color) shape {
if fill == nil { // We silently treat a nil color as black
fill = color.Black
}
return shape{fill}
}
func (shape shape) Fill() color.Color { return shape.fill }
func (shape *shape) SetFill(fill color.Color) {
if fill == nil { // We silently treat a nil color as black
fill = color.Black
}
shape.fill = fill
}
// The zero value is invalid! Use NewCircle() to create a valid Circle.
type Circle struct {
shape
radius int
}
// By calling newShape() we pass on any checking to newShape() without
// having to know what if any is required. This satisfies the Filler,
// Radiuser, Drawer, and Stringer interfaces.
func NewCircle(fill color.Color, radius int) *Circle {
return &Circle{newShape(fill), saneRadius(radius)}
}
func (circle *Circle) Radius() int {
return circle.radius
}
func (circle *Circle) SetRadius(radius int) {
circle.radius = saneRadius(radius)
}
func (circle *Circle) Draw(img draw.Image, x, y int) error {
// Algorithm taken from
// http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
// No need to check the radius is in bounds because you can only
// create circles using NewCircle() which guarantees it is within
// bounds. But the x, y might be outside the image so we check.
if err := checkBounds(img, x, y); err != nil {
return err
}
fill, radius := circle.fill, circle.radius
x0, y0 := x, y
f := 1 - radius
ddF_x, ddF_y := 1, -2*radius
x, y = 0, radius
img.Set(x0, y0+radius, fill)
img.Set(x0, y0-radius, fill)
img.Set(x0+radius, y0, fill)
img.Set(x0-radius, y0, fill)
for x < y {
if f >= 0 {
y--
ddF_y += 2
f += ddF_y
}
x++
ddF_x += 2
f += ddF_x
img.Set(x0+x, y0+y, fill)
img.Set(x0-x, y0+y, fill)
img.Set(x0+x, y0-y, fill)
img.Set(x0-x, y0-y, fill)
img.Set(x0+y, y0+x, fill)
img.Set(x0-y, y0+x, fill)
img.Set(x0+y, y0-x, fill)
img.Set(x0-y, y0-x, fill)
}
return nil
}
func (circle *Circle) String() string {
return fmt.Sprintf("circle(fill=%v, radius=%d)", circle.fill,
circle.radius)
}
func checkBounds(img image.Image, x, y int) error {
if !image.Rect(x, y, x, y).In(img.Bounds()) {
return fmt.Errorf("%s(): point (%d, %d) is outside the image\n",
caller(1), x, y)
}
return nil
}
func caller(steps int) string {
name := "?"
if pc, _, _, ok := runtime.Caller(steps + 1); ok {
name = filepath.Base(runtime.FuncForPC(pc).Name())
}
return name
}
// The zero value is invalid! Use NewRegularPolygon() to create a valid
// RegularPolygon.
type RegularPolygon struct {
*Circle
sides int
}
// This satisfies the Filler, Radiuser, Drawer, Sideser, and Stringer
// interfaces.
func NewRegularPolygon(fill color.Color, radius,
sides int) *RegularPolygon {
// By calling NewCircle() we pass on any checking (e.g., bounds
// checking) to NewCircle() without having to know what if any is
// required.
return &RegularPolygon{NewCircle(fill, radius), saneSides(sides)}
}
func (polygon *RegularPolygon) Sides() int {
return polygon.sides
}
func (polygon *RegularPolygon) SetSides(sides int) {
polygon.sides = saneSides(sides)
}
func (polygon *RegularPolygon) Draw(img draw.Image, x, y int) error {
// No need to check the radius or sides are in bounds because you can
// only create polygons using NewRegularPolygon() which guarantees they
// are within bounds. But the x, y might be outside the image so we
// check. len(points) == sides + 1
if err := checkBounds(img, x, y); err != nil {
return err
}
points := getPoints(x, y, polygon.sides, float64(polygon.Radius()))
for i := 0; i < polygon.sides; i++ { // Draw lines between the apexes
drawLine(img, points[i], points[i+1], polygon.Fill())
}
return nil
}
func getPoints(x, y, sides int, radius float64) []image.Point {
points := make([]image.Point, sides+1)
// Compute the shape's apexes (thanks to Jasmin Blanchette)
fullCircle := 2 * math.Pi
x0, y0 := float64(x), float64(y)
for i := 0; i < sides; i++ {
θ := float64(float64(i) * fullCircle / float64(sides))
x1 := x0 + (radius * math.Sin(θ))
y1 := y0 + (radius * math.Cos(θ))
points[i] = image.Pt(int(x1), int(y1))
}
points[sides] = points[0] // close the shape
return points
}
// Based on my Perl Image::Base.pm module's line() method
func drawLine(img draw.Image, start, end image.Point,
fill color.Color) {
x0, x1 := start.X, end.X
y0, y1 := start.Y, end.Y
Δx := math.Abs(float64(x1 - x0))
Δy := math.Abs(float64(y1 - y0))
if Δx >= Δy { // shallow slope
if x0 > x1 {
x0, y0, x1, y1 = x1, y1, x0, y0
}
y := y0
yStep := 1
if y0 > y1 {
yStep = -1
}
remainder := float64(int(Δx/2)) - Δx
for x := x0; x <= x1; x++ {
img.Set(x, y, fill)
remainder += Δy
if remainder >= 0.0 {
remainder -= Δx
y += yStep
}
}
} else { // steep slope
if y0 > y1 {
x0, y0, x1, y1 = x1, y1, x0, y0
}
x := x0
xStep := 1
if x0 > x1 {
xStep = -1
}
remainder := float64(int(Δy/2)) - Δy
for y := y0; y <= y1; y++ {
img.Set(x, y, fill)
remainder += Δx
if remainder >= 0.0 {
remainder -= Δy
x += xStep
}
}
}
}
func (polygon *RegularPolygon) String() string {
return fmt.Sprintf("polygon(fill=%v, radius=%d, sides=%d)",
polygon.Fill(), polygon.Radius(), polygon.sides)
}
type Option struct {
Fill color.Color
Radius int
}
// Factory function: The returned Shaper can only call
// Shaper methods unless we use type assertion: see main() in
// ../main.go for examples.
func New(shape string, option Option) (Shaper, error) {
sidesForShape := map[string]int{"triangle": 3, "square": 4,
"pentagon": 5, "hexagon": 6, "heptagon": 7, "octagon": 8,
"enneagon": 9, "nonagon": 9, "decagon": 10}
if sides, found := sidesForShape[shape]; found {
return NewRegularPolygon(option.Fill, option.Radius, sides), nil
}
if shape != "circle" {
return nil, fmt.Errorf("shapes.New(): invalid shape '%s'", shape)
}
return NewCircle(option.Fill, option.Radius), nil
}
func FilledImage(width, height int, fill color.Color) draw.Image {
if fill == nil { // We silently treat a nil color as black
fill = color.Black
}
width = saneLength(width)
height = saneLength(height)
img := image.NewRGBA(image.Rect(0, 0, width, height))
draw.Draw(img, img.Bounds(), &image.Uniform{fill}, image.ZP, draw.Src)
return img
}
func DrawShapes(img draw.Image, x, y int, shapes ...Drawer) error {
for _, shape := range shapes {
if err := shape.Draw(img, x, y); err != nil {
return err
}
// Thicker so that it shows up better in screenshots
if err := shape.Draw(img, x+1, y); err != nil {
return err
}
if err := shape.Draw(img, x, y+1); err != nil {
return err
}
}
return nil
}
func SaveImage(img image.Image, filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
switch strings.ToLower(filepath.Ext(filename)) {
case ".jpg", ".jpeg":
return jpeg.Encode(file, img, nil)
case ".png":
return png.Encode(file, img)
}
return fmt.Errorf("shapes.SaveImage(): '%s' has an unrecognized "+
"suffix", filename)
}

120
src/shaper3/shaper3.go Normal file

@ -0,0 +1,120 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"image"
"image/color"
"log"
"os"
"shaper3/shapes"
)
func main() {
log.SetFlags(0)
const width, height = 400, 200
img := shapes.FilledImage(width, height,
color.RGBA{0xFF, 0xFF, 0xFF, 0xFF})
x, y := width/4, height/2
red := color.RGBA{0xFF, 0, 0, 0xFF}
blue := color.RGBA{0, 0, 0xFF, 0xFF}
// Purely for testing New() vs. New*()
if len(os.Args) == 1 {
fmt.Println("Using NewCircle() & NewRegularPolygon()")
circle := shapes.Circle{blue, 90}
circle.Color = red
octagon := shapes.RegularPolygon{red, 75, 8}
octagon.Color = blue
polygon := shapes.RegularPolygon{image.Black, 65, 4}
if err := shapes.DrawShapes(img, x, y, circle, octagon, polygon);
err != nil {
fmt.Println(err)
}
sanityCheck("circle", circle)
sanityCheck("octagon", octagon)
sanityCheck("polygon", polygon)
} else {
fmt.Println("Using New()")
if _, err := shapes.New("Misshapen", shapes.Option{blue, 5});
err == nil {
fmt.Println("unexpectedly got a non-nil invalid shape!")
}
shape, _ := shapes.New("circle", shapes.Option{blue, 5})
circle := shape.(shapes.Circle)
circle.Color = red
circle.Radius = 90
shape, _ = shapes.New("octagon", shapes.Option{red, 10})
octagon := shape.(shapes.RegularPolygon)
octagon.Color = blue
octagon.Radius = 75
polygon, _ := shapes.New("square", shapes.Option{Radius: 65})
if err := shapes.DrawShapes(img, x, y, circle, octagon, polygon);
err != nil {
fmt.Println(err)
}
sanityCheck("circle", circle)
sanityCheck("octagon", octagon)
sanityCheck("polygon", polygon)
}
polygon := shapes.RegularPolygon{color.RGBA{0, 0x7F, 0, 0xFF}, 65, 4}
sanityCheck("polygon", polygon)
y = 30
for i, radius := range []int{60, 55, 50, 45, 40} {
polygon.Radius = radius
polygon.Sides = i + 5
x += radius
y += height / 8
if err := shapes.DrawShapes(img, x, y, polygon); err != nil {
fmt.Println(err)
}
}
filename := "shapes.png"
if err := shapes.SaveImage(img, filename); err != nil {
log.Println(err)
} else {
fmt.Println("Saved", filename)
}
fmt.Println("OK")
img = shapes.FilledImage(width, height, image.White)
x, y = width/3, height/4
}
func sanityCheck(name string, drawer shapes.Drawer) {
fmt.Print("name=", name, " ")
var fill color.Color
radius, sides := -1, -1
if circle, ok := drawer.(shapes.Circle); ok {
fill = circle.Color
radius = circle.Radius
}
if polygon, ok := drawer.(shapes.RegularPolygon); ok {
fill = polygon.Color
radius = polygon.Radius
sides = polygon.Sides
}
if fill != nil {
fmt.Print("fill=", fill, " ")
}
if radius != -1 {
fmt.Print("radius=", radius, " ")
}
if sides != -1 {
fmt.Print("sides=", sides, " ")
}
fmt.Println()
}

@ -0,0 +1,269 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package shapes
import (
"fmt"
"image"
"image/color"
"image/draw"
"image/jpeg"
"image/png"
"math"
"os"
"path/filepath"
"runtime"
"strings"
)
func clamp(minimum, x, maximum int) int {
switch {
case x < minimum:
return minimum
case x > maximum:
return maximum
}
return x
}
func validFillColor(fill color.Color) color.Color {
if fill == nil { // We silently treat a nil color as black
return color.Black
}
return fill
}
type Drawer interface {
Draw(img draw.Image, x, y int) error
}
type Circle struct {
color.Color
Radius int
}
func (circle Circle) Draw(img draw.Image, x, y int) error {
// Algorithm taken from
// http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
// No need to check the radius is in bounds because you can only
// create circles using NewCircle() which guarantees it is within
// bounds. But the x, y might be outside the image so we check.
if err := checkBounds(img, x, y); err != nil {
return err
}
fill := validFillColor(circle.Color)
radius := clamp(1, circle.Radius, 1024)
x0, y0 := x, y
f := 1 - radius
ddF_x, ddF_y := 1, -2*radius
x, y = 0, radius
img.Set(x0, y0+radius, fill)
img.Set(x0, y0-radius, fill)
img.Set(x0+radius, y0, fill)
img.Set(x0-radius, y0, fill)
for x < y {
if f >= 0 {
y--
ddF_y += 2
f += ddF_y
}
x++
ddF_x += 2
f += ddF_x
img.Set(x0+x, y0+y, fill)
img.Set(x0-x, y0+y, fill)
img.Set(x0+x, y0-y, fill)
img.Set(x0-x, y0-y, fill)
img.Set(x0+y, y0+x, fill)
img.Set(x0-y, y0+x, fill)
img.Set(x0+y, y0-x, fill)
img.Set(x0-y, y0-x, fill)
}
return nil
}
func (circle Circle) String() string {
return fmt.Sprintf("circle(fill=%v, radius=%d)", circle.Color,
circle.Radius)
}
func checkBounds(img image.Image, x, y int) error {
if !image.Rect(x, y, x, y).In(img.Bounds()) {
return fmt.Errorf("%s(): point (%d, %d) is outside the image\n",
caller(1), x, y)
}
return nil
}
func caller(steps int) string {
name := "?"
if pc, _, _, ok := runtime.Caller(steps + 1); ok {
name = filepath.Base(runtime.FuncForPC(pc).Name())
}
return name
}
type RegularPolygon struct {
color.Color
Radius int
Sides int
}
func (polygon RegularPolygon) Draw(img draw.Image, x, y int) error {
// No need to check the radius or sides are in bounds because you can
// only create polygons using NewRegularPolygon() which guarantees they
// are within bounds. But the x, y might be outside the image so we
// check. len(points) == sides + 1
if err := checkBounds(img, x, y); err != nil {
return err
}
fill := validFillColor(polygon.Color)
radius := clamp(1, polygon.Radius, 1024)
sides := clamp(3, polygon.Sides, 60)
points := getPoints(x, y, sides, float64(radius))
for i := 0; i < sides; i++ { // Draw lines between the apexes
drawLine(img, points[i], points[i+1], fill)
}
return nil
}
func getPoints(x, y, sides int, radius float64) []image.Point {
points := make([]image.Point, sides+1)
// Compute the shape's apexes (thanks to Jasmin Blanchette)
fullCircle := 2 * math.Pi
x0, y0 := float64(x), float64(y)
for i := 0; i < sides; i++ {
θ := float64(float64(i) * fullCircle / float64(sides))
x1 := x0 + (radius * math.Sin(θ))
y1 := y0 + (radius * math.Cos(θ))
points[i] = image.Pt(int(x1), int(y1))
}
points[sides] = points[0] // close the shape
return points
}
// Based on my Perl Image::Base.pm module's line() method
func drawLine(img draw.Image, start, end image.Point,
fill color.Color) {
x0, x1 := start.X, end.X
y0, y1 := start.Y, end.Y
Δx := math.Abs(float64(x1 - x0))
Δy := math.Abs(float64(y1 - y0))
if Δx >= Δy { // shallow slope
if x0 > x1 {
x0, y0, x1, y1 = x1, y1, x0, y0
}
y := y0
yStep := 1
if y0 > y1 {
yStep = -1
}
remainder := float64(int(Δx/2)) - Δx
for x := x0; x <= x1; x++ {
img.Set(x, y, fill)
remainder += Δy
if remainder >= 0.0 {
remainder -= Δx
y += yStep
}
}
} else { // steep slope
if y0 > y1 {
x0, y0, x1, y1 = x1, y1, x0, y0
}
x := x0
xStep := 1
if x0 > x1 {
xStep = -1
}
remainder := float64(int(Δy/2)) - Δy
for y := y0; y <= y1; y++ {
img.Set(x, y, fill)
remainder += Δx
if remainder >= 0.0 {
remainder -= Δy
x += xStep
}
}
}
}
func (polygon RegularPolygon) String() string {
return fmt.Sprintf("polygon(fill=%v, radius=%d, sides=%d)",
polygon.Color, polygon.Radius, polygon.Sides)
}
type Option struct {
Fill color.Color
Radius int
}
func New(shape string, option Option) (Drawer, error) {
sidesForShape := map[string]int{"triangle": 3, "square": 4,
"pentagon": 5, "hexagon": 6, "heptagon": 7, "octagon": 8,
"enneagon": 9, "nonagon": 9, "decagon": 10}
if sides, found := sidesForShape[shape]; found {
return RegularPolygon{option.Fill, option.Radius, sides}, nil
}
if shape != "circle" {
return nil, fmt.Errorf("shapes.New(): invalid shape '%s'", shape)
}
return Circle{option.Fill, option.Radius}, nil
}
func FilledImage(width, height int, fill color.Color) draw.Image {
fill = validFillColor(fill)
width = clamp(1, width, 4096)
height = clamp(1, height, 4096)
img := image.NewRGBA(image.Rect(0, 0, width, height))
draw.Draw(img, img.Bounds(), &image.Uniform{fill}, image.ZP, draw.Src)
return img
}
func DrawShapes(img draw.Image, x, y int, drawers ...Drawer) error {
for _, drawer := range drawers {
if err := drawer.Draw(img, x, y); err != nil {
return err
}
// Thicker so that it shows up better in screenshots
if err := drawer.Draw(img, x+1, y); err != nil {
return err
}
if err := drawer.Draw(img, x, y+1); err != nil {
return err
}
}
return nil
}
func SaveImage(img image.Image, filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
switch strings.ToLower(filepath.Ext(filename)) {
case ".jpg", ".jpeg":
return jpeg.Encode(file, img, nil)
case ".png":
return png.Encode(file, img)
}
return fmt.Errorf("shapes.SaveImage(): '%s' has an unrecognized "+
"suffix", filename)
}

@ -0,0 +1,39 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"image"
"image/color"
"shaper_ans1/shapes"
)
func main() {
img := shapes.FilledImage(420, 220, image.White)
fill := color.RGBA{200, 200, 200, 0xFF} // light gray
for i := 0; i < 10; i++ {
width, height := 40+(20*i), 20+(10*i)
rectangle := shapes.NewRectangle(fill,
image.Rect(0, 0, width, height))
rectangle.SetFilled(true)
x := 10 + (20 * i)
for j := i / 2; j >= 0; j-- {
rectangle.Draw(img, x+j, (x/2)+j)
}
fill.R -= uint8(i * 5)
fill.G = fill.R
fill.B = fill.R
}
shapes.SaveImage(img, "rectangle.png")
}

@ -0,0 +1,419 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package shapes
import (
"fmt"
"image"
"image/color"
"image/draw"
"image/jpeg"
"image/png"
"log"
"math"
"os"
"path/filepath"
"runtime"
"strings"
)
var saneLength, saneRadius, saneSides func(int) int
func init() {
saneLength = makeBoundedIntFunc(1, 4096)
saneRadius = makeBoundedIntFunc(1, 1024)
saneSides = makeBoundedIntFunc(3, 60)
}
func makeBoundedIntFunc(minimum, maximum int) func(int) int {
return func(x int) int {
valid := x
switch {
case x < minimum:
valid = minimum
case x > maximum:
valid = maximum
}
if valid != x {
log.Printf("%s(): replaced %d with %d\n", caller(1), x, valid)
}
return valid
}
}
func saneRectangle(rect image.Rectangle) image.Rectangle {
rect = rect.Canon()
width, height := rect.Dx(), rect.Dy()
if width < 1 || width > 4096 || height < 1 || height > 4096 {
return image.Rect(0, 0, 16, 16)
}
return rect
}
type Shaper interface {
Fill() color.Color
SetFill(fill color.Color)
Draw(img draw.Image, x, y int) error
}
type CircularShaper interface {
Shaper // Fill(); SetFill(); Draw()
Radius() int
SetRadius(radius int)
}
type RegularPolygonalShaper interface {
CircularShaper // Fill(); SetFill(); Draw(); Radius(); SetRadius()
Sides() int
SetSides(sides int)
}
type RectangularShaper interface {
Shaper // Fill(); SetFill(); Draw()
Rect() image.Rectangle
SetRect(image.Rectangle)
Filled() bool
SetFilled(bool)
}
/*
This is unexported so that we are forced to use NewCircle() to create
one thus ensuring that we always start with valid values since the zero
values are not acceptable in this case. Of course the privacy can only
be enforced outside the shapes package.
newShape() is unexported since we don't want undrawable shapes to be
created.
*/
type shape struct{ fill color.Color }
func newShape(fill color.Color) shape {
if fill == nil { // We silently treat a nil color as black
fill = color.Black
}
return shape{fill}
}
func (shape shape) Fill() color.Color { return shape.fill }
func (shape *shape) SetFill(fill color.Color) {
if fill == nil { // We silently treat a nil color as black
fill = color.Black
}
shape.fill = fill
}
// The zero value is invalid! Use NewCircle() to create a valid Circle.
type Circle struct {
shape
radius int
}
// By calling newShape() we pass on any checking to newShape() without
// having to know what if any is required.
func NewCircle(fill color.Color, radius int) *Circle {
return &Circle{newShape(fill), saneRadius(radius)}
}
func (circle *Circle) Radius() int {
return circle.radius
}
func (circle *Circle) SetRadius(radius int) {
circle.radius = saneRadius(radius)
}
func (circle *Circle) Draw(img draw.Image, x, y int) error {
// Algorithm taken from
// http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
// No need to check the radius is in bounds because you can only
// create circles using NewCircle() which guarantees it is within
// bounds. But the x, y might be outside the image so we check.
if err := checkBounds(img, x, y); err != nil {
return err
}
fill, radius := circle.fill, circle.radius
x0, y0 := x, y
f := 1 - radius
ddF_x, ddF_y := 1, -2*radius
x, y = 0, radius
img.Set(x0, y0+radius, fill)
img.Set(x0, y0-radius, fill)
img.Set(x0+radius, y0, fill)
img.Set(x0-radius, y0, fill)
for x < y {
if f >= 0 {
y--
ddF_y += 2
f += ddF_y
}
x++
ddF_x += 2
f += ddF_x
img.Set(x0+x, y0+y, fill)
img.Set(x0-x, y0+y, fill)
img.Set(x0+x, y0-y, fill)
img.Set(x0-x, y0-y, fill)
img.Set(x0+y, y0+x, fill)
img.Set(x0-y, y0+x, fill)
img.Set(x0+y, y0-x, fill)
img.Set(x0-y, y0-x, fill)
}
return nil
}
func (circle *Circle) String() string {
return fmt.Sprintf("circle(fill=%v, radius=%d)", circle.fill,
circle.radius)
}
func checkBounds(img image.Image, x, y int) error {
if !image.Rect(x, y, x, y).In(img.Bounds()) {
return fmt.Errorf("%s(): point (%d, %d) is outside the image\n",
caller(1), x, y)
}
return nil
}
func caller(steps int) string {
name := "?"
if pc, _, _, ok := runtime.Caller(steps + 1); ok {
name = filepath.Base(runtime.FuncForPC(pc).Name())
}
return name
}
// The zero value is invalid! Use NewRegularPolygon() to create a valid
// RegularPolygon.
type RegularPolygon struct {
*Circle
sides int
}
func NewRegularPolygon(fill color.Color, radius, sides int) *RegularPolygon {
// By calling NewCircle() we pass on any checking (e.g., bounds
// checking) to NewCircle() without having to know what if any is
// required.
return &RegularPolygon{NewCircle(fill, radius), saneSides(sides)}
}
func (polygon *RegularPolygon) Sides() int {
return polygon.sides
}
func (polygon *RegularPolygon) SetSides(sides int) {
polygon.sides = saneSides(sides)
}
func (polygon *RegularPolygon) Draw(img draw.Image, x, y int) error {
// No need to check the radius or sides are in bounds because you can
// only create polygons using NewRegularPolygon() which guarantees they
// are within bounds. But the x, y might be outside the image so we
// check. len(points) == sides + 1
if err := checkBounds(img, x, y); err != nil {
return err
}
points := getPoints(x, y, polygon.sides, float64(polygon.Radius()))
for i := 0; i < polygon.sides; i++ { // Draw lines between the apexes
drawLine(img, points[i], points[i+1], polygon.Fill())
}
return nil
}
func getPoints(x, y, sides int, radius float64) []image.Point {
points := make([]image.Point, sides+1)
// Compute the shape's apexes (thanks to Jasmin Blanchette)
fullCircle := 2 * math.Pi
x0, y0 := float64(x), float64(y)
for i := 0; i < sides; i++ {
θ := float64(float64(i) * fullCircle / float64(sides))
x1 := x0 + (radius * math.Sin(θ))
y1 := y0 + (radius * math.Cos(θ))
points[i] = image.Pt(int(x1), int(y1))
}
points[sides] = points[0] // close the shape
return points
}
// Based on my Perl Image::Base.pm module's line() method
func drawLine(img draw.Image, start, end image.Point,
fill color.Color) {
x0, x1 := start.X, end.X
y0, y1 := start.Y, end.Y
Δx := math.Abs(float64(x1 - x0))
Δy := math.Abs(float64(y1 - y0))
if Δx >= Δy { // shallow slope
if x0 > x1 {
x0, y0, x1, y1 = x1, y1, x0, y0
}
y := y0
yStep := 1
if y0 > y1 {
yStep = -1
}
remainder := float64(int(Δx/2)) - Δx
for x := x0; x <= x1; x++ {
img.Set(x, y, fill)
remainder += Δy
if remainder >= 0.0 {
remainder -= Δx
y += yStep
}
}
} else { // steep slope
if y0 > y1 {
x0, y0, x1, y1 = x1, y1, x0, y0
}
x := x0
xStep := 1
if x0 > x1 {
xStep = -1
}
remainder := float64(int(Δy/2)) - Δy
for y := y0; y <= y1; y++ {
img.Set(x, y, fill)
remainder += Δx
if remainder >= 0.0 {
remainder -= Δy
x += xStep
}
}
}
}
func (polygon *RegularPolygon) String() string {
return fmt.Sprintf("polygon(fill=%v, radius=%d, sides=%d)",
polygon.Fill(), polygon.Radius(), polygon.sides)
}
// The zero value is invalid! Use NewRectangle() to create a valid
// Rectangle.
type Rectangle struct {
shape
image.Rectangle
filled bool
}
func NewRectangle(fill color.Color, rect image.Rectangle) *Rectangle {
return &Rectangle{newShape(fill), saneRectangle(rect), false}
}
func (rectangle *Rectangle) Rect() image.Rectangle {
return rectangle.Rectangle
}
func (rectangle *Rectangle) SetRect(rect image.Rectangle) {
rectangle.Rectangle = saneRectangle(rect)
}
func (rectangle *Rectangle) Filled() bool {
return rectangle.filled
}
func (rectangle *Rectangle) SetFilled(filled bool) {
rectangle.filled = filled
}
// x, y are the top-left (for radius-based shapes they are the middle)
func (rectangle *Rectangle) Draw(img draw.Image, x, y int) error {
if err := checkBounds(img, x, y); err != nil {
return err
}
fill := rectangle.fill
x0, x1 := rectangle.Rectangle.Min.X, rectangle.Rectangle.Max.X
y0, y1 := rectangle.Rectangle.Min.Y, rectangle.Rectangle.Max.Y
x0 += x
x1 += x
y0 += y
y1 += y
if !rectangle.filled {
drawLine(img, image.Point{x0, y0}, image.Point{x1, y0}, fill)
drawLine(img, image.Point{x0, y1}, image.Point{x1, y1}, fill)
drawLine(img, image.Point{x0, y0}, image.Point{x0, y1}, fill)
drawLine(img, image.Point{x1, y0}, image.Point{x1, y1}, fill)
} else {
draw.Draw(img, image.Rect(x0, y0, x1+1, y1+1),
&image.Uniform{fill}, image.Pt(x0, y0), draw.Src)
}
return nil
}
type Option struct {
Fill color.Color
Radius int
Rect image.Rectangle
Filled bool
}
func New(shape string, option Option) (Shaper, error) {
sidesForShape := map[string]int{"triangle": 3, "square": 4,
"pentagon": 5, "hexagon": 6, "heptagon": 7, "octagon": 8,
"enneagon": 9, "nonagon": 9, "decagon": 10}
if sides, found := sidesForShape[shape]; found {
return NewRegularPolygon(option.Fill, option.Radius, sides), nil
}
if shape == "rectangle" {
rect := NewRectangle(option.Fill, option.Rect)
rect.SetFilled(option.Filled)
return rect, nil
}
if shape != "circle" {
return nil, fmt.Errorf("shapes.New(): invalid shape '%s'", shape)
}
return NewCircle(option.Fill, option.Radius), nil
}
func FilledImage(width, height int, fill color.Color) draw.Image {
if fill == nil {
fill = color.Black
}
width = saneLength(width)
height = saneLength(height)
img := image.NewRGBA(image.Rect(0, 0, width, height))
draw.Draw(img, img.Bounds(), &image.Uniform{fill}, image.ZP, draw.Src)
return img
}
func DrawShapes(img draw.Image, x, y int, shapes ...Shaper) error {
for _, shape := range shapes {
if err := shape.Draw(img, x, y); err != nil {
return err
}
// Thicker so that it shows up better in screenshots
if err := shape.Draw(img, x+1, y); err != nil {
return err
}
if err := shape.Draw(img, x, y+1); err != nil {
return err
}
}
return nil
}
func SaveImage(img image.Image, filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
switch strings.ToLower(filepath.Ext(filename)) {
case ".jpg", ".jpeg":
return jpeg.Encode(file, img, nil)
case ".png":
return png.Encode(file, img)
}
return fmt.Errorf("shapes.SaveImage(): '%s' has an unrecognized "+
"suffix", filename)
}

@ -0,0 +1,39 @@
// Copyright © 2011-12 Qtrac Ltd.
//
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"image"
"image/color"
"shaper_ans2/shapes"
)
func main() {
img := shapes.FilledImage(420, 220, image.White)
fill := color.RGBA{200, 200, 200, 0xFF} // light gray
for i := 0; i < 10; i++ {
width, height := 40+(20*i), 20+(10*i)
rectangle := shapes.NewRectangle(fill,
image.Rect(0, 0, width, height))
rectangle.SetFilled(true)
x := 10 + (20 * i)
for j := i / 2; j >= 0; j-- {
rectangle.Draw(img, x+j, (x/2)+j)
}
fill.R -= uint8(i * 5)
fill.G = fill.R
fill.B = fill.R
}
shapes.SaveImage(img, "rectangle.png")
}

Some files were not shown because too many files have changed in this diff Show More