| 
 | 1 | +package main  | 
 | 2 | + | 
 | 3 | +import (  | 
 | 4 | +	"bufio"  | 
 | 5 | +	"context"  | 
 | 6 | +	"encoding/csv"  | 
 | 7 | +	"flag"  | 
 | 8 | +	"fmt"  | 
 | 9 | +	"io"  | 
 | 10 | +	"log"  | 
 | 11 | +	"os"  | 
 | 12 | +	"path/filepath"  | 
 | 13 | +	"strings"  | 
 | 14 | +	"time"  | 
 | 15 | +)  | 
 | 16 | + | 
 | 17 | +func main() {  | 
 | 18 | +	var file string // flag -f for file  | 
 | 19 | +	flag.StringVar(&file, "f", "problems.csv", "file location of quiz")  | 
 | 20 | + | 
 | 21 | +	var timeLimit int  | 
 | 22 | +	flag.IntVar(&timeLimit, "t", 30, "time limit for the quiz")  | 
 | 23 | + | 
 | 24 | +	//parse the flag  | 
 | 25 | +	flag.Parse()  | 
 | 26 | + | 
 | 27 | +	// validate correct file is used  | 
 | 28 | +	if err := validatePath(file); err != nil {  | 
 | 29 | +		log.Fatal(err)  | 
 | 30 | +	}  | 
 | 31 | + | 
 | 32 | +	problems := NewProblemSheet()  | 
 | 33 | +	err := problems.populateSheet(file)  | 
 | 34 | +	if err != nil {  | 
 | 35 | +		log.Fatal(err)  | 
 | 36 | +	}  | 
 | 37 | + | 
 | 38 | +	problems.StartQuiz(time.Duration(timeLimit)*time.Second, os.Stdin, os.Stdout)  | 
 | 39 | +	problems.DisplayResults(os.Stdout)  | 
 | 40 | +}  | 
 | 41 | + | 
 | 42 | +func validatePath(path string) error {  | 
 | 43 | +	// validate extension is correct  | 
 | 44 | +	if filepath.Ext(path) != ".csv" {  | 
 | 45 | +		return fmt.Errorf("malformed file path: %s contains no csv extension", path)  | 
 | 46 | +	}  | 
 | 47 | + | 
 | 48 | +	//check that file exists  | 
 | 49 | +	_, err := os.Stat(path)  | 
 | 50 | +	if os.IsNotExist(err) {  | 
 | 51 | +		return fmt.Errorf("path does not exist: %w", err)  | 
 | 52 | +	} else if err != nil {  | 
 | 53 | +		return fmt.Errorf("error getting file info: %w", err)  | 
 | 54 | +	}  | 
 | 55 | + | 
 | 56 | +	return nil  | 
 | 57 | +}  | 
 | 58 | + | 
 | 59 | +type Problem struct {  | 
 | 60 | +	Question string  | 
 | 61 | +	Answer   string  | 
 | 62 | +}  | 
 | 63 | + | 
 | 64 | +type ProblemSheet struct {  | 
 | 65 | +	Questions         []Problem  | 
 | 66 | +	CorrectlyAnswered int  | 
 | 67 | +}  | 
 | 68 | + | 
 | 69 | +func (p *ProblemSheet) populateSheet(file string) error {  | 
 | 70 | +	//open file  | 
 | 71 | +	f, err := os.Open(file)  | 
 | 72 | +	if err != nil {  | 
 | 73 | +		return fmt.Errorf("failed to open file: %w", err)  | 
 | 74 | +	}  | 
 | 75 | +	defer f.Close()  | 
 | 76 | + | 
 | 77 | +	reader := csv.NewReader(f)  | 
 | 78 | +	lines, err := reader.ReadAll()  | 
 | 79 | +	if err != nil {  | 
 | 80 | +		return fmt.Errorf("failed to read csv: %w", err)  | 
 | 81 | +	}  | 
 | 82 | + | 
 | 83 | +	if len(lines) == 0 {  | 
 | 84 | +		return fmt.Errorf("empty csv file loaded")  | 
 | 85 | +	}  | 
 | 86 | + | 
 | 87 | +	for _, line := range lines {  | 
 | 88 | + | 
 | 89 | +		newProblem := Problem{  | 
 | 90 | +			Question: strings.TrimSpace(line[0]),  | 
 | 91 | +			Answer:   strings.TrimSpace(line[1]),  | 
 | 92 | +		}  | 
 | 93 | + | 
 | 94 | +		p.Questions = append(p.Questions, newProblem)  | 
 | 95 | +	}  | 
 | 96 | +	return nil  | 
 | 97 | +}  | 
 | 98 | + | 
 | 99 | +func NewProblemSheet() *ProblemSheet {  | 
 | 100 | +	return &ProblemSheet{}  | 
 | 101 | +}  | 
 | 102 | + | 
 | 103 | +func (p *ProblemSheet) StartQuiz(limit time.Duration, r io.Reader, w io.Writer) {  | 
 | 104 | + | 
 | 105 | +	reader := bufio.NewReader(r)  | 
 | 106 | + | 
 | 107 | +	fmt.Fprintln(w, "Press Enter to begin quiz")  | 
 | 108 | +	reader.ReadString('\n')  | 
 | 109 | + | 
 | 110 | +	ctx, cancel := context.WithTimeout(context.Background(), limit)  | 
 | 111 | +	defer cancel()  | 
 | 112 | + | 
 | 113 | +	for _, problem := range p.Questions {  | 
 | 114 | +		answerCh := make(chan string, 1)  | 
 | 115 | + | 
 | 116 | +		go func(q string) {  | 
 | 117 | +			fmt.Fprintf(w, "\n\nQuestion: %s\n", q)  | 
 | 118 | +			fmt.Fprint(w, "answer: ")  | 
 | 119 | +			userAns, _ := reader.ReadString('\n')  | 
 | 120 | +			answerCh <- strings.TrimSpace(userAns)  | 
 | 121 | +		}(problem.Question)  | 
 | 122 | + | 
 | 123 | +		select {  | 
 | 124 | +		case userAnswer := <-answerCh:  | 
 | 125 | +			if userAnswer == problem.Answer {  | 
 | 126 | +				p.CorrectlyAnswered++  | 
 | 127 | +			}  | 
 | 128 | +		case <-ctx.Done():  | 
 | 129 | +			fmt.Fprintln(w, "\nquiz not completed before timeout")  | 
 | 130 | +			return  | 
 | 131 | +		}  | 
 | 132 | +	}  | 
 | 133 | +}  | 
 | 134 | + | 
 | 135 | +func (p *ProblemSheet) DisplayResults(w io.Writer) {  | 
 | 136 | +	fmt.Fprintln(w, "Quiz has ended")  | 
 | 137 | +	fmt.Fprintf(w, "You answered %d/%d questions\n", p.CorrectlyAnswered, len(p.Questions))  | 
 | 138 | +}  | 
 | 139 | + | 
 | 140 | +//type workerFunc func() string  | 
 | 141 | +//  | 
 | 142 | +//func timeLimit(worker workerFunc, limit time.Duration) (string, error) {  | 
 | 143 | +//	out := make(chan string, 1)  | 
 | 144 | +//	ctx, cancel := context.WithTimeout(context.Background(), limit)  | 
 | 145 | +//	defer cancel()  | 
 | 146 | +//  | 
 | 147 | +//	go func() {  | 
 | 148 | +//		fmt.Println("goroutine started")  | 
 | 149 | +//		out <- worker()  | 
 | 150 | +//	}()  | 
 | 151 | +//  | 
 | 152 | +//	select {  | 
 | 153 | +//	case result := <-out:  | 
 | 154 | +//		return result, nil  | 
 | 155 | +//	case <-ctx.Done():  | 
 | 156 | +//		return "quiz not completed before timeout", errors.New("work timed out")  | 
 | 157 | +//	}  | 
 | 158 | +//}  | 
 | 159 | + | 
 | 160 | +//func (p *problemSheet) StartQuiz() string {  | 
 | 161 | +//	for _, problem := range p.questions {  | 
 | 162 | +//		fmt.Printf("\n\nQuestion: %s\n", problem.question)  | 
 | 163 | +//		fmt.Print("answer: ")  | 
 | 164 | +//		var answer string  | 
 | 165 | +//		fmt.Scanln(&answer)  | 
 | 166 | +//  | 
 | 167 | +//		if answer == problem.answer {  | 
 | 168 | +//			p.correctlyAnswered++  | 
 | 169 | +//		}  | 
 | 170 | +//	}  | 
 | 171 | +//  | 
 | 172 | +//	return "Quiz completed before timeout"  | 
 | 173 | +//}  | 
0 commit comments