Disabled external gits

This commit is contained in:
2022-04-07 18:46:57 +02:00
parent 88cb3426ad
commit 15e7120d6d
5316 changed files with 4563444 additions and 6 deletions

View File

@@ -0,0 +1,4 @@
scalaVersion := "3.0.2"
libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "2.0.0"

View File

@@ -0,0 +1 @@
sbt.version=1.5.5

Binary file not shown.

View File

@@ -0,0 +1,363 @@
package fos
import scala.util.parsing.combinator.syntactical.StandardTokenParsers
import scala.util.parsing.input._
/** This object implements a parser and evaluator for the
* simply typed lambda calculus found in Chapter 9 of
* the TAPL book.
*/
object SimplyTypedExtended extends StandardTokenParsers {
lexical.delimiters ++= List("(", ")", "\\", ".", ":", "=", "->", "{", "}", ",", "*", "+",
"=>", "|")
lexical.reserved ++= List("Bool", "Nat", "true", "false", "if", "then", "else", "succ",
"pred", "iszero", "let", "in", "fst", "snd", "fix", "letrec",
"case", "of", "inl", "inr", "as")
/** t ::= "true"
* | "false"
* | number
* | "succ" t
* | "pred" t
* | "iszero" t
* | "if" t "then" t "else" t
* | ident
* | "\" ident ":" T "." t
* | t t
* | "(" t ")"
* | "let" ident ":" T "=" t "in" t
* | "{" t "," t "}"
* | "fst" t
* | "snd" t
* | "inl" t "as" T
* | "inr" t "as" T
* | "case" t "of" "inl" ident "=>" t "|" "inr" ident "=>" t
* | "fix" t
* | "letrec" ident ":" T "=" t "in" t
*/
def term: Parser[Term] =
rep1(v) ^^ {
case x::Nil => x
case l => l.reduceLeft(App.apply)
}
def v: Parser[Term] =
"true" ^^^ True |
"false" ^^^ False |
numericLit ^^ { case n => numericRec(n.toInt,Zero)._2 } |
("succ" ~> term) ^^ { Succ.apply } |
("pred" ~> term) ^^ { Pred.apply } |
("iszero" ~> term) ^^ { IsZero.apply } |
("if" ~> term) ~ ("then" ~> term) ~ ("else" ~> term) ^^ { case cond ~ t1 ~ t2 => If(cond, t1, t2) } |
ident ^^ { Var.apply } |
("\\" ~> ident) ~ (":" ~> typeTerm) ~ ("." ~> term) ^^ { case id ~ tpe ~ t => Abs(id, tpe, t) } |
("(" ~> term <~ ")") |
("let" ~> ident) ~ (":" ~> typeTerm) ~ ("=" ~> term) ~ ("in" ~> term) ^^ { case id ~ tpe ~ t1 ~ t2 => App(Abs(id, tpe, t2), t1) } |
("{" ~> term <~ ",") ~ (term <~ "}") ^^ { case f ~ s => TermPair(f, s) } |
("fst" ~> term) ^^ { First.apply } |
("snd" ~> term) ^^ { Second.apply } |
("inl" ~> term) ~ ("as" ~> typeTerm) ^^ { case t~tpe => Inl(t,tpe) } |
("inr" ~> term) ~ ("as" ~> typeTerm) ^^ { case t~tpe => Inr(t,tpe) } |
("case" ~> term) ~ ("of" ~> "inl" ~> ident) ~ ("=>" ~> term) ~
("|" ~> "inr" ~> ident) ~ ("=>" ~> term) ^^ { case t~x1~t1~x2~t2 => Case(t,x1,t1,x2,t2) } |
("fix" ~> term) ^^ { Fix.apply } |
("letrec" ~> ident) ~ (":" ~> typeTerm) ~ ("=" ~> term) ~ ("in" ~> term) ^^ { case id~tpe~t1~t2 => App(Abs(id,tpe,t2),Fix(Abs(id,tpe,t1)))} |
failure("Unmatched Term")
def baseTypeTerm: Parser[Type] =
"Bool" ^^^ TypeBool |
"Nat" ^^^ TypeNat |
("(" ~> typeTerm <~ ")")
def pairTypeTerm: Parser[Type]=
rep1sep(baseTypeTerm, "*") ^^ { case p => p.reduceRight(TypePair.apply) }
def sumTypeTerm: Parser[Type]=
rep1sep(baseTypeTerm, "+") ^^ { case p => p.reduceRight(TypeSum.apply) }
def typeTerm: Parser[Type]=
rep1sep(pairTypeTerm|sumTypeTerm, "->") ^^ { case p => p.reduceRight(TypeFun.apply) }
def numericRec(c:Int,s:Term): (Int,Term) = {
if(c<=0) (0,s)
else numericRec(c-1,Succ(s))
}
def isNumeric(t: Term): Boolean = t match {
case Zero => true
case Pred(v) => isNumeric(v)
case Succ(v) => isNumeric(v)
case _ => false;
}
def isReducible(t: Term): Boolean = try {
reduce(t)
true
} catch { _ =>
false
}
var count: Int = 0
def alpha(t: Abs): Abs = t match {
case Abs(x,tp, t) =>
val uid = x.toString()+"_"+count
count += 1
Abs(uid, tp, recurseUID(x, t, uid))
}
def recurseUID(x: String, t: Term, uid: String): Term = t match {
case True | False | Zero => t
case Var(v) => if (v == x) Var(uid) else t
case Abs(v, tp, t) => if(v==x) Abs(uid, tp, recurseUID(x, t, uid))
else Abs(v, tp, recurseUID(x, t, uid))
case App(t1, t2) => App(recurseUID(x, t1, uid), recurseUID(x, t2, uid))
case Succ(t) => Succ(recurseUID(x, t, uid))
case Pred(t) => Pred(recurseUID(x, t, uid))
case IsZero(t) => IsZero(recurseUID(x, t,uid))
case If(cond, t1, t2) => If(recurseUID(x, cond, uid), recurseUID(x, t1, uid), recurseUID(x, t2, uid))
case TermPair(t1, t2) => TermPair(recurseUID(x, t1, uid), recurseUID(x, t2,uid))
case First(t) => First(recurseUID(x, t, uid))
case Second(t) => Second(recurseUID(x, t, uid))
case Inl(t,tp) => Inl(recurseUID(x, t, uid),tp)
case Inr(t,tp) => Inr(recurseUID(x, t, uid),tp)
case Case(t,x1,t1,x2,t2) =>
val nt1 = if (x1==x) t1 else recurseUID(x, t1, uid)
val nt2 = if (x2==x) t2 else recurseUID(x, t2, uid)
Case(recurseUID(x, t, uid),x1,nt1,x2,nt2)
case Fix(t) => Fix(recurseUID(x, t, uid))
}
def FV(t: Term): List[String] = t match {
case True | False | Zero => List.empty
case Var(v) => List(v)
case Abs(v, tp, lt) => FV(lt).filterNot(_==v)
case App(lt1, lt2) => FV(lt1):::FV(lt2)
case Succ(t) => FV(t)
case Pred(t) => FV(t)
case IsZero(t) => FV(t)
case If(cond,t1,t2) => FV(cond):::FV(t1):::FV(t2)
case TermPair(t1,t2) => FV(t1):::FV(t2)
case First(t) => FV(t)
case Second(t) => FV(t)
case Inl(t,_) => FV(t)
case Inr(t,_) => FV(t)
case Case(t,x1,t1,x2,t2) => FV(t)++: (FV(t1) diff List(x1)) ++: (FV(t2) diff List(x1))
case Fix(t) => FV(t)
}
def subst(t: Term, x: String, s: Term): Term = t match {
case True | False | Zero => t
case Var(v) if (v==x) => s
case Var(_) => t
case If(t1, t2, t3) => If(subst(t1,x,s), subst(t2,x,s), subst(t3,x,s))
case Pred(t) => Pred(subst(t,x,s))
case Succ(t) => Succ(subst(t,x,s))
case IsZero(t) => IsZero(subst(t,x,s))
case t@Abs(v, tp, lt) => if (v==x) t
else if (FV(s).contains(v)) subst(alpha(t),x,s)
else Abs(v,tp,subst(lt,x,s))
case App(t1, t2) => App(subst(t1,x,s), subst(t2,x,s))
case TermPair(t1, t2) => TermPair(subst(t1,x,s), subst(t2,x,s))
case First(t) => First(subst(t,x,s))
case Second(t) => Second(subst(t,x,s))
case Inl(t,tp) => Inl(subst(t,x,s),tp)
case Inr(t,tp) => Inr(subst(t,x,s),tp)
case Case(t,x1,t1,x2,t2) => {
var nt1 = if(x1==x) t1 else subst(t1,x,s);
var nt2 = if(x2==x) t2 else subst(t2,x,s);
Case(subst(t,x,s),x1, nt1, x2, nt2);
}
case Fix(t) => Fix(subst(t,x,s))
}
/** Call by value reducer. */
def reduce(t: Term): Term = t match {
case If(True, t1, t2) => t1
case If(False, t1, t2) => t2
case If(cond, t1, t2) => If(reduce(cond), t1, t2)
case IsZero(Zero) => True
case IsZero(Succ(v)) if isNumeric(v) => False
case IsZero(t) => IsZero(reduce(t))
case Pred(Zero) => Zero
case Pred(Succ(v)) if isNumeric(v) => v
case Pred(v) => Pred(reduce(v))
case Succ(t) => Succ(reduce(t))
case App(t1,t2) if isReducible(t1) => App(reduce(t1),t2)
case App(t1,t2) if isReducible(t2) => App(t1,reduce(t2))
case App(Abs(x, tp, t1), t2) => subst(t1,x,t2)
case First(TermPair(t,_)) if !isReducible(t) => t
case First(t) => First(reduce(t))
case Second(TermPair(_,t)) if !isReducible(t) => t
case Second(t) => Second(reduce(t))
case TermPair(t1,t2) if isReducible(t1) => TermPair(reduce(t1), t2)
case TermPair(t1,t2) if isReducible(t2) => TermPair(t1, reduce(t2))
case Inl(t,v) => Inl(reduce(t),v)
case Inr(t,v) => Inr(reduce(t),v)
case Case(tt,x1,t1,x2,t2) if !isReducible(tt) => tt match {
case Inl(v,_) => subst(t1,x1,v)
case Inr(v,_) => subst(t2,x2,v)
case _ => throw NoRuleApplies(t)
}
case Case(t,x1,t1,x2,t2) => Case(reduce(t),x1,t1,x2,t2)
case Fix(Abs(x,_,t1)) => subst(t1,x,t)
case Fix(t) => Fix(reduce(t))
case _ => throw NoRuleApplies(t)
}
/** Thrown when no reduction rule applies to the given term. */
case class NoRuleApplies(t: Term) extends Exception(t.toString)
/** Print an error message, together with the position where it occured. */
case class TypeError(t: Term, msg: String) extends Exception(msg) {
override def toString = msg + "\n" + t
}
/** The context is a list of variable names paired with their type. */
type Context = List[(String, Type)]
/** Returns the type of the given term <code>t</code>.
*
* @param ctx the initial context
* @param t the given term
* @return the computed type
*/
def typeof(ctx: Context, term: Term): Type =
term match {
case True | False => TypeBool
case Zero => TypeNat
case Succ(t) => {
val innerType = typeof(ctx, t)
if(innerType == TypeNat) TypeNat
else throw TypeError(term, f"Not typeable, Should be Nat, is $innerType")
}
case Pred(t) => {
val innerType = typeof(ctx, t)
if(innerType == TypeNat) TypeNat
else throw TypeError(term, f"Not typeable, Should be Nat, is $innerType")
}
case IsZero(t) => {
val innerType = typeof(ctx, t)
if(innerType == TypeNat) TypeBool
else throw TypeError(term, f"Not typeable, Should be Nat, is $innerType")
}
case If(cond, t1, t2) => {
val condType = typeof(ctx, cond)
val ifType = typeof(ctx, t1)
val elseType = typeof(ctx, t2)
if(condType == TypeBool && ifType == elseType) ifType
else throw TypeError(term, f"Not typeable, If not consistent: $condType $ifType, $elseType")
}
case Var(name) => ctx.find { _._1 == name } match {
case Some(_, t) => t
case None => throw TypeError(term, f"Not typeable, $name not found")
}
case TermPair(t1, t2) => TypePair(typeof(ctx, t1), typeof(ctx, t2))
case First(t) => typeof(ctx,t) match {
case TypePair(t1, t2) => t1
case _ => throw TypeError(term, f"Not typeable. Should be Pair, found ${typeof(ctx, t)}")
}
case Second(t) => typeof(ctx,t) match {
case TypePair(t1, t2) => t2
case _ => throw TypeError(term, f"Not typeable. Should be Pair, found ${typeof(ctx, t)}")
}
case App(t1, t2) => {
val t2type = typeof(ctx, t2)
val t1type = typeof(ctx, t1)
t1type match {
case TypeFun(funt1, funt2) if funt1 == t2type => funt2
case TypeFun(funt1, funt2) => throw TypeError(term, f"Not typeable. Should be $funt1, found $t2type")
case _ => throw TypeError(term, f"Not typeable. Should be Fun, found $t1type")
}
}
case Abs(x, t1, t) => TypeFun(t1, typeof(ctx.appended((x, t1)), t))
case Inl(t, tpe) => tpe match {
case TypeSum(tp, _) if tp == typeof(ctx, t) => tpe
case _ => throw TypeError(term, f"Not typeable. Should be Sum, found ${typeof(ctx, t)}")
}
case Inr(t, tpe) => tpe match {
case TypeSum(_, tp) if tp == typeof(ctx, t) => tpe
case _ => throw TypeError(term, f"Not typeable. Should be Sum, found ${typeof(ctx, t)}")
}
case Case(t, x1, t1, x2, t2) => typeof(ctx, t) match {
case TypeSum(a1, a2) =>
val ntp1 = typeof(ctx.appended((x1, a1)), t1)
val ntp2 = typeof(ctx.appended((x2, a2)), t2)
if (ntp1 == ntp2) ntp1
else throw new TypeError(term, f"Not typeable. Should be Sum, found ${typeof(ctx, t)}")
case _ => throw new TypeError(term, f"Not typeable. Should be Sum, found ${typeof(ctx, t)}")
}
case Fix(t) => typeof(ctx, t) match {
case TypeFun(tp1, tp2) if tp1==tp2 => tp2
case _ => throw new TypeError(term, f"Not typeable. Should be Fun, found ${typeof(ctx, t)}")
}
case null => throw TypeError(term, f"Unexpected Type: ${typeof(ctx,term)}")
}
def typeof(t: Term): Type = try {
typeof(Nil, t)
} catch {
case err @ TypeError(_, _) =>
Console.println(err)
null
}
/** Returns a stream of terms, each being one step of reduction.
*
* @param t the initial term
* @param reduce the evaluation strategy used for reduction.
* @return the stream of terms representing the big reduction.
*/
def path(t: Term, reduce: Term => Term): LazyList[Term] =
try {
var t1 = reduce(t)
LazyList.cons(t, path(t1, reduce))
} catch {
case NoRuleApplies(_) =>
LazyList.cons(t, LazyList.empty)
}
def main(args: Array[String]): Unit = {
val stdin = new java.io.BufferedReader(new java.io.InputStreamReader(System.in))
val tokens = new lexical.Scanner(stdin.readLine())
phrase(term)(tokens) match {
case Success(trees, _) =>
try {
println("parsed: " + trees)
println("typed: " + typeof(Nil, trees))
for (t <- path(trees, reduce))
println(t)
} catch {
case tperror: Exception => println(tperror.toString)
}
case e =>
println(e)
}
}
}

