pkgsrc-Changes archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

CVS commit: pkgsrc/pkgtools/pkglint



Module Name:    pkgsrc
Committed By:   rillig
Date:           Sun Jan  1 15:15:48 UTC 2017

Modified Files:
        pkgsrc/pkgtools/pkglint: Makefile
        pkgsrc/pkgtools/pkglint/files: buildlink3.go category_test.go files.go
            globaldata.go licenses.go line.go mkline.go mkline_test.go
            mklines.go mkshparser_test.go package.go parser.go patches.go
            patches_test.go pkglint.go pkglint_test.go plist.go shell_test.go
            shtokenizer_test.go toplevel_test.go util.go vartype.go
            vartypecheck.go
Added Files:
        pkgsrc/pkgtools/pkglint/files/getopt: getopt.go getopt_test.go
        pkgsrc/pkgtools/pkglint/files/pkgver: vercmp.go vercmp_test.go
Removed Files:
        pkgsrc/pkgtools/pkglint/files: dir.go dir_test.go getopt.go
            getopt_test.go main.go main_test.go vercmp.go vercmp_test.go

Log Message:
Cleaned up and refactored code.

The getopt and pkgver code have been extracted to separate packages to make
them reusable.

Several other functions have been moved to make the structure easier to
understand:

* dir.go and main.go have been moved to pkglint.go
* utility functions from pkglint.go have been moved to mkline.go

Now pkglint.go contains only high-level code.


To generate a diff of this commit:
cvs rdiff -u -r1.505 -r1.506 pkgsrc/pkgtools/pkglint/Makefile
cvs rdiff -u -r1.7 -r1.8 pkgsrc/pkgtools/pkglint/files/buildlink3.go \
    pkgsrc/pkgtools/pkglint/files/licenses.go \
    pkgsrc/pkgtools/pkglint/files/patches_test.go
cvs rdiff -u -r1.4 -r1.5 pkgsrc/pkgtools/pkglint/files/category_test.go \
    pkgsrc/pkgtools/pkglint/files/mkshparser_test.go
cvs rdiff -u -r1.5 -r0 pkgsrc/pkgtools/pkglint/files/dir.go
cvs rdiff -u -r1.4 -r0 pkgsrc/pkgtools/pkglint/files/dir_test.go \
    pkgsrc/pkgtools/pkglint/files/getopt_test.go \
    pkgsrc/pkgtools/pkglint/files/vercmp_test.go
cvs rdiff -u -r1.8 -r1.9 pkgsrc/pkgtools/pkglint/files/files.go \
    pkgsrc/pkgtools/pkglint/files/pkglint_test.go
cvs rdiff -u -r1.6 -r0 pkgsrc/pkgtools/pkglint/files/getopt.go
cvs rdiff -u -r1.17 -r1.18 pkgsrc/pkgtools/pkglint/files/globaldata.go
cvs rdiff -u -r1.12 -r1.13 pkgsrc/pkgtools/pkglint/files/line.go
cvs rdiff -u -r1.9 -r0 pkgsrc/pkgtools/pkglint/files/main.go
cvs rdiff -u -r1.3 -r0 pkgsrc/pkgtools/pkglint/files/main_test.go
cvs rdiff -u -r1.19 -r1.20 pkgsrc/pkgtools/pkglint/files/mkline.go
cvs rdiff -u -r1.20 -r1.21 pkgsrc/pkgtools/pkglint/files/mkline_test.go
cvs rdiff -u -r1.10 -r1.11 pkgsrc/pkgtools/pkglint/files/mklines.go \
    pkgsrc/pkgtools/pkglint/files/plist.go \
    pkgsrc/pkgtools/pkglint/files/vartype.go
cvs rdiff -u -r1.14 -r1.15 pkgsrc/pkgtools/pkglint/files/package.go
cvs rdiff -u -r1.5 -r1.6 pkgsrc/pkgtools/pkglint/files/parser.go \
    pkgsrc/pkgtools/pkglint/files/toplevel_test.go
cvs rdiff -u -r1.11 -r1.12 pkgsrc/pkgtools/pkglint/files/patches.go \
    pkgsrc/pkgtools/pkglint/files/util.go
cvs rdiff -u -r1.16 -r1.17 pkgsrc/pkgtools/pkglint/files/pkglint.go
cvs rdiff -u -r1.13 -r1.14 pkgsrc/pkgtools/pkglint/files/shell_test.go
cvs rdiff -u -r1.3 -r1.4 pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go
cvs rdiff -u -r1.21 -r1.22 pkgsrc/pkgtools/pkglint/files/vartypecheck.go
cvs rdiff -u -r1.2 -r0 pkgsrc/pkgtools/pkglint/files/vercmp.go
cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/getopt/getopt.go \
    pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go
cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/pkgver/vercmp.go \
    pkgsrc/pkgtools/pkglint/files/pkgver/vercmp_test.go

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: pkgsrc/pkgtools/pkglint/Makefile
diff -u pkgsrc/pkgtools/pkglint/Makefile:1.505 pkgsrc/pkgtools/pkglint/Makefile:1.506
--- pkgsrc/pkgtools/pkglint/Makefile:1.505      Sun Jan  1 14:47:45 2017
+++ pkgsrc/pkgtools/pkglint/Makefile    Sun Jan  1 15:15:47 2017
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.505 2017/01/01 14:47:45 rillig Exp $
+# $NetBSD: Makefile,v 1.506 2017/01/01 15:15:47 rillig Exp $
 
 PKGNAME=       pkglint-5.4.14
 DISTFILES=     # none
@@ -10,7 +10,6 @@ COMMENT=      Verifier for NetBSD packages
 LICENSE=       2-clause-bsd
 CONFLICTS+=    pkglint4-[0-9]*
 
-WRKSRC=                ${WRKDIR}/netbsd.org/pkglint
 NO_CHECKSUM=   yes
 USE_LANGUAGES= c
 USE_TOOLS+=    pax
@@ -19,35 +18,37 @@ GO_SRCPATH= netbsd.org/pkglint
 
 SUBST_CLASSES+=                pkglint
 SUBST_STAGE.pkglint=   post-configure
