All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.oliviercailloux.grade.markers.Marks Maven / Gradle / Ivy

The newest version!
package io.github.oliviercailloux.grade.markers;

import static com.google.common.base.Preconditions.checkArgument;
import static io.github.oliviercailloux.jaris.exceptions.Unchecker.IO_UNCHECKER;

import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import io.github.oliviercailloux.grade.Criterion;
import io.github.oliviercailloux.grade.CriterionGradeWeight;
import io.github.oliviercailloux.grade.IGrade;
import io.github.oliviercailloux.grade.WeightingGrade;
import io.github.oliviercailloux.grade.old.Mark;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * NB to cope for combinatorics in possible combinations of marks, keep single ones as simple as
 * possible, and possibly when asking for (e.g. POM_DEP_GUAVA), specify with a boolean if the
 * comment "pom not found" should be specified. Or, give it the grades so far, so that it will see
 * whether the comment has already been given.
 *
 * @author Olivier Cailloux
 *
 */
public class Marks {
  @SuppressWarnings("unused")
  private static final Logger LOGGER = LoggerFactory.getLogger(Marks.class);

  public static enum MarksCriterion {
    FILE_EXISTS, FILE_CONTENTS_MATCH_EXACTLY, FILE_CONTENTS_MATCH_APPROXIMATELY;

    public Criterion asCriterion() {
      return Criterion.given(toString());
    }
  }

  public static IGrade noDerivedFilesGrade(Path projectRoot) {
    final ImmutableSet forbidden =
        ImmutableSet.of(projectRoot.resolve(".classpath"), projectRoot.resolve(".project"),
            projectRoot.resolve(".settings/"), projectRoot.resolve("target/"),
            projectRoot.resolve("bin/"), projectRoot.resolve(".DS_Store"));

    final boolean contains;
    try (Stream entries = IO_UNCHECKER.getUsing(() -> Files.list(projectRoot))) {
      contains = entries.findAny().isPresent();
    }

    final boolean noForbidden;
    try (Stream entries = IO_UNCHECKER.getUsing(() -> Files.list(projectRoot))) {
      noForbidden = entries.noneMatch(forbidden::contains);
    }

    return Mark.binary(contains && noForbidden);
  }

  public static IGrade travisConfGrade(String travisContent) {
    if (travisContent.isEmpty()) {
      return Mark.zero("Configuration missing or named incorrectly.");
    }

    final Predicate lang = Predicates.contains(Pattern.compile("language: java"));
    final Predicate dist = Predicates.contains(Pattern.compile("dist: xenial"));
    /**
     * I still accept this as I suspect some of my examples in the course use it.
     */
    final Predicate distTrusty = Predicates.contains(Pattern.compile("dist: trusty"));
    final Predicate script = Predicates.contains(Pattern.compile("script: "));
    final boolean hasLang = lang.test(travisContent);
    final boolean hasDistXenial = dist.test(travisContent);
    final boolean hasDistTrusty = distTrusty.test(travisContent);
    final boolean hasDist = hasDistXenial || hasDistTrusty;
    final boolean hasScript = script.test(travisContent);
    final boolean deducibleLang = hasLang || hasScript;
    final Mark langAndNoScript;
    if (hasLang && !hasScript) {
      langAndNoScript = Mark.one();
    } else if (hasLang && hasScript) {
      langAndNoScript = Mark.given(1d / 4d, "Redundant script.");
    } else if (!hasLang && hasScript) {
      langAndNoScript = Mark.given(1d / 4d, "Script instead of lang.");
    } else {
      assert !hasLang && !hasScript;
      langAndNoScript = Mark.zero("No language given, directly or indirectly.");
    }
    final Mark distAndDeducibleLanguage;
    if (!hasDist) {
      distAndDeducibleLanguage = Mark.zero("Missing ‘dist: xenial’.");
    } else if (!deducibleLang) {
      distAndDeducibleLanguage = Mark.zero("No deducible language");
    } else if (hasDistTrusty) {
      distAndDeducibleLanguage =
          Mark.one("Tolerating dist trusty, but dist xenial is to be preferred");
    } else {
      assert hasDist && deducibleLang && !hasDistTrusty && hasDistXenial;
      distAndDeducibleLanguage = Mark.one();
    }
    return WeightingGrade.from(ImmutableSet.of(
        CriterionGradeWeight.from(Criterion.given("Language indication"), langAndNoScript, 2d / 3d),
        CriterionGradeWeight.from(Criterion.given("Dist indication (and language indicated)"),
            distAndDeducibleLanguage, 1d / 3d)));
  }

  public static IGrade timeGrade(Instant submitted, Instant deadline,
      Function timeScorer) {
    final Duration tardiness = Duration.between(deadline, submitted);

    LOGGER.debug("Last: {}, deadline: {}, tardiness: {}.", submitted, deadline, tardiness);
    final Mark grade;
    if (tardiness.compareTo(Duration.ZERO) > 0) {
      LOGGER.warn("Last event after deadline: {}.", submitted);
      final double penalty = timeScorer.apply(tardiness);
      checkArgument(0d <= penalty && penalty <= 1d);
      grade = Mark.given(penalty,
          "Last event after deadline: "
              + ZonedDateTime.ofInstant(submitted, ZoneId.of("Europe/Paris")) + ", " + tardiness
              + " late.");
    } else {
      grade = Mark.one();
    }
    return grade;
  }

  public static IGrade fileMatchesGrade(Path file, String exactTarget, Pattern approximateTarget) {
    final boolean exists = Files.exists(file);
    final String content = exists ? IO_UNCHECKER.getUsing(() -> Files.readString(file)) : "";
    final boolean matchesExactly = exists && content.stripTrailing().equals(exactTarget);
    final boolean matchesApproximately =
        exists && (matchesExactly || approximateTarget.matcher(content).matches());
    final Mark matchesExactlyMark =
        exists
            ? Mark.binary(matchesExactly, "",
                String.format("Expected \"%s\", found \"%s\".", exactTarget, content))
            : Mark.zero();
    return WeightingGrade.from(ImmutableList.of(
        CriterionGradeWeight.from(MarksCriterion.FILE_EXISTS.asCriterion(), Mark.binary(exists),
            0.5d),
        CriterionGradeWeight.from(MarksCriterion.FILE_CONTENTS_MATCH_APPROXIMATELY.asCriterion(),
            Mark.binary(matchesApproximately), 0.4d),
        CriterionGradeWeight.from(MarksCriterion.FILE_CONTENTS_MATCH_EXACTLY.asCriterion(),
            matchesExactlyMark, 0.1d)));
  }

  public static Pattern extendWhite(String basis) {
    return Pattern.compile("[\\h\\v]*\"?(?" + basis + ")\"?[\\h\\v]*",
        Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS);
  }

  public static Pattern extendAll(String basis) {
    return Pattern.compile(".*(?" + basis + ").*",
        Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS | Pattern.DOTALL);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy