Initial Commit
This commit is contained in:
commit
bbb948328e
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
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
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
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
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
|
133
src/americanise/americanise.go
Normal file
133
src/americanise/americanise.go
Normal file
@ -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
|
||||
}
|
70
src/americanise/americanise_test.go
Normal file
70
src/americanise/americanise_test.go
Normal file
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
62
src/americanise/british-american.txt
Normal file
62
src/americanise/british-american.txt
Normal file
@ -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
|
14
src/americanise/expected.txt
Normal file
14
src/americanise/expected.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 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
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.
|
100
src/apachereport1/apachereport.go
Normal file
100
src/apachereport1/apachereport.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
112
src/apachereport2/apachereport.go
Normal file
112
src/apachereport2/apachereport.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
93
src/apachereport3/apachereport.go
Normal file
93
src/apachereport3/apachereport.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
96
src/apachereport4/apachereport.go
Normal file
96
src/apachereport4/apachereport.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 (
|
||||
"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)
|
||||
}
|
||||
}
|
104
src/apachereport5/apachereport.go
Normal file
104
src/apachereport5/apachereport.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
219
src/archive_file_list/archive_file_list.go
Normal file
219
src/archive_file_list/archive_file_list.go
Normal file
@ -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
|
||||
}
|
153
src/archive_file_list_ans/archive_file_list.go
Normal file
153
src/archive_file_list_ans/archive_file_list.go
Normal file
@ -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
|
||||
}
|
7
src/bigdigits/0123456789.txt
Normal file
7
src/bigdigits/0123456789.txt
Normal file
@ -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
|
62
src/bigdigits/bigdigits.go
Normal file
62
src/bigdigits/bigdigits.go
Normal file
@ -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"},
|
||||
}
|
55
src/bigdigits/bigdigits_test.go
Normal file
55
src/bigdigits/bigdigits_test.go
Normal file
@ -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")
|
||||
}
|
||||
}
|
9
src/bigdigits_ans/0123456789.txt
Normal file
9
src/bigdigits_ans/0123456789.txt
Normal file
@ -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
|
||||
*********************************************************************
|
79
src/bigdigits_ans/bigdigits.go
Normal file
79
src/bigdigits_ans/bigdigits.go
Normal file
@ -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"},
|
||||
}
|
55
src/bigdigits_ans/bigdigits_test.go
Normal file
55
src/bigdigits_ans/bigdigits_test.go
Normal file
@ -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
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
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
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
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 }
|
16
src/cgrep3/util_freebsd.go
Normal file
16
src/cgrep3/util_freebsd.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 }
|
16
src/cgrep3/util_linux.go
Normal file
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 }
|
28
src/cgrep3/util_windows.go
Normal file
28
src/cgrep3/util_windows.go
Normal file
@ -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
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()
|
||||
}
|
||||
}
|
||||
}
|
107
src/common_prefix/common_prefix.go
Normal file
107
src/common_prefix/common_prefix.go
Normal file
@ -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
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
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)
|
||||
}
|
||||
}
|
149
src/findduplicates/findduplicates.go
Normal file
149
src/findduplicates/findduplicates.go
Normal file
@ -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
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
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
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())
|
||||
}
|
104
src/fuzzy/fuzzybool/fuzzybool.go
Normal file
104
src/fuzzy/fuzzybool/fuzzybool.go
Normal file
@ -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)
|
||||
}
|
95
src/fuzzy_immutable/fuzzybool/fuzzybool.go
Normal file
95
src/fuzzy_immutable/fuzzybool/fuzzybool.go
Normal file
@ -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)
|
||||
}
|
44
src/fuzzy_immutable/immutable_fuzzy.go
Normal file
44
src/fuzzy_immutable/immutable_fuzzy.go
Normal file
@ -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())
|
||||
}
|
111
src/fuzzy_mutable/fuzzybool/fuzzybool.go
Normal file
111
src/fuzzy_mutable/fuzzybool/fuzzybool.go
Normal file
@ -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)
|
||||
}
|
46
src/fuzzy_mutable/mutable_fuzzy.go
Normal file
46
src/fuzzy_mutable/mutable_fuzzy.go
Normal file
@ -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
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())
|
||||
}
|
104
src/fuzzy_value/fuzzybool/fuzzybool.go
Normal file
104
src/fuzzy_value/fuzzybool/fuzzybool.go
Normal file
@ -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)
|
||||
}
|
103
src/guess_separator/guess_separator.go
Normal file
103
src/guess_separator/guess_separator.go
Normal file
@ -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
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)
|
||||
}
|
74
src/imagetag1/imagetag1.go
Normal file
74
src/imagetag1/imagetag1.go
Normal file
@ -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
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
|
||||
}
|
139
src/indent_sort/indent_sort.go
Normal file
139
src/indent_sort/indent_sort.go
Normal file
@ -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
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
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
|
||||
}
|
205
src/invoicedata/invoicedata.go
Normal file
205
src/invoicedata/invoicedata.go
Normal file
@ -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
|
||||
}
|
BIN
src/invoicedata/invoices.gob.gz
Normal file
BIN
src/invoicedata/invoices.gob.gz
Normal file
Binary file not shown.
106
src/invoicedata/jsn.go
Normal file
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
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
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
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
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
|
||||
}
|
228
src/invoicedata_ans/invoicedata.go
Normal file
228
src/invoicedata_ans/invoicedata.go
Normal file
@ -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
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
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
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
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)
|
||||
}
|
124
src/linkcheck/linkutil/index.html
Normal file
124
src/linkcheck/linkutil/index.html
Normal file
@ -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 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 & 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 © 2006-11 <a href="about.html">Qtrac Ltd.</a>
|
||||
All Rights 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—both PyQt4 and C++/Qt 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 2, Python 3, and <a
|
||||
href="http://golang.org">Go</a> programming;
|
||||
<li>C++/Qt 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)—a very
|
||||
concise summary of Python 2 ↔ 3.1 differences plus
|
||||
new Python 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 OS X, and Windows, and in source form for other platforms.
|
||||
</p>
|
||||
|
||||
<p><a href="#top">Top</a></p><hr>
|
||||
</body>
|
||||
</html>
|
18
src/linkcheck/linkutil/index.links
Normal file
18
src/linkcheck/linkutil/index.links
Normal file
@ -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
|
62
src/linkcheck/linkutil/linkutil.go
Normal file
62
src/linkcheck/linkutil/linkutil.go
Normal file
@ -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
|
||||
}
|
70
src/linkcheck/linkutil/linkutil_test.go
Normal file
70
src/linkcheck/linkutil/linkutil_test.go
Normal file
@ -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))
|
||||
}
|
||||
}
|
67
src/m3u2pls/David-Bowie-Singles.m3u
Normal file
67
src/m3u2pls/David-Bowie-Singles.m3u
Normal file
@ -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
|
102
src/m3u2pls/David-Bowie-Singles.pls
Normal file
102
src/m3u2pls/David-Bowie-Singles.pls
Normal file
@ -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
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))
|
||||
}
|
92
src/m3u2pls/m3u2pls_test.go
Normal file
92
src/m3u2pls/m3u2pls_test.go
Normal file
@ -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
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
|
||||
}
|
||||
}
|
103
src/ordered_map/ordered_map.go
Normal file
103
src/ordered_map/ordered_map.go
Normal file
@ -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
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
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
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)
|
||||
}
|
64
src/palindrome/palindrome.go
Normal file
64
src/palindrome/palindrome.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 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)
|
||||
}
|
||||
}
|
67
src/palindrome_ans/palindrome.go
Normal file
67
src/palindrome_ans/palindrome.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 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)
|
||||
}
|
||||
}
|
89
src/pi_by_digits/pi_by_digits.go
Normal file
89
src/pi_by_digits/pi_by_digits.go
Normal file
@ -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
|
||||
}
|
67
src/playlist/David-Bowie-Singles.m3u
Normal file
67
src/playlist/David-Bowie-Singles.m3u
Normal file
@ -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
|
102
src/playlist/David-Bowie-Singles.pls
Normal file
102
src/playlist/David-Bowie-Singles.pls
Normal file
@ -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
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)
|
||||
}
|
||||
}
|
88
src/polar2cartesian/polar2cartesian.go
Normal file
88
src/polar2cartesian/polar2cartesian.go
Normal file
@ -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
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
|
||||
}
|
127
src/qtrac.eu/omap/omap_test.go
Normal file
127
src/qtrac.eu/omap/omap_test.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.
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
134
src/quadratic_ans1/quadratic.go
Normal file
134
src/quadratic_ans1/quadratic.go
Normal file
@ -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)
|
||||
}
|
171
src/quadratic_ans2/quadratic.go
Normal file
171
src/quadratic_ans2/quadratic.go
Normal file
@ -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
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
|
||||
}
|
96
src/safemap/safemap_test.go
Normal file
96
src/safemap/safemap_test.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 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
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
|
||||
}
|
84
src/safeslice/safeslice_test.go
Normal file
84
src/safeslice/safeslice_test.go
Normal file
@ -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
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()
|
||||
}
|
351
src/shaper1/shapes/shapes.go
Normal file
351
src/shaper1/shapes/shapes.go
Normal file
@ -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
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()
|
||||
}
|
358
src/shaper2/shapes/shapes.go
Normal file
358
src/shaper2/shapes/shapes.go
Normal file
@ -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
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()
|
||||
}
|
269
src/shaper3/shapes/shapes.go
Normal file
269
src/shaper3/shapes/shapes.go
Normal file
@ -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)
|
||||
}
|
39
src/shaper_ans1/shaper1.go
Normal file
39
src/shaper_ans1/shaper1.go
Normal file
@ -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")
|
||||
}
|
419
src/shaper_ans1/shapes/shapes.go
Normal file
419
src/shaper_ans1/shapes/shapes.go
Normal file
@ -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)
|
||||
}
|
39
src/shaper_ans2/shaper2.go
Normal file
39
src/shaper_ans2/shaper2.go
Normal file
@ -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
Loading…
x
Reference in New Issue
Block a user