github.com/exercism/configlet@v3.9.3-0.20200318193232-c70be6269e71+incompatible/track/problem_specification.go (about)

     1  package track
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	yaml "gopkg.in/yaml.v2"
    11  )
    12  
    13  const (
    14  	// ProblemSpecificationsDir is the default name of the cloned problem-specifications repository.
    15  	ProblemSpecificationsDir = "problem-specifications"
    16  	filenameDescription      = "description.md"
    17  	filenameMetadata         = "metadata.yml"
    18  )
    19  
    20  var (
    21  	// ProblemSpecificationsPath is the location of the cloned problem-specifications repository.
    22  	ProblemSpecificationsPath string
    23  )
    24  
    25  // ProblemSpecification contains metadata describing an exercise.
    26  type ProblemSpecification struct {
    27  	Slug            string
    28  	Description     string
    29  	Title           string `yaml:"title"`
    30  	Source          string `yaml:"source"`
    31  	SourceURL       string `yaml:"source_url"`
    32  	root            string
    33  	trackID         string
    34  	metadataPath    string
    35  	descriptionPath string
    36  }
    37  
    38  // NewProblemSpecification loads the specification from files on disk.
    39  // It will default to a custom specification, falling back to the generic specification
    40  // if no custom one is found.
    41  func NewProblemSpecification(root, trackID, slug string) (*ProblemSpecification, error) {
    42  	spec := &ProblemSpecification{
    43  		root:    root,
    44  		trackID: trackID,
    45  		Slug:    slug,
    46  	}
    47  	spec.Title = spec.titleCasedSlug()
    48  
    49  	if err := spec.loadMetadata(); err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	if err := spec.loadDescription(); err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	return spec, nil
    58  }
    59  
    60  // Name is a readable version of the slug.
    61  func (spec *ProblemSpecification) Name() string {
    62  	if spec.Title == "" {
    63  		spec.Title = spec.titleCasedSlug()
    64  	}
    65  	return spec.Title
    66  }
    67  
    68  // MixedCaseName returns the name with all spaces removed.
    69  func (spec *ProblemSpecification) MixedCaseName() string {
    70  	return strings.Replace(spec.titleCasedSlug(), " ", "", -1)
    71  }
    72  
    73  // SnakeCaseName converts the slug to snake case.
    74  func (spec *ProblemSpecification) SnakeCaseName() string {
    75  	return strings.Replace(spec.Slug, "-", "_", -1)
    76  }
    77  
    78  // Credits are a markdown-formatted version of the source of the exercise.
    79  func (spec *ProblemSpecification) Credits() string {
    80  	if spec.SourceURL == "" {
    81  		return spec.Source
    82  	}
    83  	if spec.Source == "" {
    84  		return fmt.Sprintf("[%s](%s)", spec.SourceURL, spec.SourceURL)
    85  	}
    86  	return fmt.Sprintf("%s [%s](%s)", spec.Source, spec.SourceURL, spec.SourceURL)
    87  }
    88  
    89  func (spec *ProblemSpecification) titleCasedSlug() string {
    90  	return strings.Title(strings.Join(strings.Split(spec.Slug, "-"), " "))
    91  }
    92  
    93  func (spec *ProblemSpecification) loadMetadata() error {
    94  	metadataPath := filepath.Join(spec.customPath(), filenameMetadata)
    95  	if _, err := os.Stat(metadataPath); os.IsNotExist(err) {
    96  		metadataPath = filepath.Join(spec.sharedPath(), filenameMetadata)
    97  	}
    98  	spec.metadataPath = metadataPath
    99  
   100  	b, err := ioutil.ReadFile(spec.metadataPath)
   101  	if err != nil {
   102  		return err
   103  	}
   104  	return yaml.Unmarshal(b, &spec)
   105  }
   106  
   107  func (spec *ProblemSpecification) loadDescription() error {
   108  	descriptionPath := filepath.Join(spec.customPath(), filenameDescription)
   109  	if _, err := os.Stat(descriptionPath); os.IsNotExist(err) {
   110  		descriptionPath = filepath.Join(spec.sharedPath(), filenameDescription)
   111  	}
   112  	spec.descriptionPath = descriptionPath
   113  
   114  	b, err := ioutil.ReadFile(spec.descriptionPath)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	spec.Description = string(b)
   119  
   120  	return nil
   121  }
   122  
   123  func (spec *ProblemSpecification) sharedPath() string {
   124  	if ProblemSpecificationsPath != "" {
   125  		return filepath.Join(ProblemSpecificationsPath, "exercises", spec.Slug)
   126  	}
   127  	return filepath.Join(spec.root, ProblemSpecificationsDir, "exercises", spec.Slug)
   128  }
   129  
   130  func (spec *ProblemSpecification) customPath() string {
   131  	return filepath.Join(spec.root, spec.trackID, "exercises", spec.Slug, ".meta")
   132  }