View File

@@ -0,0 +1,84 @@
package fos
import scala.util.parsing.input.Positional
/** Abstract Syntax Trees for terms. */
sealed abstract class Term extends Positional
case object True extends Term {
override def toString() = "true"
}
case object False extends Term {
override def toString() = "false"
}
case object Zero extends Term {
override def toString() = "0"
}
case class Succ(t: Term) extends Term {
override def toString() = s"(succ $t)"
}
case class Pred(t: Term) extends Term {
override def toString() = s"(pred $t)"
}
case class IsZero(t: Term) extends Term {
override def toString() = s"(iszero $t)"
}
case class If(cond: Term, t1: Term, t2: Term) extends Term {
override def toString() = s"if $cond then $t1 else $t2"
}
case class Var(name: String) extends Term {
override def toString() = name
}
case class Abs(v: String, tp: Type, t: Term) extends Term {
override def toString() = s"(\\$v: $tp. $t)"
}
case class App(t1: Term, t2: Term) extends Term {
override def toString() = s"($t1 $t2)"
}
case class TermPair(t1: Term, t2: Term) extends Term {
override def toString() = s"{ $t1, $t2 }"
}
case class First(t: Term) extends Term {
override def toString() = s"(fst $t)"
}
case class Second(t: Term) extends Term {
override def toString() = s"(snd $t)"
}
case class Inl(t: Term, tpe: Type) extends Term {
override def toString() = s"(inl $t as $tpe)"
}
case class Inr(t: Term, tpe: Type) extends Term {
override def toString() = s"(inr $t as $tpe)"
}
case class Case(t: Term, x1: String, t1: Term, x2: String, t2: Term) extends Term {
override def toString() = s"(case $t of inl $x1 => $t1 | inr $x2 => $t2)"
}
case class Fix(t: Term) extends Term {
override def toString() = s"(fix $t)"
}
/** Abstract Syntax Trees for types. */
abstract class Type extends Positional
case object TypeBool extends Type {
override def toString() = "Bool"
}
case object TypeNat extends Type {
override def toString() = "Nat"
}
case class TypeFun(t1: Type, t2: Type) extends Type {
override def toString() = s"($t1 -> $t2)"
}
case class TypePair(t1: Type, t2: Type) extends Type {
override def toString() = s"($t1 * $t2)"
}
case class TypeSum(t1: Type, t2: Type) extends Type {
override def toString() = s"($t1 + $t2)"
}

