Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
qubitz committed Feb 20, 2021
0 parents commit 0b818dc
Show file tree
Hide file tree
Showing 15 changed files with 578 additions and 0 deletions.
59 changes: 59 additions & 0 deletions commands/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package commands

import (
"errors"
"fmt"
)

// A Command executes a predefined procedure established before its creation.
type Command interface {
Execute() error
}

type setup interface {
Parse(args []string) Command
}

// Parse creates a valid Command from args. The expected format
// of args is: "<subcommand> <arguments...>". If an error occurs and a
// valid command can not be constructed, a new command is created explaining
// the error and suggesting the "lawyer help" command.
func Parse(args []string) Command {
if len(args) == 0 {
return suggestHelp(errors.New("no subcommand specified"))
}

setup, err := getSetup(args[0])
if err != nil {
return suggestHelp(err)
}

return setup.Parse(args[1:])
}

func getSetup(command string) (setup, error) {
switch command {
case "indict":
return setupForIndict(), nil
case "help", "--help", "-h":
return setupForHelp(), nil
default:
return nil, fmt.Errorf("unknown command \"%v\"", command)
}
}

func suggestHelp(err error) suggestHelpCommand {
return suggestHelpCommand{
error: err,
}
}

type suggestHelpCommand struct {
error
}

func (suggHelp suggestHelpCommand) Execute() error {
fmt.Println("invalid command: " + suggHelp.error.Error())
fmt.Println("use \"lawyer help\" to show usage information")
return nil
}
29 changes: 29 additions & 0 deletions commands/commands_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package commands

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestParsesCommands(t *testing.T) {
path := "tes.txt"

var cases = []struct {
in []string
want Command
}{
{
in: []string{"indict", path},
want: &indictCommand{
paths: []string{path},
lawPath: "",
},
},
}

for _, cas := range cases {
got := Parse(cas.in)
require.Equal(t, cas.want, got)
}
}
68 changes: 68 additions & 0 deletions commands/help_command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package commands

import (
"fmt"

"./manuals"
)

type helpCommand struct {
subject string
}

func (help helpCommand) Execute() (err error) {
if help.subject == "" {
printSummary()
} else {
err = printManual(help.subject)
}

return err
}

func printSummary() {
fmt.Println("Lawyer is a tool for maintaining copyright headers in source code.")
fmt.Println()
printUsage()
fmt.Println()
printCommands()
fmt.Println()
printTopics()
}

func printUsage() {
fmt.Print("Usage:")
fmt.Println()
fmt.Println("\tlawyer <command> [arguments]")
}

func printCommands() {
fmt.Println("The commands are:")
fmt.Println()
for command, manual := range manuals.CommandManualMap {
fmt.Printf("\t%-10v %v\n", command, manual.Summary)
}
fmt.Println()
fmt.Println("Use \"go help <command>\" for more information about a command.")
}

func printTopics() {
fmt.Println("Additional help topics:")
fmt.Println()
for topic, manual := range manuals.TopicManualMap {
fmt.Printf("\t%-10v %v\n", topic, manual.Summary)
}
fmt.Println()
fmt.Println("Use \"go help <topic>\" for more information about that topic.")
}

func printManual(command string) error {
foundManual := manuals.EntireManualMap[command]

if foundManual == *new(manuals.Manual) {
return fmt.Errorf("no manual found for \"%v\"", command)
}

fmt.Println(foundManual.Content)
return nil
}
28 changes: 28 additions & 0 deletions commands/help_setup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package commands

import (
"errors"
"flag"
)

type helpSetup struct {
flags *flag.FlagSet
print bool
}

func setupForHelp() setup {
return new(helpSetup)
}

func (setup helpSetup) Parse(args []string) Command {
switch len(args) {
case 0:
return new(helpCommand)
case 1:
return helpCommand{
subject: args[0],
}
default:
return suggestHelp(errors.New("too many arguments"))
}
}
54 changes: 54 additions & 0 deletions commands/indict_command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package commands

