Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,19 @@ If you'd like to contribute to Glotter2, read our [contributing guidelines](./CO

### Glotter2 releases

* Future
* 0.15.0
* Remove `Singleton` class. This affects the `Settings` and `ContainerFactory`
classes:
* Instead of `Settings()`, use `get_settings()`.
* Instead of `ContainerFactory()`, use `get_container_factory()`.
* Instead of `Settings()`, use `get_settings()`
* Instead of `ContainerFactory()`, use `get_container_factory()`
* Use [glotter2-core](https://github.com/rzuckerm/glotter2) for the following:
* Parse settings (`CoreSettingsParser`)
* Provide common source information (`CoreSource`)
* Provide common project mixin (`CoreProjectMixin`)
* `TestInfo` class is no part of `glotter2-core`. This adds the following:
* Language display name (e.g., `PHP`, `C++`, etc.)
* Allow for untestable languages by allowing `container` to be empty
* Add notes for a language
* 0.14.0:
* Add support for common test strings to reduce duplication in tests
* 0.13.0:
Expand Down
72 changes: 70 additions & 2 deletions doc/directory-level-configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,25 @@ Directory Level Configuration
Each directory that contains code that you want Glotter2 to recognize requires a file named ``testinfo.yml``.
This file contains settings pertinent only to the current directory but does not affect child directories.

There are two root level sections.
There are these root level sections.

Language Display Name
=====================

``language_display_name`` is the actual name of the language. If omitted, this field is derived from the
name of the directory. For example:

================ =====================
Directory Name Language Display Name
================ =====================
``python`` ``Python``
``go`` ``Go``
``objective-c`` ``Objective C``
``visual-basic`` ``Visual Basic``
``c-plus-plus`` ``C++``
``c-sharp`` ``C#``
``c-star`` ``C*``
================ =====================

Folder
======
Expand Down Expand Up @@ -40,6 +58,7 @@ Container
=========

``container`` contains settings that help Glotter2 know how to build and run sources in this directory.
If omitted, then this indicates that the language is untestable.

It has the following settings.

Expand Down Expand Up @@ -83,10 +102,15 @@ Jinja Format Description
``source.full_path`` The full path to the source file including its name and extension
==================== ===========

Notes
=====

``notes`` is a list of notes for the language.

Example
=======

The following is an example ``testinfo.yml`` file for a directory containing sources in go.
The following is an example ``testinfo.yml`` file for a directory containing sources in ``go``.

.. code-block:: yaml

Expand All @@ -99,3 +123,47 @@ The following is an example ``testinfo.yml`` file for a directory containing sou
tag: "1.12-alpine"
build: "go build -o {{ source.name }} {{ source.name }}{{ source.extension }}"
cmd: "./{{ source.name }}"

The following is an example ``testinfo.yml`` for a directory containing sources in ``php``.

.. code-block:: yaml

language_display_name: "PHP"
folder:
extension: ".php"
naming: "hyphen"

container:
image: "php"
tag: "8.4-alpine"
cmd: "php {{ source.name }}{{ source.extension }}"

The following is an example ``testinfo.yml`` for a directory containing sources in ``m4``.

.. code-block:: yaml

language_display_name: "m4"

folder:
extension: ".m4"
naming: "hyphen"

container:
image: "m4"
tag: "latest-alpine"
cmd: "run-m4 {{ source.name }}{{ source.extension }}"

notes:
- "m4 takes all of the command-line arguments and encloses them in a backtick (`) and single quote (')"

The following is an example ``testinfo.yml`` for a directory containing sources for an untestable language
such as Mathematica.

.. code-block:: yaml

folder:
extension: ".nb"
naming: "hyphen"

notes:
- "Mathematica is untestable because it requires a commercial license"
81 changes: 2 additions & 79 deletions glotter/project.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
from enum import Enum, auto
from typing import Annotated, ClassVar, Dict, List, Optional

from glotter_core.project import AcronymScheme, CoreProjectMixin
from pydantic import BaseModel, Field, ValidationInfo, field_validator

from glotter.auto_gen_test import AutoGenTest, AutoGenUseTests
from glotter.errors import raise_simple_validation_error, validate_str_dict, validate_str_list


class NamingScheme(Enum):
hyphen = auto()
underscore = auto()
camel = auto()
pascal = auto()
lower = auto()


class AcronymScheme(Enum):
lower = "lower"
upper = "upper"
two_letter_limit = "two_letter_limit"


class Project(BaseModel):
class Project(BaseModel, CoreProjectMixin):
VALID_REGEX: ClassVar[str] = "^[0-9a-zA-Z]+$"

words: Annotated[
Expand Down Expand Up @@ -88,66 +74,3 @@ def set_tests(self, project: "Project"):

self.requires_parameters = project.requires_parameters
self.use_tests = None

@property
def display_name(self):
return self._as_display()

def get_project_name_by_scheme(self, naming):
"""
gets a project name for a specific naming scheme

:param naming: the naming scheme
:return: the project type formatted by the directory's naming scheme
"""
try:
return {
NamingScheme.hyphen: self._as_hyphen(),
NamingScheme.underscore: self._as_underscore(),
NamingScheme.camel: self._as_camel(),
NamingScheme.pascal: self._as_pascal(),
NamingScheme.lower: self._as_lower(),
}[naming]
except KeyError as e:
raise KeyError(f'Unknown naming scheme "{naming}"') from e

def _as_hyphen(self):
return "-".join(self._try_as_acronym(word, NamingScheme.hyphen) for word in self.words)

def _as_underscore(self):
return "_".join(self._try_as_acronym(word, NamingScheme.underscore) for word in self.words)

def _as_camel(self):
return self.words[0].lower() + "".join(
self._try_as_acronym(word.title(), NamingScheme.camel) for word in self.words[1:]
)

def _as_pascal(self):
return "".join(
self._try_as_acronym(word.title(), NamingScheme.pascal) for word in self.words
)

def _as_lower(self):
return "".join(word.lower() for word in self.words)

def _as_display(self):
return " ".join(
self._try_as_acronym(word.title(), NamingScheme.underscore) for word in self.words
)

def _is_acronym(self, word):
return word.upper() in self.acronyms

def _try_as_acronym(self, word, naming_scheme):
if self._is_acronym(word):
if self.acronym_scheme == AcronymScheme.upper:
return word.upper()
elif self.acronym_scheme == AcronymScheme.lower:
return word.lower()
elif len(word) <= 2 and naming_scheme in [
NamingScheme.camel,
NamingScheme.pascal,
]:
return word.upper()

return word
77 changes: 15 additions & 62 deletions glotter/settings.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import os
from dataclasses import dataclass
from functools import cache
from typing import Dict, Optional
from warnings import warn

import yaml
from glotter_core.project import AcronymScheme
from glotter_core.settings import CoreSettingsParser
from pydantic import (
BaseModel,
Field,
Expand All @@ -14,7 +15,7 @@
)

from glotter.errors import get_error_details, raise_simple_validation_error, raise_validation_errors
from glotter.project import AcronymScheme, Project
from glotter.project import Project
from glotter.utils import error_and_exit, indent


Expand Down Expand Up @@ -206,63 +207,15 @@ def validate_projects(self):
return self


class SettingsParser:
@dataclass(frozen=True)
class SettingsParser(CoreSettingsParser):
def __init__(self, project_root):
self._project_root = project_root
self._yml_path = None
self._acronym_scheme = None
self._projects = None
self._source_root = None
self._yml_path = self._locate_yml()

yml = None
if self._yml_path is not None:
yml = self._parse_yml()
else:
self._yml_path = project_root
warn(f'.glotter.yml not found in directory "{project_root}"')

if yml is None:
yml = {}

if not isinstance(yml, dict):
error_and_exit(".glotter.yml does not contain a dict")

config = SettingsConfig(**yml, yml_path=self._yml_path)
self._acronym_scheme = config.settings.acronym_scheme
self._source_root = config.settings.source_root
self._projects = config.projects

@property
def project_root(self):
return self._project_root

@property
def yml_path(self):
return self._yml_path

@property
def source_root(self):
return self._source_root

@property
def acronym_scheme(self):
return self._acronym_scheme

@property
def projects(self):
return self._projects

def _parse_yml(self):
with open(self._yml_path, "r", encoding="utf-8") as f:
contents = f.read()

return yaml.safe_load(contents)

def _locate_yml(self):
for root, _, files in os.walk(self._project_root):
if ".glotter.yml" in files:
path = os.path.abspath(root)
return os.path.join(path, ".glotter.yml")

return None
try:
super().__init__(project_root)
except ValueError as exc:
error_and_exit(str(exc))

config = SettingsConfig(**self.yml, yml_path=self.yml_path)
object.__setattr__(self, "acronym_scheme", config.settings.acronym_scheme)
object.__setattr__(self, "source_root", config.settings.source_root)
object.__setattr__(self, "projects", config.projects)
Loading