View File

@@ -0,0 +1,280 @@
#!/usr/bin/env python3
# This script was contributed by Timothée Loyck Andres.
import argparse
import json
import os.path
import pathlib
import shutil
import ssl
import tempfile
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from getpass import getpass
from smtplib import SMTP_SSL, SMTPAuthenticationError, SMTPDataError
from typing import List, Optional, Any, Union
# ========================= CONFIG VARIABLES =========================
SMTP_SERVER = 'mail.epfl.ch'
BOT_EMAIL_ADDRESS = 'lamp.fos.bot@gmail.com'
# The minimum number of people in a group
MIN_SCIPERS = 1
# The maximum number of people in a group
MAX_SCIPERS = 3
# The number of the project (set to None to prompt user)
PROJECT_NUMBER = None
# The name of the file in which the submission configuration is stored
CONFIG_FILE_NAME = '.submission_info.json'
# The name of the folder to be zipped
SRC_FOLDER = 'src'
# ====================================================================
project_path = pathlib.Path(__file__).parent.resolve()
config_path = f'{project_path}/{CONFIG_FILE_NAME}'
src_path = f'{project_path}/{SRC_FOLDER}'
tmp_folder = tempfile.gettempdir()
class ConfigData:
def __init__(self, data_values: Optional[dict] = None, **kwargs):
"""
Creates a new data object with the given values. A dictionary may be passed, or keyword arguments for each data
piece.
Contains: email address, username, SCIPER numbers of the group's members, project number
:argument data_values: an optional dictionary containing the required data
:keyword email: the email of the user
:keyword username: the GASPAR id of the user
:keyword scipers: the list of SCIPER numbers of the group's members
:keyword project_num: the project's number
"""
if data_values is not None:
data = data_values
elif len(kwargs) > 0:
data = kwargs
else:
data = dict()
self.email: str = data.get('email')
self.username: str = data.get('username')
self.scipers: List[str] = data.get('scipers')
self.project_num: int = data.get('project_num')
def get_config_data(self) -> dict:
return {
'email': self.email,
'username': self.username,
'scipers': self.scipers,
'project_num': self.project_num
}
def get_scipers() -> List:
"""
Retrieves the group's SCIPER numbers.
:return: a list containing the SCIPER numbers of all the members of the FoS group
"""
def is_sciper(string: str) -> bool:
try:
return len(string) == 6 and int(string) > 0
except TypeError:
return False
num_scipers = None
scipers: List[str] = []
while num_scipers is None:
num_scipers = get_sanitized_input(
"Number of people in the group: ",
int,
predicate=lambda n: MIN_SCIPERS <= n <= MAX_SCIPERS,
predicate_error_msg=f"The number of people must be between {MIN_SCIPERS} and {MAX_SCIPERS} included."
)
for i in range(num_scipers):
sciper = None
while sciper is None:
sciper = get_sanitized_input(
f"SCIPER {i + 1}: ",
predicate=is_sciper,
predicate_error_msg="Invalid SCIPER number. Please try again."
)
scipers.append(sciper)
return scipers
def get_sanitized_input(prompt: str, value_type: type = str, **kwargs) -> Optional[Any]:
"""
Sanitizes the user's input.
:param prompt: the message to be displayed for the user
:param value_type: the type of value that we expect, for example str or int
:keyword allow_empty: allow the input to be empty. The returned string may be the empty string
:keyword predicate: a function that, when applied to the sanitized input, checks if it is valid
:keyword predicate_error_msg: a message to be displayed if the predicate returns false on the input
:return: the input as the passed type, or None if the input contained only whitespaces or if the type cast failed
"""
str_value = input(prompt).strip()
if len(str_value) > 0 or kwargs.get('allow_empty'):
try:
value = value_type(str_value)
p = kwargs.get('predicate')
if p is not None and not p(value):
if kwargs.get('predicate_error_msg') is None:
print("Invalid value. Please try again.")
elif len(kwargs.get('predicate_error_msg')) > 0:
print(kwargs.get('predicate_error_msg'))
return None
return value
except TypeError:
raise TypeError(f"Incorrect value type: {value_type}")
except ValueError:
print(f"The value could not be interpreted as type {value_type.__name__}. Please try again.")
return None
def get_config(from_file: bool = True) -> ConfigData:
"""
Retrieves the configuration for sending the email. It may be fetched from a configuration file, or if it does not
exist or is incomplete, it will ask the user for the data, then write it to the config file.
:param from_file: whether to retrieve the configuration from the config file if it exists. Default is True
:return: the configuration to use for the email
"""
data = ConfigData()
# Set project number if it is already specified
data.project_num = PROJECT_NUMBER
if from_file and not os.path.isfile(config_path):
print('Please provide data that will be used to submit your project.')
print(f'This information (sans the password) will be saved in: ./{CONFIG_FILE_NAME}')
if from_file and os.path.isfile(config_path):
with open(config_path, 'r') as config_file:
config = json.load(config_file)
if type(config) is dict:
data = ConfigData(config)
if data.scipers is None:
data.scipers = get_scipers()
while data.email is None:
data.email = get_sanitized_input("Email address: ", predicate=lambda address: '@' in address)
while data.username is None:
data.username = get_sanitized_input("Gaspar ID: ")
while data.project_num is None:
data.project_num = get_sanitized_input("Project number: ", int, predicate=lambda n: n > 0)
set_config(data)
return data
def set_config(data: ConfigData) -> None:
"""
Saves the configuration in the config file.
:param data: the data to be saved
"""
with open(config_path, 'w') as config_file:
json.dump(data.get_config_data(), config_file)
def create_email(frm: str, to: str, subject: str, content: Optional[str] = None,
attachments: Optional[Union[str, List[str]]] = None) -> MIMEMultipart:
"""
Creates an email.
:param frm: the address from which the email is sent
:param to: the address to which send the email
:param subject: the subject of the email
:param content: the content of the email. Can be empty
:param attachments: the attachments of the email. Can be a path or a list of paths
"""
message = MIMEMultipart()
message['From'] = frm
message['To'] = to
message['Subject'] = subject
if content is not None:
# Add content into body of message
message.attach(MIMEText(content, 'plain'))
if attachments is not None:
if type(attachments) is str:
attachments = [attachments]
for attachment_path in attachments:
part = MIMEBase("application", "octet-stream")
with open(attachment_path, 'rb') as attachment:
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header("Content-Disposition", f"attachment; filename={os.path.basename(attachment_path)}")
message.attach(part)
return message
if __name__ == '__main__':
arg_parser = argparse.ArgumentParser(description="Submits the project to the bot for grading.", allow_abbrev=False)
arg_parser.add_argument('-r', '--reset', action='store_true', help="ask for the submission data even if previously "
"specified")
arg_parser.add_argument('-s', '--self', action='store_true', help="send the mail to yourself instead of the bot")
if not os.path.isdir(src_path):
arg_parser.exit(1, f"No {SRC_FOLDER} folder found. Aborting.\n")
args = arg_parser.parse_args()
config: ConfigData = get_config(from_file=not args.reset)
password: str = getpass("Gaspar password: ")
recipient = config.email if args.self else BOT_EMAIL_ADDRESS
mail = create_email(
config.email,
recipient,
f"Project {config.project_num} ({', '.join(config.scipers)})",
attachments=shutil.make_archive(f'{tmp_folder}/{SRC_FOLDER}', 'zip', root_dir=project_path,
base_dir=f'{SRC_FOLDER}')
)
with SMTP_SSL(SMTP_SERVER, context=ssl.create_default_context()) as server:
try:
server.login(config.username, password)
server.sendmail(config.email, recipient, mail.as_string())
print(f"Submission sent to {recipient}.")
except SMTPAuthenticationError as e:
if e.smtp_code == 535:
print(f"Wrong GASPAR ID ({config.username}) or password. Your ID will be asked for again on the next"
" run.")
# Remove (potentially) incorrect ID from config
config.username = None
set_config(config)
exit(2)
else:
raise
except SMTPDataError as e:
if e.smtp_code == 550:
print("You email address seems to be incorrect. It will be asked for again on the next run.")
# Remove incorrect address from config
config.email = None
set_config(config)
exit(2)
else:
raise