github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/assignments/assignments_parser.go (about)

     1  package assignments
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/quickfeed/quickfeed/qf"
     8  	"google.golang.org/protobuf/types/known/timestamppb"
     9  	"gopkg.in/yaml.v2"
    10  )
    11  
    12  const defaultAutoApproveScoreLimit = 80
    13  
    14  // assignmentData holds information about a single assignment.
    15  // This is only used for parsing the 'assignment.yml' file.
    16  // Note that the struct can be private, but the fields must be
    17  // public to allow parsing.
    18  type assignmentData struct {
    19  	Order            uint32 `yaml:"order"`
    20  	Deadline         string `yaml:"deadline"`
    21  	IsGroupLab       bool   `yaml:"isgrouplab"`
    22  	AutoApprove      bool   `yaml:"autoapprove"`
    23  	ScoreLimit       uint32 `yaml:"scorelimit"`
    24  	Reviewers        uint32 `yaml:"reviewers"`
    25  	ContainerTimeout uint32 `yaml:"containertimeout"`
    26  }
    27  
    28  func newAssignmentFromFile(contents []byte, assignmentName string, courseID uint64) (*qf.Assignment, error) {
    29  	var newAssignment assignmentData
    30  	err := yaml.Unmarshal(contents, &newAssignment)
    31  	if err != nil {
    32  		return nil, fmt.Errorf("error unmarshalling assignment: %w", err)
    33  	}
    34  	if newAssignment.Order < 1 {
    35  		return nil, fmt.Errorf("assignment order must be greater than 0")
    36  	}
    37  	// if no auto approve score limit is defined; use the default
    38  	if newAssignment.ScoreLimit < 1 {
    39  		newAssignment.ScoreLimit = defaultAutoApproveScoreLimit
    40  	}
    41  	deadline, err := FixDeadline(newAssignment.Deadline)
    42  	if err != nil {
    43  		return nil, fmt.Errorf("error parsing deadline: %w", err)
    44  	}
    45  	// AssignmentID field from the parsed yaml is used to set Order, not assignment ID,
    46  	// or it will cause a database constraint violation (IDs must be unique)
    47  	// The Name field below is the folder name of the assignment.
    48  	assignment := &qf.Assignment{
    49  		CourseID:         courseID,
    50  		Deadline:         deadline,
    51  		Name:             assignmentName,
    52  		Order:            newAssignment.Order,
    53  		IsGroupLab:       newAssignment.IsGroupLab,
    54  		AutoApprove:      newAssignment.AutoApprove,
    55  		ScoreLimit:       newAssignment.ScoreLimit,
    56  		Reviewers:        newAssignment.Reviewers,
    57  		ContainerTimeout: newAssignment.ContainerTimeout,
    58  	}
    59  	return assignment, nil
    60  }
    61  
    62  func FixDeadline(in string) (*timestamppb.Timestamp, error) {
    63  	acceptedLayouts := []string{
    64  		"2006-1-2T15:04:05",
    65  		"2006-1-2 15:04:05",
    66  		"2006-1-2T15:04",
    67  		"2006-1-2 15:04",
    68  		"2006-1-2T1504",
    69  		"2006-1-2 1504",
    70  		"2006-1-2T15",
    71  		"2006-1-2 15",
    72  		"2006-1-2 3pm",
    73  		"2006-1-2 3:04pm",
    74  		"2006-1-2 3:04:05pm",
    75  		"2-1-2006T15:04:05",
    76  		"2-1-2006 15:04:05",
    77  		"2-1-2006T15:04",
    78  		"2-1-2006 15:04",
    79  		"2-1-2006T1504",
    80  		"2-1-2006 1504",
    81  		"2-1-2006T15",
    82  		"2-1-2006 15",
    83  		"2-1-2006 3pm",
    84  		"2-1-2006 3:04pm",
    85  		"2-1-2006 3:04:05pm",
    86  	}
    87  	for _, layout := range acceptedLayouts {
    88  		t, err := time.Parse(layout, in)
    89  		if err != nil {
    90  			continue
    91  		}
    92  		return timestamppb.New(t), nil
    93  	}
    94  	return nil, fmt.Errorf("invalid date format: %s", in)
    95  }