import (
"fmt"
"os"
"regexp"

"../laws"
"../trial"
"github.com/TwinProduction/go-color"
)

type indictCommand struct {
paths []string
lawPath string
}

func (indictment *indictCommand) Execute() error {
law, err := laws.RetrieveFrom(indictment.lawPath)
if err != nil {
return fmt.Errorf("unable to retrieve law contents\n%w", err)
}

for _, path := range indictment.paths {
err := indict(path, law.Expected)

if err != nil {
fmt.Print(color.Yellow)
fmt.Printf("%v dismissed\n", path)
fmt.Println(err.Error())
fmt.Print(color.Reset)
}
}

fmt.Println("indictment complete")
return nil
}

func indict(path string, expected regexp.Regexp) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()

innocent, evidence := trial.Conduct(expected, file)
fmt.Printf("%v is %v\n", path, trial.ToVerdict(innocent))

if !innocent {
fmt.Println("\n" + trial.FormatEvidence(evidence))
}

return nil
}
32 changes: 32 additions & 0 deletions commands/indict_setup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package commands

import (
"errors"
"flag"
)

type indictSetup struct {
flags *flag.FlagSet
}

func setupForIndict() indictSetup {
flags := flag.NewFlagSet("indict", flag.ExitOnError)

return indictSetup{
flags: flags,
}
}

func (setup indictSetup) Parse(args []string) Command {
setup.flags.Parse(args)

paths := setup.flags.Args()
if len(paths) == 0 {
return suggestHelp(
errors.New("defendant path(s) required for indictment"))
}

return &indictCommand{
paths: paths,
}
}
20 changes: 20 additions & 0 deletions commands/manuals/indict.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package manuals

var indictManual Manual = Manual{
Summary: "verify files have the expected header",
Content: `Reads the top 10 lines (the header) of every file found matching
<file_pattern> and compares them to the regular expression of
"expected" inside the law file. Enter "lawyer help law" for more
information on the law file.
Usage:
lawyer indict <file_pattern>
Arguments:
<file_pattern> file(s) to indict
If the header does not match the expected header, the header
is printed as evidence and the file is marked guilty.`,
}
20 changes: 20 additions & 0 deletions commands/manuals/law.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package manuals

var lawManual Manual = Manual{
Summary: "law file",
Content: `The "Law File" is a YAML file that describes the possible values
of a file header. Here is an example Law File:
expected: ^// © ABC Company 1996. All rights reserved.$
Explanation:
expected Regular expression of the expected file header.
The Law File should be located in the current directory from which lawyer is
executed with the name "law" and the "yml" or "yaml" extension.
Also note that YAML has multi-line support for file headers that require
multiple lines or even newlines after multiple lines. See the YAML specfication
for more details.`,
}
36 changes: 36 additions & 0 deletions commands/manuals/manuals.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package manuals

// Manual represents the coupling of the one-line summary and detailed
// explaination of a cli command or general Lawyer topic.
type Manual struct {
Summary string
Content string
}

// ManualMap represents a one-to-one mapping between a cli help keyword
// (ex. lawyer help "indict") and the corresponding Manual.
type ManualMap = map[string]Manual

// CommandManualMap is a list of all cli command Manuals mapped by their
// corresponding help keywords.
var CommandManualMap = ManualMap{
"help": Manual{"print this help page", "stop repeating yourself"},
"indict": indictManual,
}

// TopicManualMap is a list of all help topics mapped by their corresponding
// help keywords.
var TopicManualMap = ManualMap{
"law": lawManual,
}

// EntireManualMap is a list of all cli command Manuals AND help topic mapped
// by their corresponding help keywords.
var EntireManualMap = merge(CommandManualMap, TopicManualMap)

func merge(a, b ManualMap) ManualMap {
for key, value := range a {
b[key] = value
}
return b
}
Loading

0 comments on commit 0b818dc

Please sign in to comment.