#wip: Socker HTTP Server Driver.
This commit is contained in:
parent
711e347346
commit
fef6627c83
22
Dockers/Dockerfile.sass.gemini
Normal file
22
Dockers/Dockerfile.sass.gemini
Normal file
@ -0,0 +1,22 @@
|
||||
# Usamos la imagen oficial de Dart Sass basada en Alpine
|
||||
FROM dart:stable AS build
|
||||
|
||||
# Descargamos e instalamos la última versión de Dart Sass
|
||||
RUN apt-get update && apt-get install -y curl tar
|
||||
RUN curl -L https://github.com/sass/dart-sass/releases/download/1.101.0/dart-sass-1.101.0-linux-x64.tar.gz | tar -xzf - -C /opt
|
||||
|
||||
# Imagen final limpia y ligera
|
||||
FROM alpine:latest
|
||||
RUN apk add --no-cache libc6-compat libstdc++
|
||||
|
||||
# Copiamos los binarios desde la etapa anterior
|
||||
COPY --from=build /opt/dart-sass /opt/dart-sass
|
||||
|
||||
# Añadimos SASS al PATH del contenedor
|
||||
ENV PATH="/opt/dart-sass:${PATH}"
|
||||
|
||||
# Configuramos el directorio de trabajo
|
||||
WORKDIR /src
|
||||
|
||||
# Permitimos que reciba los comandos directamente
|
||||
ENTRYPOINT ["sass"]
|
||||
@ -67,6 +67,136 @@
|
||||
"default_routes_files" : "/JSON/AnP.routes.json",
|
||||
"AnP_RoutesManager_end" : null,
|
||||
|
||||
"AnP_HTTPAbstracts_start" : null,
|
||||
"default_http_messages" : {
|
||||
"100" : "Continue",
|
||||
"101" : "Switching Protocols",
|
||||
"102" : "Processing",
|
||||
"103" : "Early Hints",
|
||||
"200" : "OK",
|
||||
"201" : "Created",
|
||||
"202" : "Accepted",
|
||||
"203" : "Non-Authoritative Information",
|
||||
"204" : "No Content",
|
||||
"205" : "Reset Content",
|
||||
"206" : "Partial Content",
|
||||
"207" : "Multi-Status",
|
||||
"208" : "Already Reported",
|
||||
"226" : "IM Used",
|
||||
"300" : "Multiple Choices",
|
||||
"301" : "Moved Permanently",
|
||||
"302" : "Found",
|
||||
"303" : "See Other",
|
||||
"304" : "Not Modified",
|
||||
"305" : "Use Proxy",
|
||||
"306" : "Switch Proxy",
|
||||
"307" : "Temporary Redirect",
|
||||
"308" : "Permanent Redirect",
|
||||
"400" : "Bad Request",
|
||||
"401" : "Unauthorized",
|
||||
"402" : "Payment Required",
|
||||
"403" : "Forbidden",
|
||||
"404" : "Not Found",
|
||||
"405" : "Method Not Allowed",
|
||||
"406" : "Not Acceptable",
|
||||
"407" : "Proxy Authentication Required",
|
||||
"408" : "Request Timeout",
|
||||
"409" : "Conflict",
|
||||
"410" : "Gone",
|
||||
"411" : "Length Required",
|
||||
"412" : "Precondition Failed",
|
||||
"413" : "Payload Too Large",
|
||||
"414" : "URI Too Long",
|
||||
"415" : "Unsupported Media Type",
|
||||
"416" : "Range Not Satisfiable",
|
||||
"417" : "Expectation Failed",
|
||||
"418" : "I'm a teapot",
|
||||
"421" : "Misdirected Request",
|
||||
"422" : "Unprocessable Content",
|
||||
"423" : "Locked",
|
||||
"424" : "Failed Dependency",
|
||||
"425" : "Too Early",
|
||||
"426" : "Upgrade Required",
|
||||
"428" : "Precondition Required",
|
||||
"429" : "Too Many Requests",
|
||||
"431" : "Request Header Fields Too Large",
|
||||
"451" : "Unavailable For Legal Reasons",
|
||||
"500" : "Internal Server Error",
|
||||
"501" : "Not Implemented",
|
||||
"502" : "Bad Gateway",
|
||||
"503" : "Service Unavailable",
|
||||
"504" : "Gateway Timeout",
|
||||
"505" : "HTTP Version Not Supported",
|
||||
"506" : "Variant Also Negotiates",
|
||||
"507" : "Insufficient Storage",
|
||||
"508" : "Loop Detected",
|
||||
"510" : "Not Extended",
|
||||
"511" : "Network Authentication Required",
|
||||
"218" : "This is fine",
|
||||
"419" : "Page Expired",
|
||||
"420" : "Method Failure",
|
||||
"430" : "Request Header Fields Too Large",
|
||||
"450" : "Blocked by Windows Parental Controls",
|
||||
"498" : "Invalid Token",
|
||||
"499" : "Token Required",
|
||||
"509" : "Bandwidth Limit Exceeded",
|
||||
"529" : "Site is overloaded",
|
||||
"530" : "Site is frozen",
|
||||
"540" : "Temporarily Disabled",
|
||||
"598" : "Network read timeout error",
|
||||
"599" : "Network Connect Timeout Error",
|
||||
"783" : "Unexpected Token",
|
||||
"440" : "Login Time-out",
|
||||
"449" : "Retry With",
|
||||
"444" : "No Response",
|
||||
"494" : "Request header too large",
|
||||
"495" : "SSL Certificate Error",
|
||||
"496" : "SSL Certificate Required",
|
||||
"497" : "HTTP Request Sent to HTTPS Port",
|
||||
"520" : "Web Server Returned an Unknown Error",
|
||||
"521" : "Web Server Is Down",
|
||||
"522" : "Connection Timed Out",
|
||||
"523" : "Origin Is Unreachable",
|
||||
"524" : "A Timeout Occurred",
|
||||
"525" : "SSL Handshake Failed",
|
||||
"526" : "Invalid SSL Certificate",
|
||||
"527" : "Railgun Error",
|
||||
"561" : "Unauthorized",
|
||||
"110" : "Response is Stale",
|
||||
"111" : "Revalidation Failed",
|
||||
"112" : "Disconnected Operation",
|
||||
"113" : "Heuristic Expiration",
|
||||
"199" : "Miscellaneous Warning",
|
||||
"214" : "Transformation Applied",
|
||||
"299" : "Miscellaneous Persistent Warning"
|
||||
},
|
||||
"protocol" : "HTTP",
|
||||
"protocol_version" : "1.1",
|
||||
"default_protocol_code" : 200,
|
||||
"http_server_name" : "AnP",
|
||||
"http_server_version" : "0.0.1",
|
||||
"default_cors" : "*",
|
||||
"http_access_control_maximum_age" : 84600,
|
||||
"http_keep_alive_timeout" : 5,
|
||||
"http_keep_alive_maximum" : 100,
|
||||
"default_http_charset" : "UTF-8",
|
||||
"default_http_mime" : "text/html",
|
||||
"http_accept_range" : "bytes",
|
||||
"http_response_header" : [
|
||||
"{protocol}/{protocol_version} {protocol_code} {protocol_message}",
|
||||
"Date: {date}",
|
||||
"Server: {server}/{server_version}",
|
||||
"Last-Modified: {last_modified} ",
|
||||
"Accept-Ranges: {accept_range}",
|
||||
"Content-Length: {response_length}",
|
||||
"Access-Control-Max-Age: {access_control_max_age}",
|
||||
"Keep-Alive: timeout={keep_alive_timeout}, max={keep_alive_maximum}",
|
||||
"Access-Control-Allow-Origin: {cors}",
|
||||
"Connection: Keep-Alive",
|
||||
"Content-type: {mime}; charset={charset}"
|
||||
],
|
||||
"AnP_HTTPAbstracts_end" : null,
|
||||
|
||||
"AnP_WebSocketsServersManager_start" : null,
|
||||
"default_web_sockets_servers" : {
|
||||
"anp" : {
|
||||
@ -80,7 +210,8 @@
|
||||
"AnP_HTTPServersManager_start" : null,
|
||||
"default_http_servers" : {
|
||||
"anp" : {
|
||||
"type" : "HTTPDriver",
|
||||
"type2" : "HTTPServerDriver",
|
||||
"type" : "HTTPSocketServerDriver",
|
||||
"host" : "",
|
||||
"port" : 18000
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Self, Optional, Sequence
|
||||
from re import Match as REMath
|
||||
import datetime
|
||||
from Interfaces.Application.AnPInterface import AnPInterface
|
||||
from Utils.Common import Common
|
||||
|
||||
@ -14,18 +15,37 @@ class HTTPServersAbstract(ABC):
|
||||
|
||||
def __init__(self:Self, anp:AnPInterface, key:str, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
|
||||
self.anp:AnPInterface = anp
|
||||
self.__inputs:dict[str, Any|None] = Common.get_dictionary(inputs)
|
||||
self.host:str = self.DEFAULT_PORT
|
||||
self.port:int = self.DEFAULT_HOST
|
||||
self._inputs:dict[str, Any|None] = Common.get_dictionary(inputs)
|
||||
self.host:str = self.DEFAULT_HOST
|
||||
self.port:int = self.DEFAULT_PORT
|
||||
self._print_data:dict[str, Any|None] = {
|
||||
"port": self.port,
|
||||
"host": self.host
|
||||
}
|
||||
self.http_messages:dict[int, str] = {int(code): message for code, message in anp.settings.get((
|
||||
"default_http_messages", "http_messages"
|
||||
), inputs).items()}
|
||||
self.protocol:str = anp.settings.get(("http_protocol", "protocol"), inputs, "HTTP")
|
||||
self.protocol_version:str = anp.settings.get(("http_protocol_version", "protocol_version"), inputs, "1.1")
|
||||
self.accept_range:str = anp.settings.get(("http_accept_range", "accept_range"), inputs, "bytes")
|
||||
self.access_control_max_age:int = anp.settings.get(("http_access_control_max_age", "access_control_max_age"), inputs, 84600)
|
||||
self.keep_alive_maximum:int = anp.settings.get(("http_keep_alive_maximum", "keep_alive_maximum"), inputs, 100)
|
||||
self.keep_alive_timeout:int = anp.settings.get(("http_keep_alive_timeout", "keep_alive_timeout"), inputs, 5)
|
||||
self.cors:str = anp.settings.get(("http_cors", "cors"), inputs, "*")
|
||||
self.mime:str = anp.settings.get(("default_http_mime", "http_mime", "mime"), inputs, "text/html")
|
||||
self.charset:str = anp.settings.get(("default_http_charset", "http_charset", "charset"), inputs, "UTF-8")
|
||||
self._session_timeout:int = anp.settings.get(("sessions_timeout", "timeout"), inputs, 3600)
|
||||
self.key:str = key
|
||||
|
||||
self.update()
|
||||
|
||||
@staticmethod
|
||||
def format_datetime(datetime:datetime.datetime) -> str:
|
||||
return datetime.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||
|
||||
def get_http_message(self:Self, code:int) -> str:
|
||||
return self.http_messages.get(code, "Unknown Status Code")
|
||||
|
||||
def __update_print_data(self:Self) -> None:
|
||||
self._print_data["port"] = self.port
|
||||
self._print_data["host"] = self.host
|
||||
@ -40,8 +60,8 @@ class HTTPServersAbstract(ABC):
|
||||
|
||||
self.close()
|
||||
|
||||
self.port = self.anp.settings.get(("http_server_port", "http_port", "port"), self.__inputs, self.DEFAULT_PORT)
|
||||
self.host = self.anp.settings.get(("http_server_host", "http_host", "host"), self.__inputs, self.DEFAULT_HOST)
|
||||
self.port = self.anp.settings.get(("http_server_port", "http_port", "port"), self._inputs, self.DEFAULT_PORT)
|
||||
self.host = self.anp.settings.get(("http_server_host", "http_host", "host"), self._inputs, self.DEFAULT_HOST)
|
||||
self.__update_print_data()
|
||||
|
||||
self.anp.settings.get(("http_server_autostart", "http_autostart", "autostart")) and self.start()
|
||||
|
||||
@ -10,7 +10,7 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from http.cookies import SimpleCookie
|
||||
from Abstracts.HTTPServersAbstract import HTTPServersAbstract
|
||||
|
||||
class HTTPDriver(HTTPServersAbstract, ModelAbstract):
|
||||
class HTTPServerDriver(HTTPServersAbstract, ModelAbstract):
|
||||
|
||||
class HTTPRequestHandler(BaseHTTPRequestHandler):
|
||||
|
||||
193
Python/Drivers/HTTPSocketServerDriver.py
Normal file
193
Python/Drivers/HTTPSocketServerDriver.py
Normal file
@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Any, Optional, Self, Sequence
|
||||
from threading import Thread
|
||||
from socket import (
|
||||
socket as Socket,
|
||||
AF_INET as ADDRESS_FAMILY_IPV4,
|
||||
SOCK_STREAM as SOCKET_STREAM,
|
||||
SOL_SOCKET as SOCKET_LAYER,
|
||||
SO_REUSEADDR as SOCKET_REUSE_ADDRESS
|
||||
)
|
||||
import datetime
|
||||
from Interfaces.Application.AnPInterface import AnPInterface
|
||||
from Abstracts.HTTPServersAbstract import HTTPServersAbstract
|
||||
from Models.RequestModel import RequestModel
|
||||
from Utils.Common import Common
|
||||
from Utils.Checks import Check
|
||||
from Utils.Patterns import RE
|
||||
|
||||
class HTTPSocketServerDriver(HTTPServersAbstract):
|
||||
|
||||
def __get(self:Self, keys:str|Sequence[str], default:Any|None = None) -> Any|None:
|
||||
|
||||
real_keys:list[str] = []
|
||||
key:str
|
||||
|
||||
for key in Common.get_keys(keys):
|
||||
|
||||
header:str
|
||||
|
||||
for header in (
|
||||
"http_socket_server_driver",
|
||||
"http_server_driver",
|
||||
"http_server",
|
||||
"http",
|
||||
""
|
||||
):
|
||||
|
||||
final_key:str = (header + "_" if header else "") + key
|
||||
|
||||
if final_key not in keys:
|
||||
real_keys.append(final_key)
|
||||
|
||||
return self.anp.settings.get(real_keys, self._inputs, default)
|
||||
|
||||
def __init__(self:Self,
|
||||
anp:AnPInterface,
|
||||
key:str,
|
||||
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
|
||||
) -> None:
|
||||
|
||||
self.__server:Socket|None = None
|
||||
self.__thread:Thread|None = None
|
||||
self.__working:bool = False
|
||||
super().__init__(anp, key, inputs)
|
||||
self.__maximum_connections:int = self.__get("maximum_connections", 5)
|
||||
self.__cache_size:int = self.__get("cache_size", 1024)
|
||||
self.__response_header:str = self.__get("response_header")
|
||||
|
||||
def __listen(self:Self) -> None:
|
||||
while self.__working:
|
||||
try:
|
||||
|
||||
client:Socket
|
||||
address:str
|
||||
port:int
|
||||
data:bytes = b""
|
||||
header:str
|
||||
body:str
|
||||
header_lines:list[str]
|
||||
line:str
|
||||
request:RequestModel = RequestModel()
|
||||
get_block:str
|
||||
hash_block:str
|
||||
response:bytes
|
||||
response_body:bytes
|
||||
|
||||
client, (address, port) = self.__server.accept()
|
||||
|
||||
while True:
|
||||
|
||||
buffer:bytes = client.recv(self.__cache_size)
|
||||
|
||||
data += buffer
|
||||
if len(buffer) < self.__cache_size:
|
||||
break
|
||||
|
||||
header, body = RE.DOUBLE_NEW_LINE.split(data.decode("utf-8", errors = "ignore"), 1)
|
||||
header_lines = RE.NEW_LINE.split(header)
|
||||
(
|
||||
request.method,
|
||||
request.path,
|
||||
get_block,
|
||||
hash_block,
|
||||
request.protocol,
|
||||
request.protocol_version
|
||||
) = RE.HTTP_REQUEST.match(header_lines[0]).groups()
|
||||
|
||||
for line in header_lines[1:]:
|
||||
if RE.HTTP_HEADER_PARAMETER.match(line):
|
||||
|
||||
key:str
|
||||
value:str
|
||||
|
||||
key, value = RE.HTTP_HEADER_PARAMETER.match(line).groups()
|
||||
key = key.strip().lower()
|
||||
value = value.strip()
|
||||
if key in request.request_headers:
|
||||
if Check.is_array(request.request_headers[key]):
|
||||
request.request_headers[key].append(value)
|
||||
else:
|
||||
request.request_headers[key] = [request.request_headers[key], value]
|
||||
else:
|
||||
request.request_headers[key] = value
|
||||
|
||||
request.get_variables = self.get_variables_from(get_block)
|
||||
request.post_variables = self.get_variables_from(body)
|
||||
request.cookies = self.load_cookies(request.request_headers.get("cookie"))
|
||||
request.hash_variables = self.get_variables_from(hash_block)
|
||||
|
||||
self.anp.routes.go([self.key], request.method, request.path, request)
|
||||
|
||||
response_body = (
|
||||
request.response if Check.is_binary(request.response) else
|
||||
request.response.encode() if Check.is_string(request.response) else
|
||||
str(request.response).encode())
|
||||
response = Common.string_variables(self.__response_header, {
|
||||
"content_length" : len(response_body),
|
||||
"protocol" : request.get("protocol", request.protocol or self.protocol),
|
||||
"protocol_version" : request.get("protocol_version", request.protocol_version or self.protocol_version),
|
||||
"http_code" : request.response_code,
|
||||
"http_message" : self.get_http_message(request.response_code),
|
||||
"date" : self.format_datetime(datetime.datetime.now(datetime.timezone.utc)),
|
||||
"last_modified" : self.format_datetime(request.last_modified or request.get("last_modified") or datetime.datetime.now(datetime.timezone.utc)),
|
||||
"accept_range" : request.get("accept_range", self.accept_range),
|
||||
"response_length" : len(response_body),
|
||||
"access_control_max_age" : request.get("access_control_max_age", self.access_control_max_age),
|
||||
"keep_alive_maximum" : request.get("keep_alive_maximum", self.keep_alive_maximum),
|
||||
"keep_alive_timeout" : request.get("keep_alive_timeout", self.keep_alive_timeout),
|
||||
"cors" : request.get("cors", self.cors),
|
||||
"mime" : request.response_mime or request.get("mime", self.mime),
|
||||
"charset" : request.response_charset or request.get("charset", self.charset)
|
||||
}).encode() + response_body
|
||||
|
||||
client.sendall(response)
|
||||
client.close()
|
||||
|
||||
except Exception as exception:
|
||||
self.anp.exception(exception, "anp_http_socket_server_driver_run_exception", {
|
||||
"key" : self.key,
|
||||
"host" : self.host,
|
||||
"port" : self.port
|
||||
})
|
||||
|
||||
def start(self:Self) -> None:
|
||||
|
||||
self.__working = True
|
||||
self.__server = Socket(ADDRESS_FAMILY_IPV4, SOCKET_STREAM)
|
||||
|
||||
try:
|
||||
|
||||
self.__server.setsockopt(SOCKET_LAYER, SOCKET_REUSE_ADDRESS, 1)
|
||||
self.__server.bind((self.host, self.port))
|
||||
self.__server.listen(self.__maximum_connections)
|
||||
|
||||
self.__thread = Thread(target = self.__listen)
|
||||
self.__thread.start()
|
||||
|
||||
except Exception as exception:
|
||||
self.anp.exception(exception, "anp_http_socket_server_driver_start_exception", {
|
||||
"key" : self.key,
|
||||
"host" : self.host,
|
||||
"port" : self.port
|
||||
})
|
||||
|
||||
def close(self:Self) -> None:
|
||||
|
||||
self.__working = False
|
||||
|
||||
if self.__server:
|
||||
try:
|
||||
self.__server.close()
|
||||
except Exception as _:
|
||||
pass
|
||||
self.__server = None
|
||||
|
||||
if self.__thread:
|
||||
try:
|
||||
self.__thread.join()
|
||||
except Exception as _:
|
||||
pass
|
||||
self.__thread = None
|
||||
@ -13,6 +13,7 @@ class RequestModel:
|
||||
self.post_variables:dict[str, Any|None] = {}
|
||||
self.get_variables:dict[str, Any|None] = {}
|
||||
self.url_variables:dict[str, Any|None] = {}
|
||||
self.hash_variables:dict[str, Any|None] = {}
|
||||
self.cookies:dict[str, Any|None] = {}
|
||||
self.variables:dict[str, Any|None] = {}
|
||||
self.request_headers:dict[str, Any|None] = {}
|
||||
@ -20,11 +21,15 @@ class RequestModel:
|
||||
self.route:RouteAbstract|None = None
|
||||
self.response:str|bytes|None = None
|
||||
self.response_mime:str|None = None
|
||||
self.response_charset:str|None = None
|
||||
self.response_code:int = 0
|
||||
self.response_headers:dict[str, Any|None] = {}
|
||||
self.callback:Callable[[RequestModel, str|bytes|None], None]|None = None
|
||||
self.data:Any|None = None
|
||||
self.session:SessionModel|None = session
|
||||
self.protocol:str|None = None
|
||||
self.protocol_version:str|None = None
|
||||
self.last_modified:str|None = None
|
||||
|
||||
def get(self:Self, key:str|Sequence[str], default:Optional[Any] = None) -> Any|None:
|
||||
if self.session is not None:
|
||||
@ -34,7 +39,13 @@ class RequestModel:
|
||||
if results is not None:
|
||||
return results
|
||||
return Common.get_value(key, (
|
||||
self.cookies, self.url_variables, self.get_variables, self.post_variables, self.variables
|
||||
self.cookies,
|
||||
self.url_variables,
|
||||
self.get_variables,
|
||||
self.post_variables,
|
||||
self.variables,
|
||||
self.request_headers,
|
||||
self.hash_variables,
|
||||
), default)
|
||||
|
||||
def set_variables(self:Self, inputs:dict[str, Any|None], on:Optional[str] = None) -> None:
|
||||
@ -43,6 +54,7 @@ class RequestModel:
|
||||
self.url_variables if on == "url" else
|
||||
self.get_variables if on == "get" else
|
||||
self.post_variables if on == "post" else
|
||||
self.hash_variables if on == "hash" else
|
||||
self.variables).update(Common.get_dictionary(Common.load_json(inputs)))
|
||||
|
||||
def set_response(self:Self, data:Any|None) -> None:
|
||||
|
||||
@ -16,3 +16,6 @@ class RE:
|
||||
NEW_LINE:REPattern = re_compile(r'\r\n|[\r\n]')
|
||||
HTTP_VARIABLE:REPattern = re_compile(r'([^=&]+)=([^&]*)')
|
||||
PARENT_PATH:REPattern = re_compile(r'^(.*)[\/\\][^\/\\]+[\/\\]?$')
|
||||
DOUBLE_NEW_LINE:REPattern = re_compile(r'(?:\r\n){2}|\r{2}|\n{2}')
|
||||
HTTP_HEADER_PARAMETER:REPattern = re_compile(r'([^:]+):\s*(.+)')
|
||||
HTTP_REQUEST:REPattern = re_compile(r'^([^\s]+)\s([^\s\?\#]+)(?:\?([^#]+))?(?:\#([^\s]+))?\s([^\/]+)\/([0-9\.]+)$')
|
||||
@ -5,7 +5,8 @@ from typing import Any
|
||||
from Application.AnP import AnP
|
||||
from Controllers.AIController import AIController
|
||||
from Drivers.WebSocketServerDriver import WebSocketServerDriver
|
||||
from Drivers.HTTPDriver import HTTPDriver
|
||||
from Drivers.HTTPSocketServerDriver import HTTPSocketServerDriver
|
||||
from Drivers.HTTPServerDriver import HTTPServerDriver
|
||||
from Drivers.OllamaDriver import OllamaDriver
|
||||
from Drivers.FilesDriver import FilesDriver
|
||||
|
||||
@ -13,7 +14,8 @@ inputs:dict[str, dict[str, Any|None]] = {
|
||||
"default_models" : {
|
||||
"AIController" : AIController,
|
||||
"WebSocketServerDriver" : WebSocketServerDriver,
|
||||
"HTTPDriver" : HTTPDriver,
|
||||
"HTTPSocketServerDriver" : HTTPSocketServerDriver,
|
||||
"HTTPServerDriver" : HTTPServerDriver,
|
||||
"OllamaDriver" : OllamaDriver,
|
||||
"FilesDriver" : FilesDriver
|
||||
}
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
#!/bin/bash
|
||||
directory=`dirname $(readlink -f "$0")`
|
||||
sass $directory/../Public/scss/AnP.scss ../Public/scss/AnP.css;
|
||||
# sass $directory/../Public/scss/AnP.scss $directory/../Public/scss/AnP.css
|
||||
docker build -t dirt-sass -f $directory/../Dockers/Dockerfile.sass.gemini $directory
|
||||
cd $directory/../Public/scss
|
||||
docker run -it --rm -v $(pwd):$(pwd) -w $(pwd) dirt-sass AnP.scss AnP.css;
|
||||
cd $directory
|
||||
|
||||
Loading…
Reference in New Issue
Block a user