-SUBST_FILES.pkglint+=  main.go package_test.go
+SUBST_FILES.pkglint+=  pkglint.go package_test.go
 SUBST_SED.pkglint+=    -e s\|@VERSION@\|${PKGNAME:S/pkglint-//}\|g
 SUBST_SED.pkglint+=    -e s\|@BMAKE@\|${MAKE:Q}\|g
 
 do-extract:
-       ${RUN} mkdir -p ${WRKDIR}/pkglint/plist-clash
-       ${RUN} cd ${FILESDIR} && ${PAX} -rw *.go *.y */*.go pkglint.[01] ${WRKDIR}/pkglint
+       ${RUN} cd ${FILESDIR} && ${PAX} -rw . ${WRKDIR}/pkglint
 
 pre-build:
        ${RUN} env GOPATH=${WRKDIR}:${BUILDLINK_DIR}/gopkg go generate ${GO_BUILD_PATTERN}
 
+pre-install:
+       ${RUN} rm -rf ${WRKDIR}/pkg
+
 do-install: do-install-man
 
 .include "../../mk/bsd.prefs.mk"
 
 do-install-man: .PHONY
-.if !empty(MANINSTALL:Mcatinstall)
-.  if defined(CATMAN_SECTION_SUFFIX) && !empty(CATMAN_SECTION_SUFFIX:M[Yy][Ee][Ss])
+.if ${MANINSTALL:Mcatinstall}
+.  if ${CATMAN_SECTION_SUFFIX:M[Yy][Ee][Ss]}
        ${INSTALL_MAN} ${WRKSRC}/pkglint.0 ${DESTDIR}${PREFIX}/${PKGMANDIR}/cat1/pkglint.1
 .  else
        ${INSTALL_MAN} ${WRKSRC}/pkglint.0 ${DESTDIR}${PREFIX}/${PKGMANDIR}/cat1
 .  endif
 .endif
-.if !empty(MANINSTALL:Mmaninstall)
+.if ${MANINSTALL:Mmaninstall}
        ${INSTALL_MAN} ${WRKSRC}/pkglint.1 ${DESTDIR}${PREFIX}/${PKGMANDIR}/man1
 .endif
 
 .include "../../lang/go/go-package.mk"
 .if !empty(PKGSRC_RUN_TEST:M[yY][eE][sS])
-.include "../../devel/go-check/buildlink3.mk"
+.  include "../../devel/go-check/buildlink3.mk"
 .endif
 .include "../../mk/bsd.pkg.mk"

Index: pkgsrc/pkgtools/pkglint/files/buildlink3.go
diff -u pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.7 pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.8
--- pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.7     Sun Dec  4 15:28:36 2016
+++ pkgsrc/pkgtools/pkglint/files/buildlink3.go Sun Jan  1 15:15:47 2017
@@ -1,6 +1,7 @@
 package main
 
 import (
+       "netbsd.org/pkglint/pkgver"
        "strings"
 )
 
@@ -139,7 +140,7 @@ func ChecklinesBuildlink3Mk(mklines *MkL
                        if doCheck {
                                if abi != nil && abi.lower != "" && !containsVarRef(abi.lower) {
                                        if api != nil && api.lower != "" && !containsVarRef(api.lower) {
-                                               if pkgverCmp(abi.lower, api.lower) < 0 {
+                                               if pkgver.Compare(abi.lower, api.lower) < 0 {
                                                        abiLine.Warnf("ABI version %q should be at least API version %q (see %s).",
                                                                abi.lower, api.lower, apiLine.ReferenceFrom(abiLine))
                                                }
Index: pkgsrc/pkgtools/pkglint/files/licenses.go
diff -u pkgsrc/pkgtools/pkglint/files/licenses.go:1.7 pkgsrc/pkgtools/pkglint/files/licenses.go:1.8
--- pkgsrc/pkgtools/pkglint/files/licenses.go:1.7       Sun Dec  4 15:28:36 2016
+++ pkgsrc/pkgtools/pkglint/files/licenses.go   Sun Jan  1 15:15:47 2017
@@ -119,7 +119,7 @@ func (lc *LicenseChecker) checkNode(cond
        var licenseFile string
        if G.Pkg != nil {
                if licenseFileValue, ok := G.Pkg.varValue("LICENSE_FILE"); ok {
-                       licenseFile = G.CurrentDir + "/" + resolveVarsInRelativePath(licenseFileValue, false)
+                       licenseFile = G.CurrentDir + "/" + lc.MkLine.resolveVarsInRelativePath(licenseFileValue, false)
                }
        }
        if licenseFile == "" {
Index: pkgsrc/pkgtools/pkglint/files/patches_test.go
diff -u pkgsrc/pkgtools/pkglint/files/patches_test.go:1.7 pkgsrc/pkgtools/pkglint/files/patches_test.go:1.8
--- pkgsrc/pkgtools/pkglint/files/patches_test.go:1.7   Tue Dec 13 00:58:07 2016
+++ pkgsrc/pkgtools/pkglint/files/patches_test.go       Sun Jan  1 15:15:47 2017
@@ -117,7 +117,7 @@ func (s *Suite) Test_ChecklinesPatch__er
        lines := s.NewLines("patch-ErrorCode",
                "$"+"NetBSD$",
                "",
-               "*** Error code 1", // Looks like a context diff, but isn’t.
+               "*** Error code 1", // Looks like a context diff, but isn't.
                "",
                "--- file.orig",
                "+++ file",
@@ -346,7 +346,7 @@ func (s *Suite) Test_ChecklinesPatch__em
 }
 
 // In some context lines, the leading space character is missing.
-// Since this is no problem for patch(1), pkglint also doesn’t complain.
+// Since this is no problem for patch(1), pkglint also doesn't complain.
 func (s *Suite) Test_ChecklinesPatch__context_lines_with_tab_instead_of_space(c *check.C) {
        lines := s.NewLines("patch-aa",
                "$"+"NetBSD$",

Index: pkgsrc/pkgtools/pkglint/files/category_test.go
diff -u pkgsrc/pkgtools/pkglint/files/category_test.go:1.4 pkgsrc/pkgtools/pkglint/files/category_test.go:1.5
--- pkgsrc/pkgtools/pkglint/files/category_test.go:1.4  Tue Dec 13 00:58:07 2016
+++ pkgsrc/pkgtools/pkglint/files/category_test.go      Sun Jan  1 15:15:47 2017
@@ -11,7 +11,7 @@ func (s *Suite) Test_CheckdirCategory_to
                "# $\n"+
                "SUBDIR+=pkg1\n"+
                "SUBDIR+=\u0020aaaaa\n"+
-               "SUBDIR-=unknown #doesn’t work\n"+
+               "SUBDIR-=unknown #doesn\u2019t work\n"+
                "\n"+
                ".include \"../mk/category.mk\"\n")
 
Index: pkgsrc/pkgtools/pkglint/files/mkshparser_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkshparser_test.go:1.4 pkgsrc/pkgtools/pkglint/files/mkshparser_test.go:1.5
--- pkgsrc/pkgtools/pkglint/files/mkshparser_test.go:1.4        Sun Jul 10 21:24:47 2016
+++ pkgsrc/pkgtools/pkglint/files/mkshparser_test.go    Sun Jan  1 15:15:47 2017
@@ -263,7 +263,7 @@ func (s *ShSuite) Test_ShellParser_for_c
                        b.Words("in", "esac"),
                        b.List().AddCommand(b.SimpleCommand("echo", "$var")).AddSemicolon())))
 
-       // No semicolon necessary between the two “done”.
+       // No semicolon necessary between the two "done".
        s.test("for i in 1; do for j in 1; do echo $$i$$j; done done",
                b.List().AddCommand(b.For(
                        "i",
@@ -323,7 +323,7 @@ func (s *ShSuite) Test_ShellParser_if_cl
                        b.List().AddCommand(b.SimpleCommand("echo", "yes")).AddSemicolon(),
                        b.List().AddCommand(b.SimpleCommand("echo", "no")).AddSemicolon())))
 
-       // No semicolon necessary between the two “fi”.
+       // No semicolon necessary between the two "fi".
        s.test("if cond1; then if cond2; then action; fi fi",
                b.List().AddCommand(b.If(
                        b.List().AddCommand(b.SimpleCommand("cond1")).AddSemicolon(),

Index: pkgsrc/pkgtools/pkglint/files/files.go
diff -u pkgsrc/pkgtools/pkglint/files/files.go:1.8 pkgsrc/pkgtools/pkglint/files/files.go:1.9
--- pkgsrc/pkgtools/pkglint/files/files.go:1.8  Sat Dec 17 13:35:32 2016
+++ pkgsrc/pkgtools/pkglint/files/files.go      Sun Jan  1 15:15:47 2017
@@ -6,8 +6,8 @@ import (
        "strings"
 )
 
-func LoadNonemptyLines(fname string, joinContinuationLines bool) []*Line {
-       lines, err := readLines(fname, joinContinuationLines)
+func LoadNonemptyLines(fname string, joinBackslashLines bool) []*Line {
+       lines, err := readLines(fname, joinBackslashLines)
        if err != nil {
                NewLineWhole(fname).Errorf("Cannot be read.")
                return nil
@@ -19,8 +19,8 @@ func LoadNonemptyLines(fname string, joi
        return lines
 }
 
-func LoadExistingLines(fname string, foldBackslashLines bool) []*Line {
-       lines, err := readLines(fname, foldBackslashLines)
+func LoadExistingLines(fname string, joinBackslashLines bool) []*Line {
+       lines, err := readLines(fname, joinBackslashLines)
        if err != nil {
                NewLineWhole(fname).Fatalf("Cannot be read.")
        }
@@ -101,16 +101,16 @@ func splitRawLine(textnl string) (leadin
        return
 }
 
-func readLines(fname string, joinContinuationLines bool) ([]*Line, error) {
+func readLines(fname string, joinBackslashLines bool) ([]*Line, error) {
        rawText, err := ioutil.ReadFile(fname)
        if err != nil {
                return nil, err
        }
 
-       return convertToLogicalLines(fname, string(rawText), joinContinuationLines), nil
+       return convertToLogicalLines(fname, string(rawText), joinBackslashLines), nil
 }
 
-func convertToLogicalLines(fname string, rawText string, joinContinuationLines bool) []*Line {
+func convertToLogicalLines(fname string, rawText string, joinBackslashLines bool) []*Line {
        var rawLines []*RawLine
        for lineno, rawLine := range strings.SplitAfter(rawText, "\n") {
                if rawLine != "" {
@@ -119,7 +119,7 @@ func convertToLogicalLines(fname string,
        }
 
        var loglines []*Line
-       if joinContinuationLines {
+       if joinBackslashLines {
                for lineno := 0; lineno < len(rawLines); {
                        loglines = append(loglines, getLogicalLine(fname, rawLines, &lineno))
                }
Index: pkgsrc/pkgtools/pkglint/files/pkglint_test.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.8 pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.9
--- pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.8   Tue Dec 13 00:58:07 2016
+++ pkgsrc/pkgtools/pkglint/files/pkglint_test.go       Sun Jan  1 15:15:47 2017
@@ -4,8 +4,77 @@ import (
        "strings"
 
        check "gopkg.in/check.v1"
+       "os"
 )
 
+func (s *Suite) Test_Pkglint_Main_help(c *check.C) {
+       exitcode := new(Pkglint).Main("pkglint", "-h")
+
+       c.Check(exitcode, equals, 0)
+       c.Check(s.Output(), check.Matches, `^\Qusage: pkglint [options] dir...\E\n(?s).+`)
+}
+
+func (s *Suite) Test_Pkglint_Main_version(c *check.C) {
+       exitcode := new(Pkglint).Main("pkglint", "--version")
+
+       c.Check(exitcode, equals, 0)
+       c.Check(s.Output(), equals, confVersion+"\n")
+}
+
+func (s *Suite) Test_Pkglint_Main_no_args(c *check.C) {
+       exitcode := new(Pkglint).Main("pkglint")
+
+       c.Check(exitcode, equals, 1)
+       c.Check(s.Stderr(), equals, "FATAL: \".\" is not inside a pkgsrc tree.\n")
+}
+
+// go test -c -covermode count
+// pkgsrcdir=...
+// env PKGLINT_TESTCMDLINE="$pkgsrcdir -r" ./pkglint.test -test.coverprofile pkglint.cov -check.f TestRunPkglint
+// go tool cover -html=pkglint.cov -o coverage.html
+func (s *Suite) Test_Pkglint_coverage(c *check.C) {
+       cmdline := os.Getenv("PKGLINT_TESTCMDLINE")
+       if cmdline != "" {
+               G.logOut, G.logErr, G.debugOut = os.Stdout, os.Stderr, os.Stdout
+               new(Pkglint).Main(append([]string{"pkglint"}, splitOnSpace(cmdline)...)...)
+       }
+}
+
+func (s *Suite) Test_Pkglint_CheckDirent__outside(c *check.C) {
+       s.Init(c)
+       s.CreateTmpFile("empty", "")
+
+       new(Pkglint).CheckDirent(s.tmpdir)
+
+       c.Check(s.Output(), equals, "ERROR: ~: Cannot determine the pkgsrc root directory for \"~\".\n")
+}
+
+func (s *Suite) Test_Pkglint_CheckDirent(c *check.C) {
+       s.Init(c)
+       s.CreateTmpFile("mk/bsd.pkg.mk", "")
+       s.CreateTmpFile("category/package/Makefile", "")
+       s.CreateTmpFile("category/Makefile", "")
+       s.CreateTmpFile("Makefile", "")
+       G.globalData.Pkgsrcdir = s.tmpdir
+       pkglint := new(Pkglint)
+
+       pkglint.CheckDirent(s.tmpdir)
+
+       c.Check(s.Output(), equals, "ERROR: ~/Makefile: Must not be empty.\n")
+
+       pkglint.CheckDirent(s.tmpdir + "/category")
+
+       c.Check(s.Output(), equals, "ERROR: ~/category/Makefile: Must not be empty.\n")
+
+       pkglint.CheckDirent(s.tmpdir + "/category/package")
+
+       c.Check(s.Output(), equals, "ERROR: ~/category/package/Makefile: Must not be empty.\n")
+
+       pkglint.CheckDirent(s.tmpdir + "/category/package/nonexistent")
+
+       c.Check(s.Output(), equals, "ERROR: ~/category/package/nonexistent: No such file or directory.\n")
+}
+
 func (s *Suite) Test_resolveVariableRefs__circular_reference(c *check.C) {
        mkline := NewMkLine(NewLine("fname", 1, "GCC_VERSION=${GCC_VERSION}", nil))
        G.Pkg = NewPackage(".")
@@ -40,41 +109,6 @@ func (s *Suite) Test_resolveVariableRefs
        c.Check(resolved, equals, "gst-plugins0.10-x11/distinfo")
 }
 
-func (s *Suite) Test_MatchVarassign(c *check.C) {
-       checkVarassign := func(text string, ck check.Checker, varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment string) {
-               type va struct {
-                       varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment string
-               }
-               expected := va{varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment}
-               am, avarname, aspaceAfterVarname, aop, aalign, avalue, aspaceAfterValue, acomment := MatchVarassign(text)
-               if !am {
-                       c.Errorf("Text %q doesn’t match variable assignment", text)
-                       return
-               }
-               actual := va{avarname, aspaceAfterVarname, aop, aalign, avalue, aspaceAfterValue, acomment}
-               c.Check(actual, ck, expected)
-       }
-       checkNotVarassign := func(text string) {
-               m, _, _, _, _, _, _, _ := MatchVarassign(text)
-               if m {
-                       c.Errorf("Text %q matches variable assignment, but shouldn’t.", text)
-               }
-       }
-
-       checkVarassign("C++=c11", equals, "C+", "", "+=", "C++=", "c11", "", "")
-       checkVarassign("V=v", equals, "V", "", "=", "V=", "v", "", "")
-       checkVarassign("VAR=#comment", equals, "VAR", "", "=", "VAR=", "", "", "#comment")
-       checkVarassign("VAR=\\#comment", equals, "VAR", "", "=", "VAR=", "#comment", "", "")
-       checkVarassign("VAR=\\\\\\##comment", equals, "VAR", "", "=", "VAR=", "\\\\#", "", "#comment")
-       checkVarassign("VAR=\\", equals, "VAR", "", "=", "VAR=", "\\", "", "")
-       checkVarassign("VAR += value", equals, "VAR", " ", "+=", "VAR += ", "value", "", "")
-       checkVarassign(" VAR=value", equals, "VAR", "", "=", " VAR=", "value", "", "")
-       checkVarassign("VAR=value #comment", equals, "VAR", "", "=", "VAR=", "value", " ", "#comment")
-       checkNotVarassign("\tVAR=value")
-       checkNotVarassign("?=value")
-       checkNotVarassign("<=value")
-}
-
 func (s *Suite) Test_ChecklinesDescr(c *check.C) {
        lines := s.NewLines("DESCR",
                strings.Repeat("X", 90),

Index: pkgsrc/pkgtools/pkglint/files/globaldata.go
diff -u pkgsrc/pkgtools/pkglint/files/globaldata.go:1.17 pkgsrc/pkgtools/pkglint/files/globaldata.go:1.18
--- pkgsrc/pkgtools/pkglint/files/globaldata.go:1.17    Tue Dec 13 00:58:07 2016
+++ pkgsrc/pkgtools/pkglint/files/globaldata.go Sun Jan  1 15:15:47 2017
@@ -230,7 +230,7 @@ func (gd *GlobalData) loadTools() {
 
        // Some user-defined variables do not influence the binary
        // package at all and therefore do not have to be added to
-       // BUILD_DEFS; therefore they are marked as “already added”.
+       // BUILD_DEFS; therefore they are marked as "already added".
        systemBuildDefs["DISTDIR"] = true
        systemBuildDefs["FETCH_CMD"] = true
        systemBuildDefs["FETCH_OUTPUT_ARGS"] = true

Index: pkgsrc/pkgtools/pkglint/files/line.go
diff -u pkgsrc/pkgtools/pkglint/files/line.go:1.12 pkgsrc/pkgtools/pkglint/files/line.go:1.13
--- pkgsrc/pkgtools/pkglint/files/line.go:1.12  Sun Dec  4 15:28:36 2016
+++ pkgsrc/pkgtools/pkglint/files/line.go       Sun Jan  1 15:15:47 2017
@@ -10,7 +10,7 @@ package main
 // do not.
 //
 // Some methods allow modification of the raw lines contained in the
-// logical line, but leave the “text” field untouched. These methods are
+// logical line, but leave the Text field untouched. These methods are
 // used in the --autofix mode.
 
 import (
@@ -52,7 +52,7 @@ func NewLineMulti(fname string, firstLin
        return &Line{fname, int32(firstLine), int32(lastLine), text, rawLines, false, nil, nil, ""}
 }
 
-// NewLineEOF creates a dummy line for logging, with the “line number” EOF.
+// NewLineEOF creates a dummy line for logging, with the "line number" EOF.
 func NewLineEOF(fname string) *Line {
        return NewLineMulti(fname, -1, 0, "", nil)
 }

Index: pkgsrc/pkgtools/pkglint/files/mkline.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline.go:1.19 pkgsrc/pkgtools/pkglint/files/mkline.go:1.20
--- pkgsrc/pkgtools/pkglint/files/mkline.go:1.19        Tue Dec 13 00:58:07 2016
+++ pkgsrc/pkgtools/pkglint/files/mkline.go     Sun Jan  1 15:15:47 2017
@@ -216,7 +216,7 @@ func (mkline *MkLine) checkInclude() {
        includefile := mkline.Includefile()
        mustExist := mkline.MustExist()
        if G.opts.Debug {
-               traceStep1("includefile=%s", includefile)
+               traceStep2("includingFile=%s includefile=%s", mkline.Fname, includefile)
        }
        mkline.CheckRelativePath(includefile, mustExist)
 
@@ -1106,6 +1106,44 @@ func (mkline *MkLine) withoutMakeVariabl
        }
 }
 
+func (mkline *MkLine) resolveVarsInRelativePath(relpath string, adjustDepth bool) string {
+       tmp := relpath
+       tmp = strings.Replace(tmp, "${PKGSRCDIR}", G.CurPkgsrcdir, -1)
+       tmp = strings.Replace(tmp, "${.CURDIR}", ".", -1)
+       tmp = strings.Replace(tmp, "${.PARSEDIR}", ".", -1)
+       if contains(tmp, "${LUA_PKGSRCDIR}") {
+               tmp = strings.Replace(tmp, "${LUA_PKGSRCDIR}", G.globalData.Latest("lang", `^lua[0-9]+$`, "../../lang/$0"), -1)
+       }
+       if contains(tmp, "${PHPPKGSRCDIR}") {
+               tmp = strings.Replace(tmp, "${PHPPKGSRCDIR}", G.globalData.Latest("lang", `^php[0-9]+$`, "../../lang/$0"), -1)
+       }
+       if contains(tmp, "${SUSE_DIR_PREFIX}") {
+               suseDirPrefix := G.globalData.Latest("emulators", `^(suse[0-9]+)_base`, "$1")
+               tmp = strings.Replace(tmp, "${SUSE_DIR_PREFIX}", suseDirPrefix, -1)
+       }
+       if contains(tmp, "${PYPKGSRCDIR}") {
+               tmp = strings.Replace(tmp, "${PYPKGSRCDIR}", G.globalData.Latest("lang", `^python[0-9]+$`, "../../lang/$0"), -1)
+       }
+       if contains(tmp, "${PYPACKAGE}") {
+               tmp = strings.Replace(tmp, "${PYPACKAGE}", G.globalData.Latest("lang", `^python[0-9]+$`, "$0"), -1)
+       }
+       if G.Pkg != nil {
+               tmp = strings.Replace(tmp, "${FILESDIR}", G.Pkg.Filesdir, -1)
+               tmp = strings.Replace(tmp, "${PKGDIR}", G.Pkg.Pkgdir, -1)
+       }
+
+       if adjustDepth {
+               if m, pkgpath := match1(tmp, `^\.\./\.\./([^.].*)$`); m {
+                       tmp = G.CurPkgsrcdir + "/" + pkgpath
+               }
+       }
+
+       if G.opts.Debug {
+               traceStep2("resolveVarsInRelativePath: %q => %q", relpath, tmp)
+       }
+       return tmp
+}
+
 func (mkline *MkLine) checkText(text string) {
        if G.opts.Debug {
                defer tracecall1(text)()
@@ -1262,8 +1300,12 @@ func (mkline *MkLine) explainRelativeDir
 }
 
 func (mkline *MkLine) CheckRelativePkgdir(pkgdir string) {
+       if G.opts.Debug {
+               defer tracecall1(pkgdir)()
+       }
+
        mkline.CheckRelativePath(pkgdir, true)
-       pkgdir = resolveVarsInRelativePath(pkgdir, false)
+       pkgdir = mkline.resolveVarsInRelativePath(pkgdir, false)
 
        if m, otherpkgpath := match1(pkgdir, `^(?:\./)?\.\./\.\./([^/]+/[^/]+)$`); m {
                if !fileExists(G.globalData.Pkgsrcdir + "/" + otherpkgpath + "/Makefile") {
@@ -1280,11 +1322,15 @@ func (mkline *MkLine) CheckRelativePkgdi
 }
 
 func (mkline *MkLine) CheckRelativePath(path string, mustExist bool) {
+       if G.opts.Debug {
+               defer tracecall(path, mustExist)()
+       }
+
        if !G.Wip && contains(path, "/wip/") {
                mkline.Line.Errorf("A main pkgsrc package must not depend on a pkgsrc-wip package.")
        }
 
-       resolvedPath := resolveVarsInRelativePath(path, true)
+       resolvedPath := mkline.resolveVarsInRelativePath(path, true)
        if containsVarRef(resolvedPath) {
                return
        }
@@ -1621,7 +1667,7 @@ func (mkline *MkLine) determineUsedVaria
 // VarUseContext defines the context in which a variable is defined
 // or used. Whether that is allowed depends on:
 //
-// * The variable’s data type, as defined in vardefs.go.
+// * The variable's data type, as defined in vardefs.go.
 // * When used on the right-hand side of an assigment, the variable can
 //   represent a list of words, a single word or even only part of a
 //   word. This distinction decides upon the correct use of the :Q
@@ -1629,7 +1675,7 @@ func (mkline *MkLine) determineUsedVaria
 // * When used in preprocessing statements like .if or .for, the other
 //   operands of that statement should fit to the variable and are
 //   checked against the variable type. For example, comparing OPSYS to
-//   x86_64 doesn’t make sense.
+//   x86_64 doesn't make sense.
 type VarUseContext struct {
        vartype    *Vartype
        time       vucTime
@@ -1649,7 +1695,7 @@ const (
        vucTimeParse
 
        // All files have been read, all variables can be referenced.
-       // Variable values don’t change anymore.
+       // Variable values don't change anymore.
        vucTimeRun
 )
 
@@ -1756,6 +1802,92 @@ func (ind *Indentation) Varnames() strin
        return varnames
 }
 
+func MatchVarassign(text string) (m bool, varname, spaceAfterVarname, op, valueAlign, value, spaceAfterValue, comment string) {
+       i, n := 0, len(text)
+
+       for i < n && text[i] == ' ' {
+               i++
+       }
+
+       varnameStart := i
+       for ; i < n; i++ {
+               b := text[i]
+               switch {
+               case 'A' <= b && b <= 'Z',
+                       'a' <= b && b <= 'z',
+                       b == '_',
+                       '0' <= b && b <= '9',
+                       '$' <= b && b <= '.' && (b == '$' || b == '*' || b == '+' || b == '-' || b == '.'),
+                       b == '[',
+                       b == '{', b == '}':
+                       continue
+               }
+               break
+       }
+       varnameEnd := i
+
+       if varnameEnd == varnameStart {
+               return
+       }
+
+       for i < n && (text[i] == ' ' || text[i] == '\t') {
+               i++
+       }
+
+       opStart := i
+       if i < n {
+               if b := text[i]; b == '!' || b == '+' || b == ':' || b == '?' {
+                       i++
+               }
+       }
+       if i < n && text[i] == '=' {
+               i++
+       } else {
+               return
+       }
+       opEnd := i
+
+       if text[varnameEnd-1] == '+' && varnameEnd == opStart && text[opStart] == '=' {
+               varnameEnd--
+               opStart--
+       }
+
+       for i < n && (text[i] == ' ' || text[i] == '\t') {
+               i++
+       }
+
+       valueStart := i
+       valuebuf := make([]byte, n-valueStart)
+       j := 0
+       for ; i < n; i++ {
+               b := text[i]
+               if b == '#' && (i == valueStart || text[i-1] != '\\') {
+                       break
+               } else if b != '\\' || i+1 >= n || text[i+1] != '#' {
+                       valuebuf[j] = b
+                       j++
+               }
+       }
+
+       commentStart := i
+       for text[i-1] == ' ' || text[i-1] == '\t' {
+               i--
+       }
+       valueEnd := i
+
+       commentEnd := n
+
+       m = true
+       varname = text[varnameStart:varnameEnd]
+       spaceAfterVarname = text[varnameEnd:opStart]
+       op = text[opStart:opEnd]
+       valueAlign = text[0:valueStart]
+       value = strings.TrimSpace(string(valuebuf[:j]))
+       spaceAfterValue = text[valueEnd:commentStart]
+       comment = text[commentStart:commentEnd]
+       return
+}
+
 func MatchMkInclude(text string) (m bool, indentation, directive, filename string) {
        return match3(text, `^\.(\s*)(s?include)\s+\"([^\"]+)\"\s*(?:#.*)?$`)
 }

Index: pkgsrc/pkgtools/pkglint/files/mkline_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.20 pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.21
--- pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.20   Tue Dec 13 00:58:07 2016
+++ pkgsrc/pkgtools/pkglint/files/mkline_test.go        Sun Jan  1 15:15:47 2017
@@ -414,7 +414,7 @@ func (s *Suite) Test_MkLine_CheckVaruseP
 
        mklines.Check()
 
-       c.Check(s.Output(), equals, "") // Don’t warn that “.CURDIR should not be evaluated at load time.”
+       c.Check(s.Output(), equals, "") // Don't warn that ".CURDIR should not be evaluated at load time."
 }
 
 func (s *Suite) Test_MkLine_WarnVaruseLocalbase(c *check.C) {
@@ -606,7 +606,7 @@ func (s *Suite) Test_MkLine_variableNeed
 
        G.Mk.mklines[1].Check()
 
-       c.Check(s.Output(), equals, "") // Don’t suggest to use ${HOMEPAGE:Q}.
+       c.Check(s.Output(), equals, "") // Don't suggest to use ${HOMEPAGE:Q}.
 }
 
 // Pkglint currently does not parse $$(subshell) commands very well. As
@@ -628,7 +628,7 @@ func (s *Suite) Test_MkLine_variableNeed
        G.Mk.mklines[1].Check()
        G.Mk.mklines[2].Check()
 
-       c.Check(s.Output(), equals, "WARN: xpi.mk:2: Invoking subshells via $(...) is not portable enough.\n") // Don’t suggest to use ${AWK:Q}.
+       c.Check(s.Output(), equals, "WARN: xpi.mk:2: Invoking subshells via $(...) is not portable enough.\n") // Don't suggest to use ${AWK:Q}.
 }
 
 // LDFLAGS (and even more so CPPFLAGS and CFLAGS) may contain special
@@ -682,7 +682,7 @@ func (s *Suite) Test_MkLine_variableNeed
 
        G.Mk.mklines[1].Check()
 
-       c.Check(s.Output(), equals, "") // Don’t suggest ${ECHO:Q} here.
+       c.Check(s.Output(), equals, "") // Don't suggest ${ECHO:Q} here.
 }
 
 func (s *Suite) Test_MkLine_variableNeedsQuoting__LDADD_in_BUILDLINK_TRANSFORM(c *check.C) {
@@ -707,7 +707,7 @@ func (s *Suite) Test_MkLine_variableNeed
 
        G.Mk.mklines[0].Check()
 
-       c.Check(s.Output(), equals, "") // Don’t suggest ${REPLACE_PERL:Q}.
+       c.Check(s.Output(), equals, "") // Don't suggest ${REPLACE_PERL:Q}.
 }
 
 func (s *Suite) Test_MkLine_variableNeedsQuoting_guessed_list_variable_in_quotes(c *check.C) {
@@ -734,7 +734,7 @@ func (s *Suite) Test_MkLine_variableNeed
 
        G.Mk.Check()
 
-       c.Check(s.Output(), equals, "") // Don’t warn about missing :Q operators.
+       c.Check(s.Output(), equals, "") // Don't warn about missing :Q modifiers.
 }
 
 func (s *Suite) Test_MkLine_variableNeedsQuoting_PKGNAME_and_URL_list_in_URL_list(c *check.C) {
@@ -748,7 +748,7 @@ func (s *Suite) Test_MkLine_variableNeed
 
        G.Mk.mklines[1].checkVarassignVaruse()
 
-       c.Check(s.Output(), equals, "") // Don’t warn about missing :Q operators.
+       c.Check(s.Output(), equals, "") // Don't warn about missing :Q modifiers.
 }
 
 func (s *Suite) Test_MkLine_variableNeedsQuoting_tool_in_CONFIGURE_ENV(c *check.C) {
@@ -779,7 +779,7 @@ func (s *Suite) Test_MkLine_Varuse_Modif
 
        G.Mk.mklines[0].Check()
 
-       c.Check(s.Output(), equals, "") // Don’t warn that ${XKBBASE}/xkbcomp is used but not defined.
+       c.Check(s.Output(), equals, "") // Don't warn that ${XKBBASE}/xkbcomp is used but not defined.
 }
 
 func (s *Suite) Test_MkLine_CheckCond_comparison_with_shell_command(c *check.C) {
@@ -793,7 +793,7 @@ func (s *Suite) Test_MkLine_CheckCond_co
 
        G.Mk.Check()
 
-       // Don’t warn about unknown shell command "cc".
+       // Don't warn about unknown shell command "cc".
        c.Check(s.Output(), equals, "WARN: security/openssl/Makefile:2: Use ${PKGSRC_COMPILER:Mgcc} instead of the == operator.\n")
 }
 
@@ -924,6 +924,41 @@ func (s *Suite) Test_MkLine__comment_in_
        c.Check(s.Output(), equals, "WARN: Makefile:2: The # character starts a comment.\n")
 }
 
+func (s *Suite) Test_MatchVarassign(c *check.C) {
+       checkVarassign := func(text string, ck check.Checker, varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment string) {
+               type va struct {
+                       varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment string
+               }
+               expected := va{varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment}
+               am, avarname, aspaceAfterVarname, aop, aalign, avalue, aspaceAfterValue, acomment := MatchVarassign(text)
+               if !am {
+                       c.Errorf("Text %q doesn't match variable assignment", text)
+                       return
+               }
+               actual := va{avarname, aspaceAfterVarname, aop, aalign, avalue, aspaceAfterValue, acomment}
+               c.Check(actual, ck, expected)
+       }
+       checkNotVarassign := func(text string) {
+               m, _, _, _, _, _, _, _ := MatchVarassign(text)
+               if m {
+                       c.Errorf("Text %q matches variable assignment, but shouldn't.", text)
+               }
+       }
+
+       checkVarassign("C++=c11", equals, "C+", "", "+=", "C++=", "c11", "", "")
+       checkVarassign("V=v", equals, "V", "", "=", "V=", "v", "", "")
+       checkVarassign("VAR=#comment", equals, "VAR", "", "=", "VAR=", "", "", "#comment")
+       checkVarassign("VAR=\\#comment", equals, "VAR", "", "=", "VAR=", "#comment", "", "")
+       checkVarassign("VAR=\\\\\\##comment", equals, "VAR", "", "=", "VAR=", "\\\\#", "", "#comment")
+       checkVarassign("VAR=\\", equals, "VAR", "", "=", "VAR=", "\\", "", "")
+       checkVarassign("VAR += value", equals, "VAR", " ", "+=", "VAR += ", "value", "", "")
+       checkVarassign(" VAR=value", equals, "VAR", "", "=", " VAR=", "value", "", "")
+       checkVarassign("VAR=value #comment", equals, "VAR", "", "=", "VAR=", "value", " ", "#comment")
+       checkNotVarassign("\tVAR=value")
+       checkNotVarassign("?=value")
+       checkNotVarassign("<=value")
+}
+
 func (s *Suite) Test_Indentation(c *check.C) {
        ind := &Indentation{}
 

Index: pkgsrc/pkgtools/pkglint/files/mklines.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines.go:1.10 pkgsrc/pkgtools/pkglint/files/mklines.go:1.11
--- pkgsrc/pkgtools/pkglint/files/mklines.go:1.10       Tue Nov  1 21:40:25 2016
+++ pkgsrc/pkgtools/pkglint/files/mklines.go    Sun Jan  1 15:15:47 2017
@@ -310,7 +310,7 @@ func (va *VaralignBlock) fixalign(mkline
                return
        }
 
-       // Don’t warn about procedure parameters
+       // Don't warn about procedure parameters
        if mkline.Op() == opAssignEval && matches(mkline.Varname(), `^[a-z]`) {
                return
        }
Index: pkgsrc/pkgtools/pkglint/files/plist.go
diff -u pkgsrc/pkgtools/pkglint/files/plist.go:1.10 pkgsrc/pkgtools/pkglint/files/plist.go:1.11
--- pkgsrc/pkgtools/pkglint/files/plist.go:1.10 Sun Dec  4 15:28:36 2016
+++ pkgsrc/pkgtools/pkglint/files/plist.go      Sun Jan  1 15:15:47 2017
@@ -512,7 +512,7 @@ func (s *plistLineSorter) Sort() {
        firstLine := s.first.line
        firstLine.RememberAutofix("Sorting the whole file.")
        firstLine.logAutofix()
-       firstLine.changed = true // Otherwise the changes won’t be saved
+       firstLine.changed = true // Otherwise the changes won't be saved
        lines := []*Line{firstLine}
        lines = append(lines, s.after[s.first]...)
        for _, pline := range s.plines {
Index: pkgsrc/pkgtools/pkglint/files/vartype.go
diff -u pkgsrc/pkgtools/pkglint/files/vartype.go:1.10 pkgsrc/pkgtools/pkglint/files/vartype.go:1.11
--- pkgsrc/pkgtools/pkglint/files/vartype.go:1.10       Sun Jul 10 21:24:47 2016
+++ pkgsrc/pkgtools/pkglint/files/vartype.go    Sun Jan  1 15:15:47 2017
@@ -101,7 +101,7 @@ func (vt *Vartype) AllowedFiles(perms Ac
 }
 
 // Returns whether the type is considered a shell list.
-// This distinction between “real lists” and “considered a list” makes
+// This distinction between "real lists" and "considered a list" makes
 // the implementation of checklineMkVartype easier.
 func (vt *Vartype) IsConsideredList() bool {
        switch vt.kindOfList {
@@ -147,8 +147,8 @@ func (vt *Vartype) IsShell() bool {
        return false
 }
 
-// The basic vartype consists only of characters that don’t
-// need escaping in most contexts, like A-Za-z0-9-_.
+// IsBasicSafe returns whether the basic vartype consists only of
+// characters that don't need escaping in most contexts, like A-Za-z0-9-_.
 func (vt *Vartype) IsBasicSafe() bool {
        switch vt.basicType {
        case BtBuildlinkDepmethod,

Index: pkgsrc/pkgtools/pkglint/files/package.go
diff -u pkgsrc/pkgtools/pkglint/files/package.go:1.14 pkgsrc/pkgtools/pkglint/files/package.go:1.15
--- pkgsrc/pkgtools/pkglint/files/package.go:1.14       Sun Dec  4 15:28:36 2016
+++ pkgsrc/pkgtools/pkglint/files/package.go    Sun Jan  1 15:15:47 2017
@@ -2,12 +2,15 @@ package main
 
 import (
        "fmt"
+       "netbsd.org/pkglint/pkgver"
        "path"
        "regexp"
        "strconv"
        "strings"
 )
 
+const rePkgname = `^([\w\-.+]+)-(\d(?:\w|\.\d)*)$`
+
 // Package contains data for the pkgsrc package that is currently checked.
 type Package struct {
        Pkgpath              string  // e.g. "category/pkgdir"
@@ -104,7 +107,7 @@ func (pkg *Package) checkPossibleDowngra
 
        if change.Action == "Updated" {
                changeVersion := regcomp(`nb\d+$`).ReplaceAllString(change.Version, "")
-               if pkgverCmp(pkgversion, changeVersion) < 0 {
+               if pkgver.Compare(pkgversion, changeVersion) < 0 {
                        mkline.Line.Warnf("The package is being downgraded from %s (see %s) to %s", change.Version, change.Line.ReferenceFrom(mkline.Line), pkgversion)
                        Explain(
                                "The files in doc/CHANGES-*, in which all version changes are",
@@ -233,10 +236,10 @@ func (pkg *Package) loadPackageMakefile(
 
        allLines.DetermineUsedVariables()
 
-       pkg.Pkgdir = expandVariableWithDefault("PKGDIR", ".")
-       pkg.DistinfoFile = expandVariableWithDefault("DISTINFO_FILE", "distinfo")
-       pkg.Filesdir = expandVariableWithDefault("FILESDIR", "files")
-       pkg.Patchdir = expandVariableWithDefault("PATCHDIR", "patches")
+       pkg.Pkgdir = pkg.expandVariableWithDefault("PKGDIR", ".")
+       pkg.DistinfoFile = pkg.expandVariableWithDefault("DISTINFO_FILE", "distinfo")
+       pkg.Filesdir = pkg.expandVariableWithDefault("FILESDIR", "files")
+       pkg.Patchdir = pkg.expandVariableWithDefault("PATCHDIR", "patches")
 
        if varIsDefined("PHPEXT_MK") {
                if !varIsDefined("USE_PHP_EXT_PATCHES") {
@@ -283,7 +286,7 @@ func (pkg *Package) readMakefile(fname s
                var includeFile, incDir, incBase string
                if mkline.IsInclude() {
                        inc := mkline.Includefile()
-                       includeFile = resolveVariableRefs(resolveVarsInRelativePath(inc, true))
+                       includeFile = resolveVariableRefs(mkline.resolveVarsInRelativePath(inc, true))
                        if containsVarRef(includeFile) {
                                if !contains(fname, "/mk/") {
                                        line.Notef("Skipping include file %q. This may result in false warnings.", includeFile)
@@ -326,7 +329,7 @@ func (pkg *Package) readMakefile(fname s
 
                                // Only look in the directory relative to the
                                // current file and in the current working directory.
-                               // Pkglint doesn’t have an include dir list, like make(1) does.
+                               // Pkglint doesn't have an include dir list, like make(1) does.
                                if !fileExists(dirname + "/" + includeFile) {
                                        if dirname != G.CurrentDir { // Prevent unnecessary syscalls
                                                dirname = G.CurrentDir
@@ -530,6 +533,23 @@ func (pkg *Package) pkgnameFromDistname(
        return result
 }
 
+func (pkg *Package) expandVariableWithDefault(varname, defaultValue string) string {
+       mkline := G.Pkg.vardef[varname]
+       if mkline == nil {
+               return defaultValue
+       }
+
+       value := mkline.Value()
+       value = mkline.resolveVarsInRelativePath(value, true)
+       if containsVarRef(value) {
+               value = resolveVariableRefs(value)
+       }
+       if G.opts.Debug {
+               traceStep2("Expanded %q to %q", varname, value)
+       }
+       return value
+}
+
 func (pkg *Package) checkUpdate() {
        if pkg.EffectivePkgbase != "" {
                for _, sugg := range G.globalData.GetSuggestedPackageUpdates() {
@@ -543,7 +563,7 @@ func (pkg *Package) checkUpdate() {
                        }
 
                        pkgnameLine := pkg.EffectivePkgnameLine
-                       cmp := pkgverCmp(pkg.EffectivePkgversion, suggver)
+                       cmp := pkgver.Compare(pkg.EffectivePkgversion, suggver)
                        switch {
                        case cmp < 0:
                                pkgnameLine.Warnf("This package should be updated to %s%s.", sugg.Version, comment)

Index: pkgsrc/pkgtools/pkglint/files/parser.go
diff -u pkgsrc/pkgtools/pkglint/files/parser.go:1.5 pkgsrc/pkgtools/pkglint/files/parser.go:1.6
--- pkgsrc/pkgtools/pkglint/files/parser.go:1.5 Sun Jun  5 11:24:32 2016
+++ pkgsrc/pkgtools/pkglint/files/parser.go     Sun Jan  1 15:15:47 2017
@@ -48,6 +48,15 @@ func (p *Parser) PkgbasePattern() (pkgba
        }
 }
 
+type DependencyPattern struct {
+       pkgbase  string // "freeciv-client", "{gcc48,gcc48-libs}", "${EMACS_REQD}"
+       lowerOp  string // ">=", ">"
+       lower    string // "2.5.0", "${PYVER}"
+       upperOp  string // "<", "<="
+       upper    string // "3.0", "${PYVER}"
+       wildcard string // "[0-9]*", "1.5.*", "${PYVER}"
+}
+
 func (p *Parser) Dependency() *DependencyPattern {
        repl := p.repl
 
Index: pkgsrc/pkgtools/pkglint/files/toplevel_test.go
diff -u pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.5 pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.6
--- pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.5  Tue Dec 13 00:58:07 2016
+++ pkgsrc/pkgtools/pkglint/files/toplevel_test.go      Sun Jan  1 15:15:47 2017
@@ -14,7 +14,7 @@ func (s *Suite) Test_CheckdirToplevel(c 
                "SUBDIR+=\tccc\n"+
                "SUBDIR+=\tccc\n"+
                "#SUBDIR+=\tignoreme\n"+
-               "SUBDIR+=\tnonexisting\n"+ // This just doesn’t happen in practice.
+               "SUBDIR+=\tnonexisting\n"+ // This doesn't happen in practice, therefore no warning.
                "SUBDIR+=\tbbb\n")
        s.CreateTmpFile("archivers/Makefile", "")
        s.CreateTmpFile("bbb/Makefile", "")

Index: pkgsrc/pkgtools/pkglint/files/patches.go
diff -u pkgsrc/pkgtools/pkglint/files/patches.go:1.11 pkgsrc/pkgtools/pkglint/files/patches.go:1.12
--- pkgsrc/pkgtools/pkglint/files/patches.go:1.11       Sun Dec  4 15:28:36 2016
+++ pkgsrc/pkgtools/pkglint/files/patches.go    Sun Jan  1 15:15:47 2017
@@ -288,7 +288,7 @@ func guessFileType(line *Line, fname str
        }
 
        basename := path.Base(fname)
-       basename = strings.TrimSuffix(basename, ".in") // doesn’t influence the content type
+       basename = strings.TrimSuffix(basename, ".in") // doesn't influence the content type
        ext := strings.ToLower(strings.TrimLeft(path.Ext(basename), "."))
 
        switch {
Index: pkgsrc/pkgtools/pkglint/files/util.go
diff -u pkgsrc/pkgtools/pkglint/files/util.go:1.11 pkgsrc/pkgtools/pkglint/files/util.go:1.12
--- pkgsrc/pkgtools/pkglint/files/util.go:1.11  Sun Dec  4 15:28:36 2016
+++ pkgsrc/pkgtools/pkglint/files/util.go       Sun Jan  1 15:15:47 2017
@@ -28,6 +28,13 @@ func ifelseStr(cond bool, a, b string) s
        return b
 }
 
+func imax(a, b int) int {
+       if a > b {
+               return a
+       }
+       return b
+}
+
 func mustMatch(s string, re RegexPattern) []string {
        if m := match(s, re); m != nil {
                return m
@@ -420,7 +427,7 @@ func (r Ref) String() string {
        return fmt.Sprintf("%v", ref)
 }
 
-// Emulates make(1)’s :S substitution operator.
+// Emulates make(1)'s :S substitution operator.
 func mkopSubst(s string, left bool, from string, right bool, to string, flags string) string {
        if G.opts.Debug {
                defer tracecall(s, left, from, right, to, flags)()

Index: pkgsrc/pkgtools/pkglint/files/pkglint.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint.go:1.16 pkgsrc/pkgtools/pkglint/files/pkglint.go:1.17
--- pkgsrc/pkgtools/pkglint/files/pkglint.go:1.16       Tue Dec 13 00:58:07 2016
+++ pkgsrc/pkgtools/pkglint/files/pkglint.go    Sun Jan  1 15:15:47 2017
@@ -1,14 +1,224 @@
 package main
 
 import (
+       "fmt"
+       "io"
+       "netbsd.org/pkglint/getopt"
        "os"
+       "os/user"
        "path"
+       "path/filepath"
+       "runtime/pprof"
        "strings"
 )
 
-const (
-       rePkgname = `^([\w\-.+]+)-(\d(?:\w|\.\d)*)$`
-)
+const confMake = "@BMAKE@"
+const confVersion = "@VERSION@"
+
+func main() {
+       G.logOut, G.logErr, G.debugOut = os.Stdout, os.Stderr, os.Stdout
+       os.Exit(new(Pkglint).Main(os.Args...))
+}
+
+type Pkglint struct{}
+
+func (pkglint *Pkglint) Main(args ...string) (exitcode int) {
+       defer func() {
+               if r := recover(); r != nil {
+                       if _, ok := r.(pkglintFatal); ok {
+                               exitcode = 1
+                       } else {
+                               panic(r)
+                       }
+               }
+       }()
+
+       if exitcode := pkglint.ParseCommandLine(args); exitcode != nil {
+               return *exitcode
+       }
+
+       if G.opts.Profiling {
+               f, err := os.Create("pkglint.pprof")
+               if err != nil {
+                       dummyLine.Fatalf("Cannot create profiling file: %s", err)
+               }
+               pprof.StartCPUProfile(f)
+               defer pprof.StopCPUProfile()
+
+               G.rematch = NewHistogram()
+               G.renomatch = NewHistogram()
+               G.retime = NewHistogram()
+               G.loghisto = NewHistogram()
+       }
+
+       for _, arg := range G.opts.args {
+               G.Todo = append(G.Todo, filepath.ToSlash(arg))
+       }
+       if len(G.Todo) == 0 {
+               G.Todo = []string{"."}
+       }
+
+       G.globalData.Initialize()
+
+       currentUser, err := user.Current()
+       if err == nil {
+               // On Windows, this is `Computername\Username`.
+               G.CurrentUsername = regcomp(`^.*\\`).ReplaceAllString(currentUser.Username, "")
+       }
+
+       for len(G.Todo) != 0 {
+               item := G.Todo[0]
+               G.Todo = G.Todo[1:]
+               pkglint.CheckDirent(item)
+       }
+
+       checkToplevelUnusedLicenses()
+       pkglint.PrintSummary()
+       if G.opts.Profiling {
+               G.loghisto.PrintStats("loghisto", G.logOut, 0)
+               G.rematch.PrintStats("rematch", G.logOut, 10)
+               G.renomatch.PrintStats("renomatch", G.logOut, 10)
+               G.retime.PrintStats("retime", G.logOut, 10)
+       }
+       if G.errors != 0 {
+               return 1
+       }
+       return 0
+}
+
+func (pkglint *Pkglint) ParseCommandLine(args []string) *int {
+       gopts := &G.opts
+       opts := getopt.NewOptions()
+
+       check := opts.AddFlagGroup('C', "check", "check,...", "enable or disable specific checks")
+       opts.AddFlagVar('d', "debug", &gopts.Debug, false, "log verbose call traces for debugging")
+       opts.AddFlagVar('e', "explain", &gopts.Explain, false, "explain the diagnostics or give further help")
+       opts.AddFlagVar('f', "show-autofix", &gopts.PrintAutofix, false, "show what pkglint can fix automatically")
+       opts.AddFlagVar('F', "autofix", &gopts.Autofix, false, "try to automatically fix some errors (experimental)")
+       opts.AddFlagVar('g', "gcc-output-format", &gopts.GccOutput, false, "mimic the gcc output format")
+       opts.AddFlagVar('h', "help", &gopts.PrintHelp, false, "print a detailed usage message")
+       opts.AddFlagVar('I', "dumpmakefile", &gopts.DumpMakefile, false, "dump the Makefile after parsing")
+       opts.AddFlagVar('i', "import", &gopts.Import, false, "prepare the import of a wip package")
+       opts.AddFlagVar('m', "log-verbose", &gopts.LogVerbose, false, "allow the same log message more than once")
+       opts.AddFlagVar('p', "profiling", &gopts.Profiling, false, "profile the executing program")
+       opts.AddFlagVar('q', "quiet", &gopts.Quiet, false, "don't print a summary line when finishing")
+       opts.AddFlagVar('r', "recursive", &gopts.Recursive, false, "check subdirectories, too")
+       opts.AddFlagVar('s', "source", &gopts.PrintSource, false, "show the source lines together with diagnostics")
+       opts.AddFlagVar('V', "version", &gopts.PrintVersion, false, "print the version number of pkglint")
+       warn := opts.AddFlagGroup('W', "warning", "warning,...", "enable or disable groups of warnings")
+
+       check.AddFlagVar("ALTERNATIVES", &gopts.CheckAlternatives, true, "check ALTERNATIVES files")
+       check.AddFlagVar("bl3", &gopts.CheckBuildlink3, true, "check buildlink3.mk files")
+       check.AddFlagVar("DESCR", &gopts.CheckDescr, true, "check DESCR file")
+       check.AddFlagVar("distinfo", &gopts.CheckDistinfo, true, "check distinfo file")
+       check.AddFlagVar("extra", &gopts.CheckExtra, false, "check various additional files")
+       check.AddFlagVar("global", &gopts.CheckGlobal, false, "inter-package checks")
+       check.AddFlagVar("INSTALL", &gopts.CheckInstall, true, "check INSTALL and DEINSTALL scripts")
+       check.AddFlagVar("Makefile", &gopts.CheckMakefile, true, "check Makefiles")
+       check.AddFlagVar("MESSAGE", &gopts.CheckMessage, true, "check MESSAGE file")
+       check.AddFlagVar("mk", &gopts.CheckMk, true, "check other .mk files")
+       check.AddFlagVar("patches", &gopts.CheckPatches, true, "check patches")
+       check.AddFlagVar("PLIST", &gopts.CheckPlist, true, "check PLIST files")
+
+       warn.AddFlagVar("absname", &gopts.WarnAbsname, true, "warn about use of absolute file names")
+       warn.AddFlagVar("directcmd", &gopts.WarnDirectcmd, true, "warn about use of direct command names instead of Make variables")
+       warn.AddFlagVar("extra", &gopts.WarnExtra, false, "enable some extra warnings")
+       warn.AddFlagVar("order", &gopts.WarnOrder, false, "warn if Makefile entries are unordered")
+       warn.AddFlagVar("perm", &gopts.WarnPerm, false, "warn about unforeseen variable definition and use")
+       warn.AddFlagVar("plist-depr", &gopts.WarnPlistDepr, false, "warn about deprecated paths in PLISTs")
+       warn.AddFlagVar("plist-sort", &gopts.WarnPlistSort, false, "warn about unsorted entries in PLISTs")
+       warn.AddFlagVar("quoting", &gopts.WarnQuoting, false, "warn about quoting issues")
+       warn.AddFlagVar("space", &gopts.WarnSpace, false, "warn about inconsistent use of white-space")
+       warn.AddFlagVar("style", &gopts.WarnStyle, false, "warn about stylistic issues")
+       warn.AddFlagVar("types", &gopts.WarnTypes, true, "do some simple type checking in Makefiles")
+
+       remainingArgs, err := opts.Parse(args)
+       if err != nil {
+               fmt.Fprintf(G.logErr, "%s\n\n", err)
+               opts.Help(G.logErr, "pkglint [options] dir...")
+               exitcode := 1
+               return &exitcode
+       }
+       gopts.args = remainingArgs
+
+       if gopts.PrintHelp {
+               opts.Help(G.logOut, "pkglint [options] dir...")
+               exitcode := 0
+               return &exitcode
+       }
+
+       if G.opts.PrintVersion {
+               fmt.Fprintf(G.logOut, "%s\n", confVersion)
+               exitcode := 0
+               return &exitcode
+       }
+
+       return nil
+}
+
+func (pkglint *Pkglint) PrintSummary() {
+       if !G.opts.Quiet {
+               if G.errors != 0 || G.warnings != 0 {
+                       fmt.Fprintf(G.logOut, "%d %s and %d %s found.\n",
+                               G.errors, ifelseStr(G.errors == 1, "error", "errors"),
+                               G.warnings, ifelseStr(G.warnings == 1, "warning", "warnings"))
+               } else {
+                       io.WriteString(G.logOut, "looks fine.\n")
+               }
+               if G.explanationsAvailable && !G.opts.Explain {
+                       fmt.Fprint(G.logOut, "(Run \"pkglint -e\" to show explanations.)\n")
+               }
+               if G.autofixAvailable && !G.opts.PrintAutofix && !G.opts.Autofix {
+                       fmt.Fprint(G.logOut, "(Run \"pkglint -fs\" to show what can be fixed automatically.)\n")
+               }
+               if G.autofixAvailable && !G.opts.Autofix {
+                       fmt.Fprint(G.logOut, "(Run \"pkglint -F\" to automatically fix some issues.)\n")
+               }
+       }
+}
+
+func (pkglint *Pkglint) CheckDirent(fname string) {
+       if G.opts.Debug {
+               defer tracecall1(fname)()
+       }
+
+       st, err := os.Lstat(fname)
+       if err != nil || !st.Mode().IsDir() && !st.Mode().IsRegular() {
+               NewLineWhole(fname).Errorf("No such file or directory.")
+               return
+       }
+       isDir := st.Mode().IsDir()
+       isReg := st.Mode().IsRegular()
+
+       G.CurrentDir = ifelseStr(isReg, path.Dir(fname), fname)
+       absCurrentDir := abspath(G.CurrentDir)
+       G.Wip = !G.opts.Import && matches(absCurrentDir, `/wip/|/wip$`)
+       G.Infrastructure = matches(absCurrentDir, `/mk/|/mk$`)
+       G.CurPkgsrcdir = findPkgsrcTopdir(G.CurrentDir)
+       if G.CurPkgsrcdir == "" {
+               NewLineWhole(fname).Errorf("Cannot determine the pkgsrc root directory for %q.", G.CurrentDir)
+               return
+       }
+
+       switch {
+       case isDir && isEmptyDir(fname):
+               return
+       case isReg:
+               Checkfile(fname)
+               return
+       }
+
+       switch G.CurPkgsrcdir {
+       case "../..":
+               checkdirPackage(relpath(G.globalData.Pkgsrcdir, G.CurrentDir))
+       case "..":
+               CheckdirCategory()
+       case ".":
+               CheckdirToplevel()
+       default:
+               NewLineWhole(fname).Errorf("Cannot check directories outside a pkgsrc tree.")
+       }
+}
 
 // Returns the pkgsrc top-level directory, relative to the given file or directory.
 func findPkgsrcTopdir(fname string) string {
@@ -53,23 +263,6 @@ func resolveVariableRefs(text string) st
        }
 }
 
-func expandVariableWithDefault(varname, defaultValue string) string {
-       mkline := G.Pkg.vardef[varname]
-       if mkline == nil {
-               return defaultValue
-       }
-
-       value := mkline.Value()
-       value = resolveVarsInRelativePath(value, true)
-       if containsVarRef(value) {
-               value = resolveVariableRefs(value)
-       }
-       if G.opts.Debug {
-               traceStep2("Expanded %q to %q", varname, value)
-       }
-       return value
-}
-
 func CheckfileExtra(fname string) {
        if G.opts.Debug {
                defer tracecall1(fname)()
@@ -277,7 +470,7 @@ func Checkfile(fname string) {
                // Ok
 
        case hasPrefix(basename, "CHANGES-"):
-               // This only checks the file, but doesn’t register the changes globally.
+               // This only checks the file, but doesn't register the changes globally.
                G.globalData.loadDocChangesFromFile(fname)
 
        case matches(fname, `(?:^|/)files/[^/]*$`):
@@ -304,136 +497,3 @@ func ChecklinesTrailingEmptyLines(lines 
                lines[last].Notef("Trailing empty lines.")
        }
 }
-
-func MatchVarassign(text string) (m bool, varname, spaceAfterVarname, op, valueAlign, value, spaceAfterValue, comment string) {
-       i, n := 0, len(text)
-
-       for i < n && text[i] == ' ' {
-               i++
-       }
-
-       varnameStart := i
-       for ; i < n; i++ {
-               b := text[i]
-               switch {
-               case 'A' <= b && b <= 'Z',
-                       'a' <= b && b <= 'z',
-                       b == '_',
-                       '0' <= b && b <= '9',
-                       '$' <= b && b <= '.' && (b == '$' || b == '*' || b == '+' || b == '-' || b == '.'),
-                       b == '[',
-                       b == '{', b == '}':
-                       continue
-               }
-               break
-       }
-       varnameEnd := i
-
-       if varnameEnd == varnameStart {
-               return
-       }
-
-       for i < n && (text[i] == ' ' || text[i] == '\t') {
-               i++
-       }
-
-       opStart := i
-       if i < n {
-               if b := text[i]; b == '!' || b == '+' || b == ':' || b == '?' {
-                       i++
-               }
-       }
-       if i < n && text[i] == '=' {
-               i++
-       } else {
-               return
-       }
-       opEnd := i
-
-       if text[varnameEnd-1] == '+' && varnameEnd == opStart && text[opStart] == '=' {
-               varnameEnd--
-               opStart--
-       }
-
-       for i < n && (text[i] == ' ' || text[i] == '\t') {
-               i++
-       }
-
-       valueStart := i
-       valuebuf := make([]byte, n-valueStart)
-       j := 0
-       for ; i < n; i++ {
-               b := text[i]
-               if b == '#' && (i == valueStart || text[i-1] != '\\') {
-                       break
-               } else if b != '\\' || i+1 >= n || text[i+1] != '#' {
-                       valuebuf[j] = b
-                       j++
-               }
-       }
-
-       commentStart := i
-       for text[i-1] == ' ' || text[i-1] == '\t' {
-               i--
-       }
-       valueEnd := i
-
-       commentEnd := n
-
-       m = true
-       varname = text[varnameStart:varnameEnd]
-       spaceAfterVarname = text[varnameEnd:opStart]
-       op = text[opStart:opEnd]
-       valueAlign = text[0:valueStart]
-       value = strings.TrimSpace(string(valuebuf[:j]))
-       spaceAfterValue = text[valueEnd:commentStart]
-       comment = text[commentStart:commentEnd]
-       return
-}
-
-type DependencyPattern struct {
-       pkgbase  string // "freeciv-client", "{gcc48,gcc48-libs}", "${EMACS_REQD}"
-       lowerOp  string // ">=", ">"
-       lower    string // "2.5.0", "${PYVER}"
-       upperOp  string // "<", "<="
-       upper    string // "3.0", "${PYVER}"
-       wildcard string // "[0-9]*", "1.5.*", "${PYVER}"
-}
-
-func resolveVarsInRelativePath(relpath string, adjustDepth bool) string {
-       tmp := relpath
-       tmp = strings.Replace(tmp, "${PKGSRCDIR}", G.CurPkgsrcdir, -1)
-       tmp = strings.Replace(tmp, "${.CURDIR}", ".", -1)
-       tmp = strings.Replace(tmp, "${.PARSEDIR}", ".", -1)
-       if contains(tmp, "${LUA_PKGSRCDIR}") {
-               tmp = strings.Replace(tmp, "${LUA_PKGSRCDIR}", G.globalData.Latest("lang", `^lua[0-9]+$`, "../../lang/$0"), -1)
-       }
-       if contains(tmp, "${PHPPKGSRCDIR}") {
-               tmp = strings.Replace(tmp, "${PHPPKGSRCDIR}", G.globalData.Latest("lang", `^php[0-9]+$`, "../../lang/$0"), -1)
-       }
-       if contains(tmp, "${SUSE_DIR_PREFIX}") {
-               suseDirPrefix := G.globalData.Latest("emulators", `^(suse[0-9]+)_base`, "$1")
-               tmp = strings.Replace(tmp, "${SUSE_DIR_PREFIX}", suseDirPrefix, -1)
-       }
-       if contains(tmp, "${PYPKGSRCDIR}") {
-               tmp = strings.Replace(tmp, "${PYPKGSRCDIR}", G.globalData.Latest("lang", `^python[0-9]+$`, "../../lang/$0"), -1)
-       }
-       if contains(tmp, "${PYPACKAGE}") {
-               tmp = strings.Replace(tmp, "${PYPACKAGE}", G.globalData.Latest("lang", `^python[0-9]+$`, "$0"), -1)
-       }
-       if G.Pkg != nil {
-               tmp = strings.Replace(tmp, "${FILESDIR}", G.Pkg.Filesdir, -1)
-               tmp = strings.Replace(tmp, "${PKGDIR}", G.Pkg.Pkgdir, -1)
-       }
-
-       if adjustDepth {
-               if m, pkgpath := match1(tmp, `^\.\./\.\./([^.].*)$`); m {
-                       tmp = G.CurPkgsrcdir + "/" + pkgpath
-               }
-       }
-
-       if G.opts.Debug {
-               traceStep2("resolveVarsInRelativePath: %q => %q", relpath, tmp)
-       }
-       return tmp
-}

Index: pkgsrc/pkgtools/pkglint/files/shell_test.go
diff -u pkgsrc/pkgtools/pkglint/files/shell_test.go:1.13 pkgsrc/pkgtools/pkglint/files/shell_test.go:1.14
--- pkgsrc/pkgtools/pkglint/files/shell_test.go:1.13    Tue Dec 13 00:58:07 2016
+++ pkgsrc/pkgtools/pkglint/files/shell_test.go Sun Jan  1 15:15:47 2017
@@ -426,7 +426,7 @@ func (s *Suite) Test_ShellLine_checkComm
 func (s *Suite) Test_splitIntoMkWords(c *check.C) {
        url := "http://registry.gimp.org/file/fix-ca.c?action=download&id=9884&file=";
 
-       words, rest := splitIntoShellTokens(dummyLine, url) // Doesn’t really make sense
+       words, rest := splitIntoShellTokens(dummyLine, url) // Doesn't really make sense
 
        c.Check(words, check.DeepEquals, []string{"http://registry.gimp.org/file/fix-ca.c?action=download";, "&", "id=9884", "&", "file="})
        c.Check(rest, equals, "")

Index: pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go
diff -u pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go:1.3 pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go:1.4
--- pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go:1.3       Sat Jul  9 09:43:48 2016
+++ pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go   Sun Jan  1 15:15:47 2017
@@ -291,7 +291,7 @@ func (s *Suite) Test_ShTokenizer_ShAtom(
                token(shtWord, "echo", shqDquotBackt),
                token(shtSpace, " ", shqDquotBackt),
                token(shtWord, "\"", shqDquotBacktDquot),
-               token(shtWord, "\\`echo foo\\`", shqDquotBacktDquot), // One token, since it doesn’t influence parsing.
+               token(shtWord, "\\`echo foo\\`", shqDquotBacktDquot), // One token, since it doesn't influence parsing.
                token(shtWord, "\"", shqDquotBackt),
                token(shtWord, "`", shqDquot),
                token(shtWord, "\"", shqPlain))
@@ -435,7 +435,7 @@ func (s *Suite) Test_ShTokenizer_ShToken
                        NewShAtomVaruse("${PATH:Q}", shqPlain, "PATH", "Q")),
                NewShToken("true", NewShAtom(shtWord, "true", shqPlain)))
 
-       if false { // Don’t know how to tokenize this correctly.
+       if false { // Don't know how to tokenize this correctly.
                check("id=$$(${AWK} '{print}' < ${WRKSRC}/idfile)",
                        NewShToken("id=$$(${AWK} '{print}' < ${WRKSRC}/idfile)",
                                NewShAtom(shtWord, "id=", shqPlain),

Index: pkgsrc/pkgtools/pkglint/files/vartypecheck.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.21 pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.22
--- pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.21  Tue Dec 13 00:58:07 2016
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck.go       Sun Jan  1 15:15:47 2017
@@ -591,7 +591,7 @@ func (cv *VartypeCheck) Option() {
        }
 
        if m, optname := match1(value, `^-?([a-z][-0-9a-z+]*)$`); m {
-               if _, found := G.globalData.PkgOptions[optname]; !found { // There’s a difference between empty and absent here.
+               if _, found := G.globalData.PkgOptions[optname]; !found { // There's a difference between empty and absent here.
                        line.Warnf("Unknown option \"%s\".", optname)
                        Explain(
                                "This option is not documented in the mk/defaults/options.description",

Added files:

Index: pkgsrc/pkgtools/pkglint/files/getopt/getopt.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/getopt/getopt.go:1.1
--- /dev/null   Sun Jan  1 15:15:48 2017
+++ pkgsrc/pkgtools/pkglint/files/getopt/getopt.go      Sun Jan  1 15:15:47 2017
@@ -0,0 +1,236 @@
+package getopt
+
+// Self-written getopt to support multi-argument options.
+
+import (
+       "fmt"
+       "io"
+       "strings"
+       "text/tabwriter"
+       "unicode/utf8"
+)
+
+type Options struct {
+       options []*option
+}
+
+func NewOptions() *Options {
+       return new(Options)
+}
+
+func (o *Options) AddFlagGroup(shortName rune, longName, argDescription, description string) *FlagGroup {
+       grp := new(FlagGroup)
+       opt := &option{shortName, longName, argDescription, description, grp}
+       o.options = append(o.options, opt)
+       return grp
+}
+
+func (o *Options) AddFlagVar(shortName rune, longName string, pflag *bool, defval bool, description string) {
+       *pflag = defval
+       opt := &option{shortName, longName, "", description, pflag}
+       o.options = append(o.options, opt)
+}
+
+func (o *Options) Parse(args []string) (remainingArgs []string, err error) {
+       var skip int
+       for i := 1; i < len(args) && err == nil; i++ {
+               arg := args[i]
+               switch {
+               case arg == "--":
+                       remainingArgs = append(remainingArgs, args[i+1:]...)
+                       return
+               case strings.HasPrefix(arg, "--"):
+                       skip, err = o.parseLongOption(args, i, arg[2:])
+                       i += skip
+               case strings.HasPrefix(arg, "-"):
+                       skip, err = o.parseShortOptions(args, i, arg[1:])
+                       i += skip
+               default:
+                       remainingArgs = append(remainingArgs, arg)
+               }
+       }
+       if err != nil {
+               err = optErr(args[0] + ": " + err.Error())
+       }
+       return
+}
+
+func (o *Options) parseLongOption(args []string, i int, argRest string) (skip int, err error) {
+       parts := strings.SplitN(argRest, "=", 2)
+       argname := parts[0]
+       var argval *string
+       if 1 < len(parts) {
+               argval = &parts[1]
+       }
+
+       for _, opt := range o.options {
+               if argname == opt.longName {
+                       return o.handleLongOption(args, i, opt, argval)
+               }
+       }
+
+       var prefixOpt *option
+       for _, opt := range o.options {
+               if strings.HasPrefix(opt.longName, argname) {
+                       if prefixOpt == nil {
+                               prefixOpt = opt
+                       } else {
+                               return 0, optErr(fmt.Sprintf("ambiguous option: --%s could mean --%s or --%s", argRest, prefixOpt.longName, opt.longName))
+                       }
+               }
+       }
+       if prefixOpt != nil {
+               return o.handleLongOption(args, i, prefixOpt, argval)
+       }
+       return 0, optErr("unknown option: --" + argRest)
+}
+
+func (o *Options) handleLongOption(args []string, i int, opt *option, argval *string) (skip int, err error) {
+       switch data := opt.data.(type) {
+       case *bool:
+               if argval == nil {
+                       *data = true
+               } else {
+                       switch *argval {
+                       case "true", "on", "enabled", "1":
+                               *data = true
+                       case "false", "off", "disabled", "0":
+                               *data = false
+                       default:
+                               return 0, optErr("invalid argument for option --" + opt.longName)
+                       }
+               }
+               return 0, nil
+       case *FlagGroup:
+               if argval == nil {
+                       return 1, data.parse("--"+opt.longName+"=", args[i+1])
+               } else {
+                       return 0, data.parse("--"+opt.longName+"=", *argval)
+               }
+       }
+       panic("getopt: unknown option type")
+}
+
+func (o *Options) parseShortOptions(args []string, i int, optchars string) (skip int, err error) {
+optchar:
+       for ai, optchar := range optchars {
+               for _, opt := range o.options {
+                       if optchar == opt.shortName {
+                               switch data := opt.data.(type) {
+                               case *bool:
+                                       *data = true
+                                       continue optchar
+                               case *FlagGroup:
+                                       argarg := optchars[ai+utf8.RuneLen(optchar):]
+                                       if argarg != "" {
+                                               return 0, data.parse(string([]rune{'-', optchar}), argarg)
+                                       } else if i+1 < len(args) {
+                                               return 1, data.parse(string([]rune{'-', optchar}), args[i+1])
+                                       } else {
+                                               return 0, optErr("option requires an argument: -" + string([]rune{optchar}))
+                                       }
+                               }
+                       }
+               }
+               return 0, optErr("unknown option: -" + string([]rune{optchar}))
+       }
+       return 0, nil
+}
+
+func (o *Options) Help(out io.Writer, generalUsage string) {
+       wr := tabwriter.NewWriter(out, 1, 0, 2, ' ', tabwriter.TabIndent)
+
+       io.WriteString(wr, "usage: "+generalUsage+"\n")
+       io.WriteString(wr, "\n")
+       wr.Flush()
+
+       for _, opt := range o.options {
+               if opt.argDescription == "" {
+                       fmt.Fprintf(wr, "  -%c, --%s\t %s\n",
+                               opt.shortName, opt.longName, opt.description)
+               } else {
+                       fmt.Fprintf(wr, "  -%c, --%s=%s\t %s\n",
+                               opt.shortName, opt.longName, opt.argDescription, opt.description)
+               }
+       }
+       wr.Flush()
+
+       hasFlagGroups := false
+       for _, opt := range o.options {
+               switch flagGroup := opt.data.(type) {
+               case *FlagGroup:
+                       hasFlagGroups = true
+                       io.WriteString(wr, "\n")
+                       fmt.Fprintf(wr, "  Flags for -%c, --%s:\n", opt.shortName, opt.longName)
+                       io.WriteString(wr, "    all\t all of the following\n")
+                       io.WriteString(wr, "    none\t none of the following\n")
+                       for _, flag := range flagGroup.flags {
+                               state := "disabled"
+                               if *flag.value {
+                                       state = "enabled"
+                               }
+                               fmt.Fprintf(wr, "    %s\t %s (%v)\n", flag.name, flag.help, state)
+                       }
+                       wr.Flush()
+               }
+       }
+       if hasFlagGroups {
+               io.WriteString(wr, "\n")
+               io.WriteString(wr, "  (Prefix a flag with \"no-\" to disable it.)\n")
+               wr.Flush()
+       }
+}
+
+type option struct {
+       shortName      rune
+       longName       string
+       argDescription string
+       description    string
+       data           interface{}
+}
+
+type FlagGroup struct {
+       flags []*groupFlag
+}
+
+func (fg *FlagGroup) AddFlagVar(name string, flag *bool, defval bool, help string) {
+       opt := &groupFlag{name, flag, help}
+       fg.flags = append(fg.flags, opt)
+       *flag = defval
+}
+
+func (fg *FlagGroup) parse(optionPrefix, arg string) (err error) {
+argopt:
+       for _, argopt := range strings.Split(arg, ",") {
+               if argopt == "none" || argopt == "all" {
+                       for _, opt := range fg.flags {
+                               *opt.value = argopt == "all"
+                       }
+                       continue argopt
+               }
+               for _, opt := range fg.flags {
+                       if argopt == opt.name {
+                               *opt.value = true
+                               continue argopt
+                       }
+                       if argopt == "no-"+opt.name {
+                               *opt.value = false
+                               continue argopt
+                       }
+               }
+               return optErr("unknown option: " + optionPrefix + argopt)
+       }
+       return nil
+}
+
+type groupFlag struct {
+       name  string
+       value *bool
+       help  string
+}
+
+type optErr string
+
+func (err optErr) Error() string {
+       return string(err)
+}
Index: pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go:1.1
--- /dev/null   Sun Jan  1 15:15:48 2017
+++ pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go Sun Jan  1 15:15:47 2017
@@ -0,0 +1,107 @@
+package getopt
+
+import (
+       check "gopkg.in/check.v1"
+       "testing"
+)
+
+type Suite struct {
+}
+
+var _ = check.Suite(new(Suite))
+
+func Test(t *testing.T) { check.TestingT(t) }
+
+func (s *Suite) Test_Options_Parse_short(c *check.C) {
+       opts := NewOptions()
+       var help bool
+       opts.AddFlagVar('h', "help", &help, false, "prints a help page")
+
+       args, err := opts.Parse([]string{"progname", "-h"})
+
+       c.Assert(err, check.IsNil)
+       c.Check(args, check.IsNil)
+       c.Check(help, check.Equals, true)
+}
+
+func (s *Suite) Test_Options_Parse_unknown_short(c *check.C) {
+       opts := NewOptions()
+
+       _, err := opts.Parse([]string{"progname", "-z"})
+
+       c.Check(err.Error(), check.Equals, "progname: unknown option: -z")
+}
+
+func (s *Suite) Test_Options_Parse_unknown_long(c *check.C) {
+       opts := NewOptions()
+
+       _, err := opts.Parse([]string{"progname", "--unknown-long"})
+
+       c.Check(err.Error(), check.Equals, "progname: unknown option: --unknown-long")
+}
+
+func (s *Suite) Test_Options_Parse_unknown_flag_in_group(c *check.C) {
+       opts := NewOptions()
+       opts.AddFlagGroup('W', "warnings", "", "")
+
+       _, err := opts.Parse([]string{"progname", "-Wall", "-Werror"})
+
+       c.Check(err.Error(), check.Equals, "progname: unknown option: -Werror")
+
+       _, err = opts.Parse([]string{"progname", "--warnings=all", "--warnings=no-error"})
+
+       c.Check(err.Error(), check.Equals, "progname: unknown option: --warnings=no-error")
+
+       _, err = opts.Parse([]string{"progname", "-W"})
+
+       c.Check(err.Error(), check.Equals, "progname: option requires an argument: -W")
+}
+
+func (s *Suite) Test_Options_Parse_abbreviated_long(c *check.C) {
+       opts := NewOptions()
+       var longFlag, longerFlag bool
+       opts.AddFlagVar('?', "long", &longFlag, false, "")
+       opts.AddFlagVar('?', "longer", &longerFlag, false, "")
+
+       _, err := opts.Parse([]string{"progname", "--lo"})
+
+       c.Check(err.Error(), check.Equals, "progname: ambiguous option: --lo could mean --long or --longer")
+
+       args, err := opts.Parse([]string{"progname", "--long"})
+
+       c.Assert(err, check.IsNil)
+       c.Check(args, check.IsNil)
+       c.Check(longFlag, check.Equals, true)
+       c.Check(longerFlag, check.Equals, false)
+
+       longFlag = false
+       args, err = opts.Parse([]string{"progname", "--longe"})
+
+       c.Assert(err, check.IsNil)
+       c.Check(args, check.IsNil)
+       c.Check(longFlag, check.Equals, false)
+       c.Check(longerFlag, check.Equals, true)
+}
+
+func (s *Suite) Test_Options_Parse_mixed_args_and_options(c *check.C) {
+       opts := NewOptions()
+       var aflag, bflag bool
+       opts.AddFlagVar('a', "aflag", &aflag, false, "")
+       opts.AddFlagVar('b', "bflag", &bflag, false, "")
+
+       args, err := opts.Parse([]string{"progname", "-a", "arg1", "-b", "arg2"})
+
+       c.Assert(err, check.IsNil)
+       c.Check(args, check.DeepEquals, []string{"arg1", "arg2"})
+       c.Check(aflag, check.Equals, true)
+       c.Check(bflag, check.Equals, true)
+
+       aflag = false
+       bflag = false
+       args, err = opts.Parse([]string{"progname", "-a", "--", "arg1", "-b", "arg2"})
+
+       c.Assert(err, check.IsNil)
+       c.Check(args, check.DeepEquals, []string{"arg1", "-b", "arg2"})
+       c.Check(aflag, check.Equals, true)
+       c.Check(bflag, check.Equals, false)
+}

Index: pkgsrc/pkgtools/pkglint/files/pkgver/vercmp.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/pkgver/vercmp.go:1.1
--- /dev/null   Sun Jan  1 15:15:48 2017
+++ pkgsrc/pkgtools/pkglint/files/pkgver/vercmp.go      Sun Jan  1 15:15:47 2017
@@ -0,0 +1,105 @@
+package pkgver
+
+// See pkgtools/pkg_install/files/lib/dewey.c
+
+import (
+       "strings"
+)
+
+func imax(a, b int) int {
+       if a > b {
+               return a
+       }
+       return b
+}
+func icmp(a, b int) int {
+       if a < b {
+               return -1
+       }
+       if a > b {
+               return 1
+       }
+       return 0
+}
+
+func Compare(left, right string) int {
+       lv := newVersion(left)
+       rv := newVersion(right)
+
+       m := imax(len(lv.v), len(rv.v))
+       for i := 0; i < m; i++ {
+               if c := icmp(lv.Place(i), rv.Place(i)); c != 0 {
+                       return c
+               }
+       }
+       return icmp(lv.nb, rv.nb)
+}
+
+type version struct {
+       v  []int
+       nb int
+}
+
+func newVersion(vstr string) *version {
+       v := new(version)
+       rest := strings.ToLower(vstr)
+       for rest != "" {
+               switch {
+               case isdigit(rest[0]):
+                       n := 0
+                       i := 0
+                       for i < len(rest) && isdigit(rest[i]) {
+                               n = 10*n + int(rest[i]-'0')
+                               i++
+                       }
+                       rest = rest[i:]
+                       v.Add(n)
+               case rest[0] == '_' || rest[0] == '.':
+                       v.Add(0)
+                       rest = rest[1:]
+               case strings.HasPrefix(rest, "alpha"):
+                       v.Add(-3)
+                       rest = rest[5:]
+               case strings.HasPrefix(rest, "beta"):
+                       v.Add(-2)
+                       rest = rest[4:]
+               case strings.HasPrefix(rest, "pre"):
+                       v.Add(-1)
+                       rest = rest[3:]
+               case strings.HasPrefix(rest, "rc"):
+                       v.Add(-1)
+                       rest = rest[2:]
+               case strings.HasPrefix(rest, "pl"):
+                       v.Add(0)
+                       rest = rest[2:]
+               case strings.HasPrefix(rest, "nb"):
+                       i := 2
+                       n := 0
+                       for i < len(rest) && isdigit(rest[i]) {
+                               n = 10*n + int(rest[i]-'0')
+                               i++
+                       }
+                       v.nb = n
+                       rest = rest[i:]
+               case rest[0]-'a' <= 'z'-'a':
+                       v.Add(int(rest[0] - 'a' + 1))
+                       rest = rest[1:]
+               default:
+                       rest = rest[1:]
+               }
+       }
+       return v
+}
+
+func (v *version) Add(i int) {
+       v.v = append(v.v, i)
+}
+func isdigit(b byte) bool {
+       return b-'0' <= 9
+}
+func (v *version) Place(i int) int {
+       if i < len(v.v) {
+               return v.v[i]
+       }
+       return 0
+}
Index: pkgsrc/pkgtools/pkglint/files/pkgver/vercmp_test.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/pkgver/vercmp_test.go:1.1
--- /dev/null   Sun Jan  1 15:15:48 2017
+++ pkgsrc/pkgtools/pkglint/files/pkgver/vercmp_test.go Sun Jan  1 15:15:47 2017
@@ -0,0 +1,72 @@
+package pkgver
+
+import (
+       check "gopkg.in/check.v1"
+       "testing"
+)
+
+type Suite struct{}
+
+func Test(t *testing.T) {
+       check.Suite(&Suite{})
+       check.TestingT(t)
+}
+
+func (s *Suite) Test_newVersion(c *check.C) {
+       c.Check(newVersion("5.0"), check.DeepEquals, &version{[]int{5, 0, 0}, 0})
+       c.Check(newVersion("5.0nb5"), check.DeepEquals, &version{[]int{5, 0, 0}, 5})
+       c.Check(newVersion("0.0.1-SNAPSHOT"), check.DeepEquals, &version{[]int{0, 0, 0, 0, 1, 19, 14, 1, 16, 19, 8, 15, 20}, 0})
+       c.Check(newVersion("1.0alpha3"), check.DeepEquals, &version{[]int{1, 0, 0, -3, 3}, 0})
+       c.Check(newVersion("2.5beta"), check.DeepEquals, &version{[]int{2, 0, 5, -2}, 0})
+       c.Check(newVersion("20151110"), check.DeepEquals, &version{[]int{20151110}, 0})
+       c.Check(newVersion("0"), check.DeepEquals, &version{[]int{0}, 0})
+       c.Check(newVersion("nb1"), check.DeepEquals, &version{nil, 1})
+       c.Check(newVersion("1.0.1a"), check.DeepEquals, &version{[]int{1, 0, 0, 0, 1, 1}, 0})
+       c.Check(newVersion("1.0.1z"), check.DeepEquals, &version{[]int{1, 0, 0, 0, 1, 26}, 0})
+       c.Check(newVersion("0pre20160620"), check.DeepEquals, &version{[]int{0, -1, 20160620}, 0})
+}
+
+func (s *Suite) Test_pkgverCmp(c *check.C) {
+       var versions = [][]string{
+               {"0pre20160620"},
+               {"0"},
+               {"nb1"},
+               {"0.0.1-SNAPSHOT"},
+               {"1.0alpha"},
+               {"1.0alpha3"},
+               {"1", "1.0", "1.0.0"},
+               {"1.0nb1"},
+               {"1.0nb2"},
+               {"1.0.1a"},
+               {"1.0.1z"},
+               {"2.0pre", "2.0rc"},
+               {"2.0", "2.0pl"},
+               {"2.0.1nb4"},
+               {"2.0.1nb17"},
+               {"2.5beta"},
+               {"5.0"},
+               {"5.0nb5"},
+               {"5.5", "5.005"},
+               {"20151110"},
+       }
+
+       for i, iversions := range versions {
+               for _, iversion := range iversions {
+                       for j, jversions := range versions {
+                               for _, jversion := range jversions {
+                                       actual := Compare(iversion, jversion)
+                                       if i < j && !(actual < 0) {
+                                               c.Check([]interface{}{i, iversion, j, jversion, "<0"}, check.DeepEquals, []interface{}{i, iversion, j, jversion, actual})
+                                       }
+                                       if i == j && !(actual == 0) {
+                                               c.Check([]interface{}{i, iversion, j, jversion, "==0"}, check.DeepEquals, []interface{}{i, iversion, j, jversion, actual})
+                                       }
+                                       if i > j && !(actual > 0) {
+                                               c.Check([]interface{}{i, iversion, j, jversion, ">0"}, check.DeepEquals, []interface{}{i, iversion, j, jversion, actual})
+                                       }
+                               }
+                       }
+
+               }
+       }
+}



Home | Main Index | Thread Index | Old Index