Browse Source

Code uploaded.

Ethosa 5 years ago
parent
commit
089d8c0f81
10 changed files with 362 additions and 2 deletions
  1. 25 2
      README.md
  2. 10 0
      akane.nimble
  3. 264 0
      akane/akane.nim
  4. 13 0
      tests/test1/main.nim
  5. 1 0
      tests/test1/nim.cfg
  6. 14 0
      tests/test2/main.nim
  7. 1 0
      tests/test2/nim.cfg
  8. 12 0
      tests/test2/templates/index.html
  9. 21 0
      tests/test3/main.nim
  10. 1 0
      tests/test3/nim.cfg

+ 25 - 2
README.md

@@ -1,2 +1,25 @@
-# akane
-The Nim asynchronous web framework.
+<h1 align="center">Akane</h1>
+<div align="center">The Nim asynchronous web framework.
+
+[![Open Source Love](https://badges.frapsoft.com/os/v1/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/)
+[![Nim language-plastic](https://github.com/Ethosa/yukiko/blob/master/nim-lang.svg)](https://github.com/Ethosa/yukiko/blob/master/nim-lang.svg)
+[![License](https://img.shields.io/github/license/Ethosa/akane)](https://github.com/Ethosa/akane/blob/master/LICENSE)
+
+<h4>Latest version - 0.0.1</h4>
+<h4>Stable version - ?</h4>
+</div>
+
+# Install
+-   `git`: `nimble install https://github.com/Ethosa/akane.git`
+
+# Features
+-   Pages with URL handling methods: `equals`, `startswith`, `endswith`, `regex`,`notfound`.
+-   `templates` folder.
+
+# FAQ
+-   How I can help to develop this project?
+    You can put a :star: :3
+
+<div align="center">
+  Copyright 2020 Ethosa
+</div>

+ 10 - 0
akane.nimble

@@ -0,0 +1,10 @@
+[Package]
+name = "akane"
+author = "Ethosa"
+version = "0.0.1"
+description = "The Nim asynchronous web framework."
+license = "MIT"
+srcDir = "akane"
+
+[Deps]
+Requires: "nim >= 1.0.0"

+ 264 - 0
akane/akane.nim

@@ -0,0 +1,264 @@
+# author: Ethosa
+# ----- CORE ----- #
+import asyncdispatch
+import asynchttpserver
+export asyncdispatch
+export asynchttpserver
+
+# ----- SUPPORT ----- #
+import asyncfile  # loadtemplate
+import strutils  # startsWith, endsWith
+export strutils
+import macros
+import json  # urlParams
+export json
+import uri  # decodeUri
+export uri
+import os
+import re  # regex
+export re
+
+
+type
+  ServerRef* = ref object
+    debug*: bool
+    port*: uint16
+    address*: string
+    server*: AsyncHttpServer
+
+
+proc newServer*(address: string = "127.0.0.1",
+                port: uint16 = 5000, debug: bool = false): ServerRef =
+  ## Creates a new ServerRef object.
+  ##
+  ## Arguments:
+  ## -   ``address`` - server address, e.g. "127.0.0.1"
+  ## -   ``port`` - server port, e.g. 5000
+  ## -   ``debug`` - debug mode
+  if not existsDir("templates"):
+    createDir("templates")
+  return ServerRef(
+    address: address, port: port,
+    server: newAsyncHttpServer(), debug: debug
+  )
+
+
+proc loadtemplate*(name: string): Future[string] {.async, inline.} =
+  var
+    file = openAsync(("templates" / name) & ".html")
+    readed = await file.readAll()
+  file.close()
+  return readed
+
+
+proc parseQuery*(request: Request): Future[JsonNode] {.async.} =
+  ## Decodes query.
+  ## e.g.:
+  ##   "a=5&b=10" -> {"a": "5", "b": "10"}
+  var data = request.url.query.split("&")
+  result = %*{}
+  for i in data:
+    var timed = i.split("=")
+    if timed.len > 1:
+      result[decodeUrl(timed[0])] = %decodeUrl(timed[1])
+
+
+macro answer*(request, message: untyped): untyped =
+  ## Responds from server with utf-8.
+  result = newCall(
+    "respond",
+    request,
+    ident("Http200"),
+    newCall(
+      "&",
+      newLit("<head><meta charset='utf-8'></head>"),
+      message
+    )
+  )
+
+
+macro pages*(server: ServerRef, body: untyped): untyped =
+  ## This macro provides convenient page adding.
+  ##
+  ## ..code-block::Nim
+  ##  server.pages:
+  ##    equals("/home"):
+  ##      echo url
+  ##      echo urlParams
+  var
+    stmtlist = newStmtList()
+    notfound_declaration = false
+  stmtlist.add(  # let urlParams: JsonNode = await parseQuery(request)
+    newNimNode(nnkLetSection).add(
+      newNimNode(nnkIdentDefs).add(
+        ident("urlParams"),
+        ident("JsonNode"),
+        newCall(
+          "await",
+          newCall(
+            "parseQuery",
+            ident("request")
+          )
+        )
+      )
+    ),
+    newNimNode(nnkLetSection).add(
+      newNimNode(nnkIdentDefs).add(
+        ident("decoded_url"),
+        ident("string"),
+        newCall(
+          "decodeUrl",
+          newNimNode(nnkDotExpr).add(
+            newNimNode(nnkDotExpr).add(
+              ident("request"), ident("url")
+            ),
+            ident("path")
+          )
+        )
+      )
+    )
+  )
+  stmtlist.add(newNimNode(nnkIfStmt))
+
+  for i in body:  # for each page in statment list.
+    let
+      current = $i[0]
+      path = if i.len == 3: i[1] else: newEmptyNode()
+      slist = if i.len == 3: i[2] else: i[1]
+    if (i.kind == nnkCall and i[0].kind == nnkIdent and
+        (path.kind == nnkStrLit or path.kind == nnkCallStrLit or path.kind == nnkEmpty) and
+        slist.kind == nnkStmtList):
+      if current == "equals":
+        slist.insert(0,
+            newNimNode(nnkLetSection).add(
+              newNimNode(nnkIdentDefs).add(
+                ident("url"),
+                ident("string"),
+                path
+              )
+            )
+          )
+        stmtlist[2].add(  # request.path.url == i[1]
+          newNimNode(nnkElifBranch).add(
+            newCall("==", path, ident("decoded_url")),
+            slist))
+      elif current == "startswith":
+        slist.insert(0,
+          newNimNode(nnkLetSection).add(
+            newNimNode(nnkIdentDefs).add(
+              ident("url"),
+              ident("string"),
+              newCall(
+                "[]",
+                ident("decoded_url"),
+                newCall(
+                  "..^",
+                  newCall("len", path),
+                  newLit(1))
+              )
+            )
+          )
+        )
+        stmtlist[2].add(
+          newNimNode(nnkElifBranch).add(
+            newCall(
+              "startsWith",
+              ident("decoded_url"),
+              path),
+            slist))
+      elif current == "endswith":
+        slist.insert(0,
+          newNimNode(nnkLetSection).add(
+            newNimNode(nnkIdentDefs).add(
+              ident("url"),
+              ident("string"),
+              newCall(
+                "[]",
+                ident("decoded_url"),
+                newCall(
+                  "..^",
+                  newLit(0),
+                  newCall("+", newLit(1), newCall("len", path))
+                )
+              )
+            )
+          )
+          )
+        stmtlist[2].add(
+          newNimNode(nnkElifBranch).add(
+            newCall(
+              "endsWith",
+              ident("decoded_url"),
+              path),
+            slist))
+      elif current == "regex":
+        slist.insert(0,
+            newNimNode(nnkDiscardStmt).add(
+              newCall("match", ident("decoded_url"), path, ident("url"))
+            )
+          )
+        slist.insert(0,
+          newNimNode(nnkVarSection).add(
+            newNimNode(nnkIdentDefs).add(
+              ident("url"),
+              newNimNode(nnkBracketExpr).add(
+                ident("array"),
+                newLit(20),
+                ident("string")
+              ),
+              newEmptyNode()
+            )
+          ))
+        stmtlist[2].add(
+          newNimNode(nnkElifBranch).add(
+            newCall(
+              "match",
+              ident("decoded_url"),
+              path),
+            slist))
+      elif current == "notfound":
+        notfound_declaration = true
+        stmtlist[2].add(newNimNode(nnkElse).add(slist))
+
+  if not notfound_declaration:
+    stmtlist[2].add(
+      newNimNode(nnkElse).add(
+        newCall(
+          "await",
+          newCall(
+            "respond",
+            ident("request"),
+            ident("Http404"),
+            newLit("Not found"))
+          )
+        )
+      )
+
+  result = newNimNode(nnkProcDef).add(
+    newNimNode(nnkPostfix).add(
+      ident("*"), ident("receivepages")  # procedure name.
+    ),
+    newEmptyNode(),  # for template and macros
+    newEmptyNode(),  # generics
+    newNimNode(nnkFormalParams).add(  # proc params
+      newEmptyNode(),  # return type
+      newNimNode(nnkIdentDefs).add(  # param
+        ident("request"),  # param name
+        ident("Request"),  # param type
+        newEmptyNode()  # param default value
+      )
+    ),
+    newNimNode(nnkPragma).add(  # pragma declaration
+      ident("async"),
+      ident("gcsafe")
+    ),
+    newEmptyNode(),
+    stmtlist)
+
+
+macro start*(server: ServerRef): untyped =
+  ## Starts server.
+  result = quote do:
+    if `server`.debug:
+      echo "Server starts on http://", `server`.address, ":", `server`.port
+    waitFor `server`.server.serve(Port(`server`.port), receivepages)

+ 13 - 0
tests/test1/main.nim

@@ -0,0 +1,13 @@
+# author: Ethosa
+# Hello world prorgam.
+import akane
+
+var server = newServer("127.0.0.1", 5000, debug=true)  # default params
+
+
+server.pages:
+  equals("/helloworld"):  # when url is "domain/helloworld"
+    await request.answer "Hello, World!"  # sends utf-8 encoded message.
+
+
+server.start()  # Starts server.

+ 1 - 0
tests/test1/nim.cfg

@@ -0,0 +1 @@
+--path:"../../akane"

+ 14 - 0
tests/test2/main.nim

@@ -0,0 +1,14 @@
+# author: Ethosa
+# Templates.
+import akane
+
+var server = newServer()
+
+
+server.pages:
+  equals("/"):  # when url is "domain/"
+    var index = await loadtemplate("index")
+    await request.answer(index)
+
+
+server.start()  # Starts server.

+ 1 - 0
tests/test2/nim.cfg

@@ -0,0 +1 @@
+--path:"../../akane"

+ 12 - 0
tests/test2/templates/index.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>templates</title>
+  <meta charset="utf-8">
+</head>
+<body>
+  <div align="center">
+    Template test
+  </div>
+</body>
+</html>

+ 21 - 0
tests/test3/main.nim

@@ -0,0 +1,21 @@
+# author: Ethosa
+# equals, startswith, endswith, notfound and regex.
+import akane
+
+var server = newServer()
+
+
+server.pages:
+  equals("/helloworld"):  # when url is "domain/helloworld"
+    await request.answer("Hello, World!")
+  startswith("/get"):  # when url is "domain/get...", try "domain/get100000"
+    await request.answer("I see only \"" & url & "\"")  # url var, contains text after "/get".
+  endswith("/info"):  # when url is ".../info", try "domain/user/info"
+    await request.answer("I see only \"" & url & "\"")  # url var, contains text before "/info".
+  regex(re"\A\/id(\d+)\z"):  # when url is re"domain/id\d+", try "domain/id123" and "domain/idNotNumber"
+    await request.answer("Hello, user with ID" & url[0])  # url var contains array[20, string], matched from URL.
+  notfound:  # `notfound` should be at the end of the remaining cases
+    await request.answer("<h1 align='center'>Sorry, but page not found :(</h1>")
+
+
+server.start()  # Starts server.

+ 1 - 0
tests/test3/nim.cfg

@@ -0,0 +1 @@
+--path:"../../akane"